github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/cmd/operator-sdk/build/cmd.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package build 16 17 import ( 18 "errors" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "strings" 25 26 "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" 27 "github.com/operator-framework/operator-sdk/internal/pkg/scaffold/input" 28 "github.com/operator-framework/operator-sdk/internal/util/projutil" 29 "github.com/operator-framework/operator-sdk/internal/util/yamlutil" 30 "github.com/operator-framework/operator-sdk/pkg/test" 31 32 "github.com/ghodss/yaml" 33 log "github.com/sirupsen/logrus" 34 "github.com/spf13/cobra" 35 ) 36 37 var ( 38 namespacedManBuild string 39 testLocationBuild string 40 enableTests bool 41 dockerBuildArgs string 42 ) 43 44 func NewCmd() *cobra.Command { 45 buildCmd := &cobra.Command{ 46 Use: "build <image>", 47 Short: "Compiles code and builds artifacts", 48 Long: `The operator-sdk build command compiles the code, builds the executables, 49 and generates Kubernetes manifests. 50 51 <image> is the container image to be built, e.g. "quay.io/example/operator:v0.0.1". 52 This image will be automatically set in the deployment manifests. 53 54 After build completes, the image would be built locally in docker. Then it needs to 55 be pushed to remote registry. 56 For example: 57 $ operator-sdk build quay.io/example/operator:v0.0.1 58 $ docker push quay.io/example/operator:v0.0.1 59 `, 60 RunE: buildFunc, 61 } 62 buildCmd.Flags().BoolVar(&enableTests, "enable-tests", false, "Enable in-cluster testing by adding test binary to the image") 63 buildCmd.Flags().StringVar(&testLocationBuild, "test-location", "./test/e2e", "Location of tests") 64 buildCmd.Flags().StringVar(&namespacedManBuild, "namespaced-manifest", "deploy/operator.yaml", "Path of namespaced resources manifest for tests") 65 buildCmd.Flags().StringVar(&dockerBuildArgs, "docker-build-args", "", "Extra docker build arguments as one string such as \"--build-arg https_proxy=$https_proxy\"") 66 return buildCmd 67 } 68 69 /* 70 * verifyDeploymentImages checks image names of pod 0 in deployments found in the provided yaml file. 71 * This is done because e2e tests require a namespaced manifest file to configure a namespace with 72 * required resources. This function is intended to identify if a user used a different image name 73 * for their operator in the provided yaml, which would result in the testing of the wrong operator 74 * image. As it is possible for a namespaced yaml to have multiple deployments (such as the vault 75 * operator, which depends on the etcd-operator), this is just a warning, not a fatal error. 76 */ 77 func verifyDeploymentImage(yamlFile []byte, imageName string) error { 78 warningMessages := "" 79 scanner := yamlutil.NewYAMLScanner(yamlFile) 80 for scanner.Scan() { 81 yamlSpec := scanner.Bytes() 82 83 yamlMap := make(map[string]interface{}) 84 err := yaml.Unmarshal(yamlSpec, &yamlMap) 85 if err != nil { 86 return fmt.Errorf("could not unmarshal YAML namespaced spec: (%v)", err) 87 } 88 kind, ok := yamlMap["kind"].(string) 89 if !ok { 90 return fmt.Errorf("yaml manifest file contains a 'kind' field that is not a string") 91 } 92 if kind == "Deployment" { 93 // this is ugly and hacky; we should probably make this cleaner 94 nestedMap, ok := yamlMap["spec"].(map[string]interface{}) 95 if !ok { 96 continue 97 } 98 nestedMap, ok = nestedMap["template"].(map[string]interface{}) 99 if !ok { 100 continue 101 } 102 nestedMap, ok = nestedMap["spec"].(map[string]interface{}) 103 if !ok { 104 continue 105 } 106 containersArray, ok := nestedMap["containers"].([]interface{}) 107 if !ok { 108 continue 109 } 110 for _, item := range containersArray { 111 image, ok := item.(map[string]interface{})["image"].(string) 112 if !ok { 113 continue 114 } 115 if image != imageName { 116 warningMessages = fmt.Sprintf("%s\nWARNING: Namespace manifest contains a deployment with image %v, which does not match the name of the image being built: %v", warningMessages, image, imageName) 117 } 118 } 119 } 120 } 121 if err := scanner.Err(); err != nil { 122 return fmt.Errorf("failed to verify deployment image: (%v)", err) 123 } 124 if warningMessages == "" { 125 return nil 126 } 127 return errors.New(warningMessages) 128 } 129 130 func verifyTestManifest(image string) error { 131 namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) 132 if err != nil { 133 return fmt.Errorf("could not read namespaced manifest: (%v)", err) 134 } 135 136 err = verifyDeploymentImage(namespacedBytes, image) 137 // the error from verifyDeploymentImage is just a warning, not fatal error 138 if err != nil { 139 log.Warn(err) 140 } 141 return nil 142 } 143 144 func buildFunc(cmd *cobra.Command, args []string) error { 145 if len(args) != 1 { 146 return fmt.Errorf("command %s requires exactly one argument", cmd.CommandPath()) 147 } 148 149 projutil.MustInProjectRoot() 150 goBuildEnv := append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0") 151 goTrimFlags := []string{"-gcflags", "all=-trimpath=${GOPATH}", "-asmflags", "all=-trimpath=${GOPATH}"} 152 absProjectPath := projutil.MustGetwd() 153 projectName := filepath.Base(absProjectPath) 154 155 // Don't need to build Go code if a non-Go Operator. 156 if projutil.GetOperatorType() == projutil.OperatorTypeGo { 157 managerDir := filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir) 158 outputBinName := filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName) 159 goBuildArgs := append(append([]string{"build"}, goTrimFlags...), "-o", outputBinName, managerDir) 160 buildCmd := exec.Command("go", goBuildArgs...) 161 buildCmd.Env = goBuildEnv 162 if err := projutil.ExecCmd(buildCmd); err != nil { 163 return fmt.Errorf("failed to build operator binary: (%v)", err) 164 } 165 } 166 167 image := args[0] 168 baseImageName := image 169 if enableTests { 170 baseImageName += "-intermediate" 171 } 172 173 log.Infof("Building Docker image %s", baseImageName) 174 175 dbArgs := []string{"build", ".", "-f", "build/Dockerfile", "-t", baseImageName} 176 177 if dockerBuildArgs != "" { 178 splitArgs := strings.Fields(dockerBuildArgs) 179 dbArgs = append(dbArgs, splitArgs...) 180 } 181 182 dbcmd := exec.Command("docker", dbArgs...) 183 if err := projutil.ExecCmd(dbcmd); err != nil { 184 if enableTests { 185 return fmt.Errorf("failed to output intermediate image %s: (%v)", image, err) 186 } 187 return fmt.Errorf("failed to output build image %s: (%v)", image, err) 188 } 189 190 if enableTests { 191 if projutil.IsOperatorGo() { 192 testBinary := filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName+"-test") 193 goTestBuildArgs := append(append([]string{"test"}, goTrimFlags...), "-c", "-o", testBinary, testLocationBuild+"/...") 194 buildTestCmd := exec.Command("go", goTestBuildArgs...) 195 buildTestCmd.Env = goBuildEnv 196 if err := projutil.ExecCmd(buildTestCmd); err != nil { 197 return fmt.Errorf("failed to build test binary: (%v)", err) 198 } 199 } 200 201 // if a user is using an older sdk repo as their library, make sure they have required build files 202 testDockerfile := filepath.Join(scaffold.BuildTestDir, scaffold.DockerfileFile) 203 _, err := os.Stat(testDockerfile) 204 if err != nil && os.IsNotExist(err) { 205 206 log.Info("Generating build manifests for test-framework.") 207 208 cfg := &input.Config{ 209 Repo: projutil.CheckAndGetProjectGoPkg(), 210 AbsProjectPath: absProjectPath, 211 ProjectName: projectName, 212 } 213 214 s := &scaffold.Scaffold{} 215 switch t := projutil.GetOperatorType(); t { 216 case projutil.OperatorTypeGo: 217 err = s.Execute(cfg, 218 &scaffold.TestFrameworkDockerfile{}, 219 &scaffold.GoTestScript{}, 220 &scaffold.TestPod{Image: image, TestNamespaceEnv: test.TestNamespaceEnv}, 221 ) 222 case projutil.OperatorTypeAnsible: 223 return fmt.Errorf("test scaffolding for Ansible Operators is not implemented") 224 case projutil.OperatorTypeHelm: 225 return fmt.Errorf("test scaffolding for Helm Operators is not implemented") 226 default: 227 return projutil.ErrUnknownOperatorType{} 228 } 229 230 if err != nil { 231 return fmt.Errorf("test framework manifest scaffold failed: (%v)", err) 232 } 233 } 234 235 log.Infof("Building test Docker image %s", image) 236 237 testDbArgs := []string{"build", ".", "-f", testDockerfile, "-t", image, "--build-arg", "NAMESPACEDMAN=" + namespacedManBuild, "--build-arg", "BASEIMAGE=" + baseImageName} 238 239 if dockerBuildArgs != "" { 240 splitArgs := strings.Fields(dockerBuildArgs) 241 testDbArgs = append(testDbArgs, splitArgs...) 242 } 243 244 testDbcmd := exec.Command("docker", testDbArgs...) 245 if err := projutil.ExecCmd(testDbcmd); err != nil { 246 return fmt.Errorf("failed to output test image %s: (%v)", image, err) 247 } 248 // Check image name of deployments in namespaced manifest 249 if err := verifyTestManifest(image); err != nil { 250 return nil 251 } 252 } 253 254 log.Info("Operator build complete.") 255 return nil 256 }