diff --git a/.github/workflows/operatorPRBuildAndDeploy.yml b/.github/workflows/operatorBuildAndDeploy.yml similarity index 69% rename from .github/workflows/operatorPRBuildAndDeploy.yml rename to .github/workflows/operatorBuildAndDeploy.yml index ef840991b..9cf429e73 100644 --- a/.github/workflows/operatorPRBuildAndDeploy.yml +++ b/.github/workflows/operatorBuildAndDeploy.yml @@ -1,5 +1,9 @@ name: Cass Operator Build & Deploy -on: pull_request +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: build_operator_docker: name: Build Cass Operator Docker Image @@ -9,8 +13,11 @@ jobs: GOROOT: /usr/local/go1.13 steps: - uses: actions/checkout@v2 + if: github.event_name == 'pull_request' with: ref: ${{ github.event.pull_request.head.sha }} + - uses: actions/checkout@v2 + if: github.event_name != 'pull_request' - name: Set up Go 1.13 uses: actions/setup-go@v1 with: @@ -31,14 +38,20 @@ jobs: run: | export PATH=$GOROOT/bin:$GOPATH/bin:$PATH mage operator:testGenerateClient - - name: Build docker + - name: Build docker - standard and ubi images env: - MO_BRANCH: ${{ github.event.pull_request.head.ref }} + PR_REF: ${{ github.event.pull_request.head.ref }} + MO_BASE_OS: 'registry.access.redhat.com/ubi7/ubi-minimal:7.8' run: | + if [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then + export MO_BRANCH=${PR_REF} + else + export MO_BRANCH="master" + fi; export PATH=$GOROOT/bin:$GOPATH/bin:$PATH mage operator:testAndBuild - name: Deploy to ECR - if: github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' env: MO_ECR_ID: ${{ secrets.ECR_ID }} MO_ECR_SECRET: ${{ secrets.ECR_SECRET }} @@ -48,7 +61,7 @@ jobs: export MO_TAGS=$(cat ./build/tagsToPush.txt) mage operator:deployToECR - name: Deploy to GH Packages - if: github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' env: MO_GH_USR: 'datastax/cass-operator' MO_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/operatorStableBuildAndDeploy.yml b/.github/workflows/operatorStableBuildAndDeploy.yml deleted file mode 100644 index ad14229f4..000000000 --- a/.github/workflows/operatorStableBuildAndDeploy.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Cass Operator Stable Build & Deploy -on: - push: - branches: - - master -jobs: - build_operator_docker: - name: Build Cass Operator Docker Image - runs-on: ubuntu-latest - env: - GOPATH: /home/runner/go - GOROOT: /usr/local/go1.13 - steps: - - uses: actions/checkout@v2 - - name: Set up Go 1.13 - uses: actions/setup-go@v1 - with: - go-version: 1.13 - - name: Install Mage - run: | - cd /tmp - wget https://github.com/magefile/mage/releases/download/v1.9.0/mage_1.9.0_Linux-64bit.tar.gz - tar -xvf mage_1.9.0_Linux-64bit.tar.gz - mkdir -p $GOPATH/bin - mv mage $GOPATH/bin/mage - sudo chmod +x $GOPATH/bin/mage - - name: Test Sdk Generate - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - mage operator:testSdkGenerate - - name: Test Client Generate - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - mage operator:testGenerateClient - - name: Build docker - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - mage operator:testAndBuild - - name: Deploy to ECR - env: - MO_ECR_ID: ${{ secrets.ECR_ID }} - MO_ECR_SECRET: ${{ secrets.ECR_SECRET }} - MO_ECR_REPO: ${{ secrets.ECR_REPO }} - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - export MO_TAGS=$(cat ./build/tagsToPush.txt) - mage operator:deployToECR - - name: Deploy to GH Packages - env: - MO_GH_USR: 'datastax/cass-operator' - MO_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MO_GH_PKG_REPO: 'datastax/cass-operator/operator' - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - export MO_TAGS=$(cat ./build/tagsToPush.txt) - mage operator:deployToGHPackages diff --git a/mage/k8s/lib.go b/mage/k8s/lib.go index e2d5e6ea5..8bb44e525 100644 --- a/mage/k8s/lib.go +++ b/mage/k8s/lib.go @@ -25,6 +25,7 @@ import ( const ( OperatorImage = "datastax/cass-operator:latest" + OperatorImageUBI = "datastax/cass-operator:latest-ubi" envLoadDevImages = "M_LOAD_DEV_IMAGES" envK8sFlavor = "M_K8S_FLAVOR" ) @@ -38,9 +39,21 @@ var supportedFlavors = map[string]ClusterActions{ "gke": gcp.ClusterActions, } +func getOperatorImage() string { + var img string + if baseOs := os.Getenv(operator.EnvBaseOs); baseOs != "" { + img = "datastax/cass-operator:latest-ubi" + } else { + img = "datastax/cass-operator:latest" + } + return img +} + func loadImagesFromBuildSettings(cfg ClusterActions, settings BuildSettings) { for _, image := range settings.Dev.Images { - shutil.RunVPanic("docker", "pull", image) + // we likely don't always care if we fail to pull + // because we could be testing local images + _ = shutil.RunV("docker", "pull", image) cfg.LoadImage(image) } } @@ -96,7 +109,8 @@ func SetupEmptyCluster() { clusterActions.ApplyDefaultStorage() //TODO make this part optional operator.BuildDocker() - clusterActions.LoadImage(OperatorImage) + operatorImg := getOperatorImage() + clusterActions.LoadImage(operatorImg) } // Bootstrap a cluster, then run Ginkgo integration tests. @@ -131,8 +145,8 @@ func SetupExampleCluster() { mg.Deps(SetupEmptyCluster) kubectl.CreateSecretLiteral("cassandra-superuser-secret", "devuser", "devpass").ExecVPanic() + overrides := map[string]string{"image": getOperatorImage()} var namespace = "default" - var overrides = map[string]string{"image": "datastax/cass-operator:latest"} err := helm_util.Install("./charts/cass-operator-chart", "cass-operator", namespace, overrides) mageutil.PanicOnError(err) diff --git a/mage/operator/deploy.go b/mage/operator/deploy.go index d7c2ba003..c57a850b3 100644 --- a/mage/operator/deploy.go +++ b/mage/operator/deploy.go @@ -58,9 +58,10 @@ func retagAndPush(tags []string, remoteUrl string) { func retagAndPushForGH(tags []string) { pkgRepo := mageutil.RequireEnv(envGHPackageRepo) reg := regexp.MustCompile(`.*\:`) - for _, tag := range tags { + for _, t := range tags { + tag := strings.TrimSpace(t) updatedTag := reg.ReplaceAllString(tag, fmt.Sprintf("%s:", pkgRepo)) - fullGHTag := fmt.Sprintf("%s/%s", ghPackagesRegistry, updatedTag) + fullGHTag := fmt.Sprintf("%s/%s", ghPackagesRegistry, strings.TrimSpace(updatedTag)) dockerTag(tag, fullGHTag) fmt.Printf("- Pushing image %s\n", fullGHTag) dockerutil.Push(fullGHTag).WithCfg(rootBuildDir).ExecVPanic() diff --git a/mage/operator/lib.go b/mage/operator/lib.go index 1d9c52679..af4804ecd 100644 --- a/mage/operator/lib.go +++ b/mage/operator/lib.go @@ -23,6 +23,8 @@ import ( ) const ( + dockerBase = "./operator/docker/base/Dockerfile" + dockerUbi = "./operator/docker/ubi/Dockerfile" rootBuildDir = "./build" sdkBuildDir = "operator/build" diagramsDir = "./docs/developer/diagrams" @@ -36,6 +38,7 @@ const ( envGitBranch = "MO_BRANCH" envVersionString = "MO_VERSION" envGitHash = "MO_HASH" + EnvBaseOs = "MO_BASE_OS" errorUnstagedPreGenerate = ` Unstaged changes detected. @@ -73,7 +76,7 @@ func checkForUnstagedChanges(message string) { func writeBuildFile(fileName string, contents string) { mageutil.EnsureDir(rootBuildDir) outputPath := filepath.Join(rootBuildDir, fileName) - err := ioutil.WriteFile(outputPath, []byte(contents+"\n"), 0666) + err := ioutil.WriteFile(outputPath, []byte(contents), 0666) if err != nil { fmt.Printf("Failed to write file at %s\n", outputPath) panic(err) @@ -349,17 +352,34 @@ func calcFullVersion(settings cfgutil.BuildSettings, git GitData) FullVersion { } } -func runDockerBuild(version FullVersion) []string { +func calcVersionAndTags(version FullVersion, ubiBase bool) (string, []string) { repoPath := "datastax/cass-operator" - versionedTag := fmt.Sprintf("%s:%v", repoPath, version) - tagsToPush := []string{ - versionedTag, - fmt.Sprintf("%s:%s", repoPath, version.Hash), + var versionedTag string + var tagsToPush []string + + if ubiBase { + versionedTag = fmt.Sprintf("%s:%v-ubi", repoPath, version) + tagsToPush = []string{ + versionedTag, + fmt.Sprintf("%s:%s-ubi", repoPath, version.Hash), + fmt.Sprintf("%s:latest-ubi", repoPath), + } + } else { + versionedTag = fmt.Sprintf("%s:%v", repoPath, version) + tagsToPush = []string{ + versionedTag, + fmt.Sprintf("%s:%s", repoPath, version.Hash), + fmt.Sprintf("%s:latest", repoPath), + } } - tags := append(tagsToPush, fmt.Sprintf("%s:latest", repoPath)) + + return versionedTag, tagsToPush +} + +func runDockerBuild(versionedTag string, dockerTags []string, extraBuildArgs []string, dockerfile string) { buildArgs := []string{fmt.Sprintf("VERSION_STAMP=%s", versionedTag)} - dockerutil.Build(".", "", "./operator/Dockerfile", tags, buildArgs).ExecVPanic() - return tagsToPush + buildArgs = append(buildArgs, extraBuildArgs...) + dockerutil.Build(".", "", dockerfile, dockerTags, buildArgs).ExecVPanic() } func runGoBuild(version string) { @@ -423,12 +443,24 @@ func BuildDocker() { settings := cfgutil.ReadBuildSettings() git := getGitData() version := calcFullVersion(settings, git) - operatorTags := runDockerBuild(version) + + //build regular docker image + versionedTag, dockerTags := calcVersionAndTags(version, false) + runDockerBuild(versionedTag, dockerTags, nil, dockerBase) + + if baseOs := os.Getenv(EnvBaseOs); baseOs != "" { + //build ubi docker image + args := []string{fmt.Sprintf("BASE_OS=%s", baseOs)} + ubiVersionedTag, ubiDockerTags := calcVersionAndTags(version, true) + runDockerBuild(ubiVersionedTag, ubiDockerTags, args, dockerUbi) + dockerTags = append(dockerTags, ubiDockerTags...) + } + // Write the versioned image tags to a file in our build // directory so that other targets in the build process can identify // what was built. This is particularly important to know // for targets that retag and deploy to external docker repositories - outputText := strings.Join(operatorTags, "|") + outputText := strings.Join(dockerTags, "|") writeBuildFile("tagsToPush.txt", outputText) } diff --git a/operator/cmd/manager/main.go b/operator/cmd/manager/main.go index 7efecdda0..e46788b4c 100644 --- a/operator/cmd/manager/main.go +++ b/operator/cmd/manager/main.go @@ -8,6 +8,7 @@ import ( "errors" "flag" "fmt" + "io/ioutil" "os" "path/filepath" "runtime" @@ -120,6 +121,7 @@ func main() { ctx := context.Background() // Become the leader before proceeding err = leader.Become(ctx, "cass-operator-lock") + if err != nil { log.Error(err, "could not become leader") os.Exit(1) @@ -132,6 +134,10 @@ func main() { log.Error(err, "Failed to ensure webhook CA configuration") } + if err = readBaseOsIntoEnv(); err != nil { + log.Error(err, "Failed to read base OS into env") + } + // Set default manager options options := manager.Options{ Namespace: namespace, @@ -200,6 +206,36 @@ func main() { } } +func readBaseOsIntoEnv() error { + baseOsArgFilePath := "/var/lib/cass-operator/base_os" + + info, err := os.Stat(baseOsArgFilePath) + if os.IsNotExist(err) { + msg := fmt.Sprintf("Could not locate base OS arg file at %s", baseOsArgFilePath) + err = fmt.Errorf("%s. %v", msg, err) + return err + } + + if info.IsDir() { + msg := fmt.Sprintf("Base OS arg path is a directory not a file: %s", baseOsArgFilePath) + err = fmt.Errorf("%s. %v", msg, err) + return err + } + + rawVal, err := ioutil.ReadFile(baseOsArgFilePath) + if err != nil { + msg := fmt.Sprintf("Failed to read base OS arg file at %s", baseOsArgFilePath) + err = fmt.Errorf("%s. %v", msg, err) + return err + } + + baseOs := strings.TrimSpace(string(rawVal)) + os.Setenv(api.EnvBaseImageOs, baseOs) + log.Info(fmt.Sprintf("%s set to '%s'", api.EnvBaseImageOs, baseOs)) + + return nil +} + // addMetrics will create the Services and Service Monitors to allow the operator export the metrics by using // the Prometheus operator func addMetrics(ctx context.Context, cfg *rest.Config) { diff --git a/operator/Dockerfile b/operator/docker/base/Dockerfile similarity index 95% rename from operator/Dockerfile rename to operator/docker/base/Dockerfile index ec6d3ac3a..3d2a8fdb3 100644 --- a/operator/Dockerfile +++ b/operator/docker/base/Dockerfile @@ -48,6 +48,9 @@ RUN MO_VERSION=${VERSION_STAMP} mage operator:buildGo FROM alpine:3.9 ENV GOPATH=/go + +RUN mkdir -p /var/lib/cass-operator/ +RUN touch /var/lib/cass-operator/base_os WORKDIR /go # All we need from the builder image is operator executable diff --git a/operator/docker/ubi/Dockerfile b/operator/docker/ubi/Dockerfile new file mode 100644 index 000000000..7bed555ca --- /dev/null +++ b/operator/docker/ubi/Dockerfile @@ -0,0 +1,49 @@ +ARG BASE_OS +FROM datastax/cass-operator:latest AS base + +############################################################# + +FROM ${BASE_OS} AS builder + +# Update the builder layer and create user +RUN microdnf update && rm -rf /var/cache/yum && \ + microdnf install shadow-utils && microdnf clean all && \ + useradd -r -s /bin/false -U -G root cassandra + +############################################################# +FROM ${BASE_OS} + +ARG BASE_OS +ARG VERSION_STAMP=DEV + +LABEL maintainer="DataStax, Inc " +LABEL name="cass-operator" +LABEL vendor="DataStax, Inc" +LABEL release="${VERSION_STAMP}" +LABEL summary="DataStax Kubernetes Operator for Apache Cassandra " +LABEL description="The DataStax Kubernetes Operator for Apache Cassandra®. This operator handles the provisioning and day to day management of Apache Cassandra based clusters. Features include configuration deployment, node remediation, and automatic upgrades." + +# Update the builder layer and create user +RUN microdnf update && rm -rf /var/cache/yum && \ + microdnf install procps-ng && microdnf clean all + +# Copy user accounts information +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/shadow /etc/shadow +COPY --from=builder /etc/group /etc/group +COPY --from=builder /etc/gshadow /etc/gshadow + +# Copy operator binary +COPY --from=base /go/bin/operator /operator +COPY ./operator/docker/ubi/LICENSE /licenses/ + +RUN mkdir -p /var/lib/cass-operator/ +RUN echo ${BASE_OS} > /var/lib/cass-operator/base_os + +RUN chown cassandra:root /operator && \ + chmod 0555 /operator + +USER cassandra:root + + +ENTRYPOINT ["/operator"] \ No newline at end of file diff --git a/operator/docker/ubi/LICENSE b/operator/docker/ubi/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/operator/docker/ubi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go index 3cfd49bf1..f02737728 100644 --- a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go +++ b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types.go @@ -6,6 +6,7 @@ package v1beta1 import ( "encoding/json" "fmt" + "os" "github.com/Jeffail/gabs" "github.com/pkg/errors" @@ -18,8 +19,6 @@ import ( ) const ( - defaultConfigBuilderImage = "datastax/cass-config-builder:1.0.0" - // ClusterLabel is the operator's label for the cluster name ClusterLabel = "cassandra.datastax.com/cluster" @@ -46,30 +45,75 @@ const ( // This type exists so there's no chance of pushing random strings to our progress status type ProgressState string +const ( + defaultConfigBuilderImage = "datastax/cass-config-builder:1.0.0" + cassandra_3_11_6 = "datastax/cassandra-mgmtapi-3_11_6:v0.1.5" + cassandra_4_0_0 = "datastax/cassandra-mgmtapi-4_0_0:v0.1.5" + dse_6_8_0 = "datastax/dse-server:6.8.0" + ubi_cassandra_3_11_6 = "datastax/cassandra:3.11.6-ubi7" + ubi_cassandra_4_0_0 = "datastax/cassandra:4.0-ubi7" + ubi_dse_6_8_0 = "datastax/dse-server:6.8.0-ubi7" + ubi_defaultConfigBuilderImage = "datastax/cass-config-builder:1.0.0-ubi7" + EnvBaseImageOs = "BASE_IMAGE_OS" +) + // getImageForServerVersion tries to look up a known image for a server type and version number. // In the event that no image is found, an error is returned func getImageForServerVersion(server, version string) (string, error) { - const ( - cassandra_3_11_6 = "datastax/cassandra-mgmtapi-3_11_6:v0.1.5" - cassandra_4_0_0 = "datastax/cassandra-mgmtapi-4_0_0:v0.1.5" - dse_6_8_0 = "datastax/dse-server:6.8.0" - ) - sv := server + "-" + version + baseImageOs := os.Getenv(EnvBaseImageOs) + + var imageCalc func(string) (string, bool) + var img string + var success bool + var errMsg string + + if baseImageOs == "" { + imageCalc = getImageForDefaultBaseOs + errMsg = fmt.Sprintf("server '%s' and version '%s' do not work together", server, version) + } else { + // if this operator was compiled using a UBI base image + // such as registry.access.redhat.com/ubi7/ubi-minimal:7.8 + // then we use specific cassandra and init container coordinates + // that are built accordingly + errMsg = fmt.Sprintf("server '%s' and version '%s', along with the specified base OS '%s', do not work together", server, version, baseImageOs) + imageCalc = getImageForUniversalBaseOs + } + + img, success = imageCalc(server + "-" + version) + if !success { + return "", fmt.Errorf(errMsg) + } + + return img, nil +} + +func getImageForDefaultBaseOs(sv string) (string, bool) { + switch sv { + case "dse-6.8.0": + return dse_6_8_0, true + case "cassandra-3.11.6": + return cassandra_3_11_6, true + case "cassandra-4.0.0": + return cassandra_4_0_0, true + } + return "", false +} + +func getImageForUniversalBaseOs(sv string) (string, bool) { switch sv { case "dse-6.8.0": - return dse_6_8_0, nil + return ubi_dse_6_8_0, true case "cassandra-3.11.6": - return cassandra_3_11_6, nil + return ubi_cassandra_3_11_6, true case "cassandra-4.0.0": - return cassandra_4_0_0, nil + return ubi_cassandra_4_0_0, true } - err := fmt.Errorf("server '%s' and version '%s' do not work together", server, version) - return "", err + return "", false } type CassandraUser struct { SecretName string `json:"secretName"` - Superuser bool `json:"superuser"` + Superuser bool `json:"superuser"` } // CassandraDatacenterSpec defines the desired state of a CassandraDatacenter @@ -229,8 +273,8 @@ func NewDatacenterCondition(conditionType DatacenterConditionType, status corev1 type CassandraDatacenterStatus struct { Conditions []DatacenterCondition `json:"conditions,omitempty"` - // Deprecated. Use usersUpserted instead. The timestamp at - // which CQL superuser credentials were last upserted to the + // Deprecated. Use usersUpserted instead. The timestamp at + // which CQL superuser credentials were last upserted to the // management API // +optional SuperUserUpserted metav1.Time `json:"superUserUpserted,omitempty"` @@ -306,10 +350,15 @@ func init() { } func (dc *CassandraDatacenter) GetConfigBuilderImage() string { - if dc.Spec.ConfigBuilderImage == "" { - return defaultConfigBuilderImage + var image string + if dc.Spec.ConfigBuilderImage != "" { + image = dc.Spec.ConfigBuilderImage + } else if baseImageOs := os.Getenv(EnvBaseImageOs); baseImageOs != "" { + image = ubi_defaultConfigBuilderImage + } else { + image = defaultConfigBuilderImage } - return dc.Spec.ConfigBuilderImage + return image } // GetServerImage produces a fully qualified container image to pull diff --git a/operator/pkg/reconciliation/constructor.go b/operator/pkg/reconciliation/constructor.go index 3c91e9e1a..6f99388f6 100644 --- a/operator/pkg/reconciliation/constructor.go +++ b/operator/pkg/reconciliation/constructor.go @@ -7,6 +7,7 @@ package reconciliation import ( "fmt" + "os" api "github.com/datastax/cass-operator/operator/pkg/apis/cassandra/v1beta1" "github.com/datastax/cass-operator/operator/pkg/httphelper" @@ -121,7 +122,7 @@ func newStatefulSetForCassandraDatacenterWithDefunctPvcManagedBy( rackName string, dc *api.CassandraDatacenter, replicaCount int) (*appsv1.StatefulSet, error) { - + return newStatefulSetForCassandraDatacenterHelper(rackName, dc, replicaCount, true) } @@ -132,7 +133,7 @@ func usesDefunctPvcManagedByLabel(sts *appsv1.StatefulSet) bool { if ok && value == oplabels.ManagedByLabelDefunctValue { usesDefunct = true break - } + } } return usesDefunct @@ -404,7 +405,11 @@ func buildContainers(dc *api.CassandraDatacenter, serverVolumeMounts []corev1.Vo // server logger container loggerContainer := corev1.Container{} loggerContainer.Name = "server-system-logger" - loggerContainer.Image = "busybox" + if baseImageOs := os.Getenv(api.EnvBaseImageOs); baseImageOs != "" { + loggerContainer.Image = baseImageOs + } else { + loggerContainer.Image = "busybox" + } loggerContainer.Args = []string{ "/bin/sh", "-c", "tail -n+1 -F /var/log/cassandra/system.log", }