diff --git a/Makefile b/Makefile index 38050195..c5dde9ad 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Image URL to use all building/pushing image targets -IMG ?= kubespheredev/s2ioperator:v0.0.11 +IMG ?= kubespheredev/s2ioperator:advanced-2.1.0 all: test manager diff --git a/config/crds/devops_v1alpha1_s2ibuilder.yaml b/config/crds/devops_v1alpha1_s2ibuilder.yaml index 30dd2f9e..872e6ddc 100644 --- a/config/crds/devops_v1alpha1_s2ibuilder.yaml +++ b/config/crds/devops_v1alpha1_s2ibuilder.yaml @@ -217,6 +217,11 @@ spec: type: string type: object type: array + isBinaryURL: + description: IsBinaryURL explain the type of SourceURL. If it is + IsBinaryURL, it will download the file directly without using + git. + type: boolean keepSymlinks: description: KeepSymlinks indicates to copy symlinks as symlinks. Default behavior is to follow symlinks and copy files by content. diff --git a/config/crds/devops_v1alpha1_s2irun.yaml b/config/crds/devops_v1alpha1_s2irun.yaml index 618534f6..942643ba 100644 --- a/config/crds/devops_v1alpha1_s2irun.yaml +++ b/config/crds/devops_v1alpha1_s2irun.yaml @@ -60,6 +60,9 @@ spec: description: NewRevisionId override the default NewRevisionId in its s2ibuilder. type: string + newSourceURL: + description: NewSourceURL is used to download new binary artifacts + type: string newTag: description: NewTag override the default tag in its s2ibuilder, image name cannot be changed. diff --git a/config/default/manager_image_patch.yaml b/config/default/manager_image_patch.yaml index 91e6499e..1ddce023 100644 --- a/config/default/manager_image_patch.yaml +++ b/config/default/manager_image_patch.yaml @@ -8,5 +8,5 @@ spec: spec: containers: # Change the value of image field below to your controller image URL - - image: kubespheredev/s2ioperator:v0.0.11 + - image: kubespheredev/s2ioperator:advanced-2.1.0 name: manager diff --git a/config/samples/b2i/tomcat-deployment.yaml b/config/samples/b2i/tomcat-deployment.yaml new file mode 100644 index 00000000..9bd74607 --- /dev/null +++ b/config/samples/b2i/tomcat-deployment.yaml @@ -0,0 +1,49 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: tomcat-s2i + namespace: default + labels: + app: tomcat-s2i + s2ibuilder-b2i-tomcat: s2ibuilder-b2i-tomcat +spec: + replicas: 0 + selector: + matchLabels: + app: tomcat-s2i + template: + metadata: + creationTimestamp: null + labels: + app: tomcat-s2i + spec: + containers: + - name: tomcat + image: 'runzexia/hello-java:latest' + ports: + - containerPort: 8080 + protocol: TCP + resources: + limits: + cpu: 100m + memory: 200Mi + requests: + cpu: 10m + memory: 10Mi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: IfNotPresent + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst + serviceAccountName: default + serviceAccount: default + securityContext: {} + schedulerName: default-scheduler + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 25% + maxSurge: 25% + revisionHistoryLimit: 10 + progressDeadlineSeconds: 600 diff --git a/config/samples/b2i/tomcat-s2i-builder.yaml b/config/samples/b2i/tomcat-s2i-builder.yaml new file mode 100644 index 00000000..eeceb59d --- /dev/null +++ b/config/samples/b2i/tomcat-s2i-builder.yaml @@ -0,0 +1,25 @@ +apiVersion: devops.kubesphere.io/v1alpha1 +kind: S2iBuilder +metadata: + labels: + controller-tools.k8s.io: "1.0" + name: s2ibuilder-b2i-tomcat + namespace: default + annotations: + devops.kubesphere.io/autoscale: '[{"Kind": "Deployment","Name":"tomcat-s2i","initReplicas":3},{"Kind":"StatefulSet","Name":"tomcat-s2i"}]' +spec: + # Add fields here + config: + displayName: "For Test" + sourceUrl: "https://github.com/kubesphere/devops-java-sample/releases/download/v0.0.1/spring-mvc-java11.war" + imageName: runzexia/hello-java + tag: latest + builderPullPolicy: if-not-present + export: true + isBinaryURL: true + pushAuthentication: + username: UserShouldEnterUserName + password: UserShouldEnterUserPassword + builderImage: kubespheredev/tomcat85-java11-centos7 + buildVolumes: ["s2i_java_cache:/tmp/artifacts"] + diff --git a/config/samples/b2i/tomcat-s2i-run.yaml b/config/samples/b2i/tomcat-s2i-run.yaml new file mode 100644 index 00000000..c40a68e6 --- /dev/null +++ b/config/samples/b2i/tomcat-s2i-run.yaml @@ -0,0 +1,11 @@ +apiVersion: devops.kubesphere.io/v1alpha1 +kind: S2iRun +metadata: + labels: + controller-tools.k8s.io: "1.0" + name: tomcat-s2irun + namespace: default +spec: + builderName: s2ibuilder-b2i-tomcat + newTag: v0.1.0 + diff --git a/config/samples/b2i/tomcat-statefulset.yaml b/config/samples/b2i/tomcat-statefulset.yaml new file mode 100644 index 00000000..604eb837 --- /dev/null +++ b/config/samples/b2i/tomcat-statefulset.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app: tomcat-s2i + s2ibuilder-b2i-tomcat: s2ibuilder-b2i-tomcat + name: tomcat-s2i + namespace: default +spec: + podManagementPolicy: OrderedReady + replicas: 0 + revisionHistoryLimit: 10 + serviceName: test + selector: + matchLabels: + app: tomcat-s2i + template: + metadata: + creationTimestamp: null + labels: + app: tomcat-s2i + spec: + containers: + - image: runzexia/hello-java:latest + imagePullPolicy: Always + name: container-frx25k + resources: + limits: + cpu: 100m + memory: 200Mi + requests: + cpu: 10m + memory: 10Mi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: default + serviceAccountName: default + terminationGracePeriodSeconds: 30 + updateStrategy: + rollingUpdate: + partition: 0 + type: RollingUpdate diff --git a/config/samples/devops_v1alpha2_s2ibuilder_runtimeimage.yaml b/config/samples/devops_v1alpha2_s2ibuilder_runtimeimage.yaml index 815ac211..2511d070 100644 --- a/config/samples/devops_v1alpha2_s2ibuilder_runtimeimage.yaml +++ b/config/samples/devops_v1alpha2_s2ibuilder_runtimeimage.yaml @@ -8,13 +8,13 @@ metadata: spec: config: displayName: "For Test" - builderImage: kubesphere/java-8-centos7:advanced-2.0.0 + builderImage: kubespheredev/java-8-centos7:latest runtimeArtifacts: - source: /deployments - runtimeImage: zhuxiaoyang/s2i-java8-runtime:latest + runtimeImage: kubespheredev/java-8-runtime:latest imageName: kubesphere/sample-java - sourceUrl: https://github.com/soulseen/devops-java-sample.git + sourceUrl: https://github.com/kubesphere/devops-java-sample.git tag: latest revisionId: master builderPullPolicy: if-not-present - buildVolumes: ["s2i_java_cache:/opt/app-root/lib"] \ No newline at end of file + buildVolumes: ["s2i_java_cache:/opt/app-root/lib"] diff --git a/config/templates/java.yaml b/config/templates/java.yaml index 919189ef..779fc1f3 100644 --- a/config/templates/java.yaml +++ b/config/templates/java.yaml @@ -3,16 +3,18 @@ kind: S2iBuilderTemplate metadata: labels: controller-tools.k8s.io: "1.0" + builder-type.kubesphere.io/s2i: "s2i" + builder-type.kubesphere.io/b2i: "b2i" name: java spec: containerInfo: - - builderImage: kubespheredev/java-11-centos7:advanced-2.0.0 - runtimeImage: zhuxiaoyang/s2i-java11-runtime:latest + - builderImage: kubespheredev/java-11-centos7:latest + runtimeImage: kubespheredev/java-11-runtime:latest runtimeArtifacts: - source: "/deployments" buildVolumes: ["s2i_java_cache:/tmp/artifacts"] - - builderImage: kubespheredev/java-8-centos7:advanced-2.0.0 - runtimeImage: zhuxiaoyang/s2i-java8-runtime:latest + - builderImage: kubespheredev/java-8-centos7:latest + runtimeImage: kubespheredev/java-8-runtime:latest runtimeArtifacts: - source: "/deployments" buildVolumes: ["s2i_java_cache:/tmp/artifacts"] @@ -132,65 +134,10 @@ spec: description: "The list of hosts that should be reached directly, bypassing the proxy, that translates into the http.nonProxyHosts system property." required: false defaultValue: "" - - key: AB_JOLOKIA_OFF - type: string - description: "If set disables activation of Jolokia (i.e. echos an empty value). By default, Jolokia is enabled." - required: false - defaultValue: "" - - key: AB_JOLOKIA_CONFIG - type: string - description: " If set uses this file (including path) as Jolokia JVM agent properties (as described in Jolokia's reference manual). If not set, the /opt/jolokia/etc/jolokia.properties will be created using the settings as defined in this document, otherwise the reset of the settings in this document are ignored." - required: false - defaultValue: "" - - key: AB_JOLOKIA_HOST - type: string - description: "Host address to bind to (Default: 0.0.0.0)" - required: false - defaultValue: "" - - key: AB_JOLOKIA_HOST - type: string - description: "Port to use (Default: 8778)" - required: false - defaultValue: "" - - key: AB_JOLOKIA_USER - type: string - description: "User for basic authentication. Defaults to 'jolokia'" - required: false - defaultValue: "" - - key: AB_JOLOKIA_PASSWORD - type: string - description: "Password for basic authentication. By default authentication is switched off." - required: false - defaultValue: "" - - key: AB_JOLOKIA_PASSWORD_RANDOM - type: string - description: "Should a random AB_JOLOKIA_PASSWORD be generated? Generated value will be written to /opt/jolokia/etc/jolokia.pw" - required: false - defaultValue: "" - - key: AB_JOLOKIA_HTTPS - type: string - description: "Switch on secure communication with https. By default self signed server certificates are generated if no serverCert configuration is given in AB_JOLOKIA_OPTS" - required: false - defaultValue: "" - - key: AB_JOLOKIA_ID - type: string - description: "Agent ID to use ($HOSTNAME by default, which is the container id)" - required: false - defaultValue: "" - - key: AB_JOLOKIA_DISCOVERY_ENABLED - type: string - description: "Enable Jolokia discovery. Defaults to false." - required: false - defaultValue: "" - - key: AB_JOLOKIA_OPTS - type: string - description: "Additional options to be appended to the agent configuration. They should be given in the format \"key=value,key=value,...\"" - required: false - defaultValue: "" codeFramework: java defaultBaseImage: kubespheredev/java-8-centos7:advanced-2.0.0 version: 0.0.1 description: "This is a S2I builder template for Java builds whose result can be run directly without any further application server.It's suited ideally for microservices with a flat classpath (including \"far jars\"). This image also provides an easy integration with an Jolokia agent." - iconPath: assets/java.png \ No newline at end of file + iconPath: assets/java.png diff --git a/config/templates/nodejs.yaml b/config/templates/nodejs.yaml index 42d6cb8f..f7669f3f 100644 --- a/config/templates/nodejs.yaml +++ b/config/templates/nodejs.yaml @@ -3,6 +3,7 @@ kind: S2iBuilderTemplate metadata: labels: controller-tools.k8s.io: "1.0" + builder-type.kubesphere.io/s2i: "s2i" name: nodejs spec: containerInfo: @@ -57,4 +58,4 @@ spec: defaultBaseImage: kubespheredev/nodejs-8-centos7 version: 0.0.1 description: "Node.js available as container is a base platform for building and running various Node.js applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices." - iconPath: assets/nodejs.png \ No newline at end of file + iconPath: assets/nodejs.png diff --git a/config/templates/python.yaml b/config/templates/python.yaml index 0cfffd16..bd4c9d5b 100644 --- a/config/templates/python.yaml +++ b/config/templates/python.yaml @@ -3,6 +3,7 @@ kind: S2iBuilderTemplate metadata: labels: controller-tools.k8s.io: "1.0" + builder-type.kubesphere.io/s2i: "s2i" name: python spec: containerInfo: @@ -82,4 +83,4 @@ spec: description: "Python available as container is a base platform for building and running various Python applications and frameworks. Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python's elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms. This container image includes an npm utility, so users can use it to install JavaScript modules for their web applications. There is no guarantee for any specific npm or nodejs version, that is included in the image; those versions can be changed anytime and the nodejs itself is included just to make the npm work." - iconPath: assets/python.png \ No newline at end of file + iconPath: assets/python.png diff --git a/config/templates/tomcat.yaml b/config/templates/tomcat.yaml new file mode 100644 index 00000000..025bd50c --- /dev/null +++ b/config/templates/tomcat.yaml @@ -0,0 +1,141 @@ +apiVersion: devops.kubesphere.io/v1alpha1 +kind: S2iBuilderTemplate +metadata: + labels: + controller-tools.k8s.io: "1.0" + builder-type.kubesphere.io/s2i: "s2i" + builder-type.kubesphere.io/b2i: "b2i" + name: tomcat +spec: + containerInfo: + - builderImage: kubespheredev/tomcat85-java11-centos7:latest + runtimeImage: kubespheredev/tomcat85-java11-runtime:latest + runtimeArtifacts: + - source: "/deployments" + buildVolumes: ["s2i_java_cache:/tmp/artifacts"] + - builderImage: kubespheredev/tomcat85-java8-centos7:latest + runtimeImage: kubespheredev/tomcat85-java8-runtime:latest + runtimeArtifacts: + - source: "/deployments" + buildVolumes: ["s2i_java_cache:/tmp/artifacts"] + environment: + - key: MAVEN_ARGS + type: string + description: "Arguments to use when calling Maven, replacing the default package hawt-app:build -DskipTests -e. Please be sure to run the hawt-app:build goal (when not already bound to the package execution phase), otherwise the startup scripts won't work." + required: false + defaultValue: "" + - key: MAVEN_ARGS_APPEND + type: string + description: "Additional Maven arguments, useful for temporary adding arguments like -X or -am -pl ." + required: false + defaultValue: "" + - key: ARTIFACT_DIR + type: string + description: "Path to target/ where the jar files are created for multi module builds. These are added to ${MAVEN_ARGS}" + required: false + defaultValue: "" + - key: ARTIFACT_COPY_ARGS + type: string + description: "Arguments to use when copying artifacts from the output dir to the application dir. Useful to specify which artifacts will be part of the image. It defaults to -r hawt-app/* when a hawt-app dir is found on the build directory, otherwise jar files only will be included (*.jar)." + required: false + defaultValue: "" + - key: MAVEN_CLEAR_REPO + type: boolean + description: "If set then the Maven repository is removed after the artifact is built. This is useful for keeping the created application image small, but prevents incremental builds. The default is false" + required: false + defaultValue: "" + - key: JAVA_APP_DIR + type: string + description: "the directory where the application resides. All paths in your application are relative to this directory. By default it is the same directory where this startup script resides." + required: false + defaultValue: "" + - key: JAVA_LIB_DIR + type: string + description: "directory holding the Java jar files as well an optional classpath file which holds the classpath. Either as a single line classpath (colon separated) or with jar files listed line-by-line. If not set JAVA_LIB_DIR is the same as JAVA_APP_DIR." + required: false + defaultValue: "" + - key: JAVA_OPTIONS + type: string + description: "options to add when calling java" + required: false + defaultValue: "" + - key: JAVA_MAJOR_VERSION + type: string + description: "a number >= 7. If the version is set then only options suitable for this version are used. When set to 7 options known only to Java > 8 will be removed. For versions >= 10 no explicit memory limit is calculated since Java >= 10 has support for container limits." + required: false + defaultValue: "" + - key: JAVA_MAX_MEM_RATIO + type: string + description: "is used when no -Xmx option is given in JAVA_OPTIONS. This is used to calculate a default maximal Heap Memory based on a containers restriction. If used in a Docker container without any memory constraints for the container then this option has no effect. If there is a memory constraint then -Xmx is set to a ratio of the container available memory as set here. The default is 25 when the maximum amount of memory available to the container is below 300M, 50 otherwise, which means in that case that 50% of the available memory is used as an upper boundary. You can skip this mechanism by setting this value to 0 in which case no -Xmx option is added." + required: false + defaultValue: "" + - key: JAVA_INIT_MEM_RATIO + type: string + description: "is used when no -Xms option is given in JAVA_OPTIONS. This is used to calculate a default initial Heap Memory based on a containers restriction. If used in a Docker container without any memory constraints for the container then this option has no effect. If there is a memory constraint then -Xms is set to a ratio of the container available memory as set here. By default this value is not set." + required: false + defaultValue: "" + - key: JAVA_MAX_CORE + type: string + description: "restrict manually the number of cores available which is used for calculating certain defaults like the number of garbage collector threads. If set to 0 no base JVM tuning based on the number of cores is performed." + required: false + defaultValue: "" + - key: JAVA_DIAGNOSTICS + type: string + description: "set this to get some diagnostics information to standard out when things are happening" + required: false + defaultValue: "" + - key: JAVA_MAIN_CLASS + type: string + description: "main class to use as argument for java. When this environment variable is given, all jar files in $JAVA_APP_DIR are added to the classpath as well as $JAVA_LIB_DIR." + required: false + defaultValue: "" + - key: JAVA_APP_JAR + type: string + description: "A jar file with an appropriate manifest so that it can be started with java -jar if no $JAVA_MAIN_CLASS is set. In all cases this jar file is added to the classpath, too." + required: false + defaultValue: "" + - key: JAVA_APP_NAME + type: string + description: "Name to use for the process" + required: false + defaultValue: "" + - key: JAVA_CLASSPATH + type: string + description: "the classpath to use. If not given, the startup script checks for a file ${JAVA_APP_DIR}/classpath and use its content literally as classpath. If this file doesn't exists all jars in the app dir are added (classes:${JAVA_APP_DIR}/*)." + required: false + defaultValue: "" + - key: JAVA_DEBUG + type: string + description: "If set remote debugging will be switched on" + required: false + defaultValue: "" + - key: JAVA_DEBUG_SUSPEND + type: string + description: "If set enables suspend mode in remote debugging" + required: false + defaultValue: "" + - key: JAVA_DEBUG_PORT + type: string + description: "Port used for remote debugging. Default: 5005" + required: false + defaultValue: "" + - key: HTTP_PROXY + type: string + description: "The URL of the proxy server that translates into the http.proxyHost and http.proxyPort system properties." + required: false + defaultValue: "" + - key: HTTPS_PROXY + type: string + description: "The URL of the proxy server that translates into the https.proxyHost and https.proxyPort system properties." + required: false + defaultValue: "" + - key: NO_PROXY + type: string + description: "The list of hosts that should be reached directly, bypassing the proxy, that translates into the http.nonProxyHosts system property." + required: false + defaultValue: "" + codeFramework: tomcat + defaultBaseImage: kubespheredev/tomcat85-java8-centos7:latest + version: 0.0.1 + description: "This is a S2I builder template for Java builds whose result can be run directly with Tomcat application server." + iconPath: assets/tomcat.png diff --git a/deploy/s2ioperator.yaml b/deploy/s2ioperator.yaml index e31b0a43..bc7c627d 100644 --- a/deploy/s2ioperator.yaml +++ b/deploy/s2ioperator.yaml @@ -217,6 +217,11 @@ spec: type: string type: object type: array + isBinaryURL: + description: IsBinaryURL explain the type of SourceURL. If it is + IsBinaryURL, it will download the file directly without using + git. + type: boolean keepSymlinks: description: KeepSymlinks indicates to copy symlinks as symlinks. Default behavior is to follow symlinks and copy files by content. @@ -238,7 +243,7 @@ spec: nodeAffinityKey: description: The key of Node Affinity. type: string - nodeAffinityValue: + nodeAffinityValues: description: The values of Node Affinity. items: type: string @@ -648,6 +653,9 @@ spec: description: NewRevisionId override the default NewRevisionId in its s2ibuilder. type: string + newSourceURL: + description: NewSourceURL is used to download new binary artifacts + type: string newTag: description: NewTag override the default tag in its s2ibuilder, image name cannot be changed. diff --git a/pkg/apis/devops/v1alpha1/openapi_generated.go b/pkg/apis/devops/v1alpha1/openapi_generated.go index 1af52519..386062fd 100644 --- a/pkg/apis/devops/v1alpha1/openapi_generated.go +++ b/pkg/apis/devops/v1alpha1/openapi_generated.go @@ -1529,6 +1529,13 @@ func schema_pkg_apis_devops_v1alpha1_S2iConfig(ref common.ReferenceCallback) com Format: "", }, }, + "isBinaryURL": { + SchemaProps: spec.SchemaProps{ + Description: "IsBinaryURL explain the type of SourceURL. If it is IsBinaryURL, it will download the file directly without using git.", + Type: []string{"boolean"}, + Format: "", + }, + }, "gitSecretRef": { SchemaProps: spec.SchemaProps{ Description: "GitSecretRef is the BasicAuth Secret of Git Clone", @@ -1712,6 +1719,13 @@ func schema_pkg_apis_devops_v1alpha1_S2iRunSpec(ref common.ReferenceCallback) co Format: "", }, }, + "newSourceURL": { + SchemaProps: spec.SchemaProps{ + Description: "NewSourceURL is used to download new binary artifacts", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"builderName"}, }, diff --git a/pkg/apis/devops/v1alpha1/s2ibuilder_types.go b/pkg/apis/devops/v1alpha1/s2ibuilder_types.go index bae7b5bf..98a8d1bf 100644 --- a/pkg/apis/devops/v1alpha1/s2ibuilder_types.go +++ b/pkg/apis/devops/v1alpha1/s2ibuilder_types.go @@ -401,6 +401,10 @@ type S2iConfig struct { // SourceURL is url of the codes such as https://github.com/a/b.git SourceURL string `json:"sourceUrl"` + // IsBinaryURL explain the type of SourceURL. + // If it is IsBinaryURL, it will download the file directly without using git. + IsBinaryURL bool `json:"isBinaryURL,omitempty"` + // GitSecretRef is the BasicAuth Secret of Git Clone GitSecretRef *corev1.LocalObjectReference `json:"gitSecretRef,omitempty"` diff --git a/pkg/apis/devops/v1alpha1/s2irun_types.go b/pkg/apis/devops/v1alpha1/s2irun_types.go index 211f53e2..ac5101c5 100644 --- a/pkg/apis/devops/v1alpha1/s2irun_types.go +++ b/pkg/apis/devops/v1alpha1/s2irun_types.go @@ -41,6 +41,8 @@ type S2iRunSpec struct { NewTag string `json:"newTag,omitempty"` //NewRevisionId override the default NewRevisionId in its s2ibuilder. NewRevisionId string `json:"newRevisionId,omitempty"` + //NewSourceURL is used to download new binary artifacts + NewSourceURL string `json:"newSourceURL,omitempty"` } // S2iRunStatus defines the observed state of S2iRun diff --git a/pkg/controller/s2irun/ksbuilder_jobs.go b/pkg/controller/s2irun/ksbuilder_jobs.go index dce7a8cc..b2632691 100644 --- a/pkg/controller/s2irun/ksbuilder_jobs.go +++ b/pkg/controller/s2irun/ksbuilder_jobs.go @@ -46,6 +46,7 @@ func (r *ReconcileS2iRun) NewConfigMap(instance *devopsv1alpha1.S2iRun, config d config.Tag = GetNewImageName(instance, config) config.RevisionId = GetNewRevisionId(instance, config) + config.SourceURL = GetNewSourceURL(instance, config) err := r.setDockerSecret(instance, &config) if err != nil { diff --git a/pkg/controller/s2irun/s2irun_controller.go b/pkg/controller/s2irun/s2irun_controller.go index 6f9b48a7..fab1e0b7 100644 --- a/pkg/controller/s2irun/s2irun_controller.go +++ b/pkg/controller/s2irun/s2irun_controller.go @@ -300,6 +300,14 @@ func GetNewRevisionId(instance *devopsv1alpha1.S2iRun, config devopsv1alpha1.S2i } } +func GetNewSourceURL(instance *devopsv1alpha1.S2iRun, config devopsv1alpha1.S2iConfig) string { + if instance.Spec.NewSourceURL != "" { + return instance.Spec.NewSourceURL + } else { + return config.SourceURL + } +} + // ScaleWorkLoads will auto scale workloads define in s2ibuilder's annotations func (r *ReconcileS2iRun) ScaleWorkLoads(instance *devopsv1alpha1.S2iRun, builder *devopsv1alpha1.S2iBuilder) error { if _, ok := instance.Annotations[devopsv1alpha1.S2iRunDoNotAutoScaleAnnotations]; !ok { diff --git a/pkg/webhook/default_server/s2irun/validating/s2irun_create_update_handler.go b/pkg/webhook/default_server/s2irun/validating/s2irun_create_update_handler.go index 61152232..484a329b 100644 --- a/pkg/webhook/default_server/s2irun/validating/s2irun_create_update_handler.go +++ b/pkg/webhook/default_server/s2irun/validating/s2irun_create_update_handler.go @@ -51,7 +51,19 @@ type S2iRunCreateUpdateHandler struct { func (h *S2iRunCreateUpdateHandler) validatingS2iRunFn(ctx context.Context, obj *devopsv1alpha1.S2iRun) (bool, string, error) { origin := &devopsv1alpha1.S2iRun{} - err := h.Client.Get(context.TODO(), types2.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, origin) + builder := &devopsv1alpha1.S2iBuilder{} + + err := h.Client.Get(context.TODO(), types2.NamespacedName{Namespace: origin.Namespace, Name: obj.Spec.BuilderName}, builder) + if err != nil && !k8serror.IsNotFound(err) { + return false, "validate failed", errors.NewFieldInvalidValueWithReason("no", "could not call k8s api") + } + if !k8serror.IsNotFound(err) { + if obj.Spec.NewSourceURL != "" && !builder.Spec.Config.IsBinaryURL { + return false, "validate failed", errors.NewFieldInvalidValueWithReason("newSourceURL", "only b2i could set newSourceURL") + } + } + + err = h.Client.Get(context.TODO(), types2.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, origin) if !k8serror.IsNotFound(err) && origin.Status.RunState != "" && !reflect.DeepEqual(origin.Spec, obj.Spec) { return false, "validate failed", errors.NewFieldInvalidValueWithReason("spec", "should not change s2i run spec when job started") } diff --git a/test/Jenkinsfile.kubesphere b/test/Jenkinsfile.kubesphere index b73fa3a8..edb12a66 100644 --- a/test/Jenkinsfile.kubesphere +++ b/test/Jenkinsfile.kubesphere @@ -49,6 +49,10 @@ spec: sh "sed -i.bak 's#UserShouldEnterUserName#$DOCKER_USERNAME#' ./config/samples/autoscale/*.yaml" sh "sed -i.bak 's#UserShouldEnterUserPassword#$DOCKER_PASSWORD#' ./config/samples/autoscale/*.yaml" } + withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "sed-dockerhub-id" ,)]) { + sh "sed -i.bak 's#UserShouldEnterUserName#$DOCKER_USERNAME#' ./config/samples/b2i/*.yaml" + sh "sed -i.bak 's#UserShouldEnterUserPassword#$DOCKER_PASSWORD#' ./config/samples/b2i/*.yaml" + } withCredentials([string(credentialsId: 'dockerconfig',variable: 'DOCKERCONFIG')]){ sh "sed -i.bak 's#UserShouldEnterBase64DockerConfig#$DOCKERCONFIG#' ./config/samples/secret/*.yaml" } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 432e18c0..82db4802 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -68,7 +68,7 @@ var _ = Describe("", func() { job := &batchv1.Job{} Eventually(func() error { return testClient.Get(context.TODO(), depKey, job) }, timeout, time.Second). Should(Succeed()) - + res := checkAnffinitTaint(job, NodeAffinityKey, NodeAffinityValue, TaintKey) Expect(res).To(Equal(true)) @@ -90,7 +90,7 @@ var _ = Describe("", func() { } } return fmt.Errorf("Failed") - }, time.Minute*5, time.Second*10).Should(Succeed()) + }, time.Minute*10, time.Second*10).Should(Succeed()) Eventually(func() error { return testClient.Delete(context.TODO(), s2ibuilder) }, timeout, time.Second).Should(Succeed()) Eventually(func() bool { return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: s2irun.Name, Namespace: s2irun.Namespace}, s2irun)) @@ -575,6 +575,7 @@ var _ = Describe("", func() { }, timeout, time.Second).Should(BeTrue()) }) + It("Should work well when using git secrets", func() { //create a s2ibuilder secret := &corev1.Secret{} @@ -653,9 +654,171 @@ var _ = Describe("", func() { return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: s2ibuilder.Name, Namespace: s2ibuilder.Namespace}, s2ibuilder)) }, timeout, time.Second).Should(BeTrue()) }) + + It("Should b2i work well when using exactly the example yamls", func() { + //create a s2ibuilder + deploy := &appsv1.Deployment{} + + reader, err := os.Open(workspace + "/config/samples/b2i/tomcat-deployment.yaml") + Expect(err).NotTo(HaveOccurred(), "Cannot read sample yamls") + err = yaml.NewYAMLOrJSONDecoder(reader, 10).Decode(deploy) + Expect(err).NotTo(HaveOccurred(), "Cannot unmarshal yamls") + err = testClient.Create(context.TODO(), deploy) + Expect(err).NotTo(HaveOccurred()) + defer testClient.Delete(context.TODO(), deploy) + + statefulSet := &appsv1.StatefulSet{} + reader, err = os.Open(workspace + "/config/samples/b2i/tomcat-statefulset.yaml") + Expect(err).NotTo(HaveOccurred(), "Cannot read sample yamls") + err = yaml.NewYAMLOrJSONDecoder(reader, 10).Decode(statefulSet) + Expect(err).NotTo(HaveOccurred(), "Cannot unmarshal yamls") + err = testClient.Create(context.TODO(), statefulSet) + Expect(err).NotTo(HaveOccurred()) + defer testClient.Delete(context.TODO(), statefulSet) + + s2ibuilder := &devopsv1alpha1.S2iBuilder{} + reader, err = os.Open(workspace + "/config/samples/b2i/tomcat-s2i-builder.yaml") + Expect(err).NotTo(HaveOccurred(), "Cannot read sample yamls") + err = yaml.NewYAMLOrJSONDecoder(reader, 10).Decode(s2ibuilder) + Expect(err).NotTo(HaveOccurred(), "Cannot unmarshal yamls") + err = testClient.Create(context.TODO(), s2ibuilder) + Expect(err).NotTo(HaveOccurred()) + // Create the S2iRun object and expect the Reconcile and Deployment to be created + s2irun := &devopsv1alpha1.S2iRun{} + reader, err = os.Open(workspace + "/config/samples/b2i/tomcat-s2i-run.yaml") + Expect(err).NotTo(HaveOccurred(), "Cannot read sample yamls") + err = yaml.NewYAMLOrJSONDecoder(reader, 10).Decode(s2irun) + Expect(err).NotTo(HaveOccurred(), "Cannot unmarshal yamls") + err = testClient.Create(context.TODO(), s2irun) + Expect(err).NotTo(HaveOccurred()) + + createdInstance := &devopsv1alpha1.S2iRun{} + Eventually(func() error { + return testClient.Get(context.TODO(), types.NamespacedName{Name: s2irun.Name, Namespace: s2irun.Namespace}, createdInstance) + }, timeout).Should(Succeed()) + + instanceUidSlice := strings.Split(string(createdInstance.UID), "-") + var cmKey = types.NamespacedName{ + Name: s2irun.Name + fmt.Sprintf("-%s", instanceUidSlice[len(instanceUidSlice)-1]) + "-configmap", + Namespace: s2irun.Namespace} + var depKey = types.NamespacedName{ + Name: s2irun.Name + fmt.Sprintf("-%s", instanceUidSlice[len(instanceUidSlice)-1]) + "-job", + Namespace: s2irun.Namespace} + //configmap + cm := &corev1.ConfigMap{} + Eventually(func() error { return testClient.Get(context.TODO(), cmKey, cm) }, timeout). + Should(Succeed()) + Expect(testClient.Delete(context.TODO(), cm)).NotTo(HaveOccurred()) + Eventually(func() error { return testClient.Get(context.TODO(), cmKey, cm) }, timeout). + Should(Succeed()) + + job := &batchv1.Job{} + Eventually(func() error { return testClient.Get(context.TODO(), depKey, job) }, timeout, time.Second). + Should(Succeed()) + + //for our example, the status must be successful + Eventually(func() error { + err = testClient.Get(context.TODO(), depKey, job) + if err != nil { + return err + } + if job.Status.Succeeded == 1 { + //Status of s2ibuilder should update too + tempBuilder := &devopsv1alpha1.S2iBuilder{} + err = testClient.Get(context.TODO(), types.NamespacedName{Name: s2ibuilder.Name, Namespace: s2ibuilder.Namespace}, tempBuilder) + if err != nil { + return err + } + if tempBuilder.Status.LastRunName != nil && *tempBuilder.Status.LastRunName == s2irun.Name && tempBuilder.Status.LastRunState == devopsv1alpha1.Successful && tempBuilder.Status.RunCount == 1 { + return nil + } + } + return fmt.Errorf("Failed") + }, time.Minute*5, time.Second*10).Should(Succeed()) + + Eventually(func() error { + err = testClient.Get(context.TODO(), depKey, job) + if err != nil { + return err + } + if job.Status.Succeeded == 1 { + //Status of s2ibuilder should update too + testDeploy := &appsv1.Deployment{} + err = testClient.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, testDeploy) + if err != nil { + return err + } + if testDeploy.Spec.Replicas != nil && *testDeploy.Spec.Replicas == 3 && testDeploy.Spec.Template.Spec.Containers[0].Image == "runzexia/hello-java:v0.1.0" { + return nil + } + } + return fmt.Errorf("Failed %+v", job) + }, time.Minute*5, time.Second*10).Should(Succeed()) + + Eventually(func() error { + err = testClient.Get(context.TODO(), depKey, job) + if err != nil { + return err + } + if job.Status.Succeeded == 1 { + //Status of s2ibuilder should update too + testStatefulSet := &appsv1.StatefulSet{} + err = testClient.Get(context.TODO(), types.NamespacedName{Name: statefulSet.Name, Namespace: statefulSet.Namespace}, testStatefulSet) + if err != nil { + return err + } + if testStatefulSet.Spec.Replicas != nil && *testStatefulSet.Spec.Replicas == 1 && testStatefulSet.Spec.Template.Spec.Containers[0].Image == "runzexia/hello-java:v0.1.0" { + return nil + } + } + return fmt.Errorf("Failed") + }, time.Minute*5, time.Second*10).Should(Succeed()) + Eventually(func() error { return testClient.Delete(context.TODO(), s2ibuilder) }, timeout, time.Second).Should(Succeed()) + Eventually(func() bool { + + return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: s2irun.Name, Namespace: s2irun.Namespace}, s2irun)) + }, timeout, time.Second).Should(BeTrue()) + Eventually(func() bool { + return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: s2ibuilder.Name, Namespace: s2ibuilder.Namespace}, s2ibuilder)) + }, timeout, time.Second).Should(BeTrue()) + + Eventually(func() error { + + testStatefulSet := &appsv1.StatefulSet{} + err = testClient.Get(context.TODO(), types.NamespacedName{Name: statefulSet.Name, Namespace: statefulSet.Namespace}, testStatefulSet) + if err != nil { + return err + } + if _, ok := testStatefulSet.Labels[s2ibuilder.Name]; ok { + return fmt.Errorf("should remove statefulset label") + } + return nil + }, time.Minute*5, time.Second*10).Should(Succeed()) + + Eventually(func() error { + + testDeployment := &appsv1.Deployment{} + err = testClient.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, testDeployment) + if err != nil { + return err + } + if _, ok := testDeployment.Labels[s2ibuilder.Name]; ok { + return fmt.Errorf("should remove statefulset label") + } + return nil + }, time.Minute*5, time.Second*10).Should(Succeed()) + Eventually(func() error { return testClient.Delete(context.TODO(), statefulSet) }, timeout, time.Second).Should(Succeed()) + Eventually(func() error { return testClient.Delete(context.TODO(), deploy) }, timeout, time.Second).Should(Succeed()) + Eventually(func() bool { + return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, deploy)) + }, timeout, time.Second).Should(BeTrue()) + Eventually(func() bool { + return errors.IsNotFound(testClient.Get(context.TODO(), types.NamespacedName{Name: statefulSet.Name, Namespace: statefulSet.Namespace}, statefulSet)) + }, timeout, time.Second).Should(BeTrue()) + }) }) -func checkAnffinitTaint(job *batchv1.Job, nodeAffinityKey string, nodeAffinityValue string, taintKey string) (bool) { +func checkAnffinitTaint(job *batchv1.Job, nodeAffinityKey string, nodeAffinityValue string, taintKey string) bool { if job.Spec.Template.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions[0].Key != nodeAffinityKey { fmt.Println("Check nodeAffinityKey error.") fmt.Println(job.Spec.Template.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions[0].Key, nodeAffinityKey)