github.com/argoproj/argo-cd@v1.8.7/util/kustomize/kustomize.go (about) 1 package kustomize 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "sort" 10 "strings" 11 12 "github.com/argoproj/gitops-engine/pkg/utils/kube" 13 "github.com/pkg/errors" 14 log "github.com/sirupsen/logrus" 15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 17 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 18 certutil "github.com/argoproj/argo-cd/util/cert" 19 executil "github.com/argoproj/argo-cd/util/exec" 20 "github.com/argoproj/argo-cd/util/git" 21 ) 22 23 // represents a Docker image in the format NAME[:TAG]. 24 type Image = string 25 26 // Kustomize provides wrapper functionality around the `kustomize` command. 27 type Kustomize interface { 28 // Build returns a list of unstructured objects from a `kustomize build` command and extract supported parameters 29 Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions) ([]*unstructured.Unstructured, []Image, error) 30 } 31 32 // NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool. 33 func NewKustomizeApp(path string, creds git.Creds, fromRepo string, binaryPath string) Kustomize { 34 return &kustomize{ 35 path: path, 36 creds: creds, 37 repo: fromRepo, 38 binaryPath: binaryPath, 39 } 40 } 41 42 type kustomize struct { 43 // path inside the checked out tree 44 path string 45 // creds structure 46 creds git.Creds 47 // the Git repository URL where we checked out 48 repo string 49 // optional kustomize binary path 50 binaryPath string 51 } 52 53 func (k *kustomize) getBinaryPath() string { 54 if k.binaryPath != "" { 55 return k.binaryPath 56 } 57 return "kustomize" 58 } 59 60 func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions) ([]*unstructured.Unstructured, []Image, error) { 61 62 if opts != nil { 63 if opts.NamePrefix != "" { 64 cmd := exec.Command(k.getBinaryPath(), "edit", "set", "nameprefix", "--", opts.NamePrefix) 65 cmd.Dir = k.path 66 _, err := executil.Run(cmd) 67 if err != nil { 68 return nil, nil, err 69 } 70 } 71 if opts.NameSuffix != "" { 72 cmd := exec.Command(k.getBinaryPath(), "edit", "set", "namesuffix", "--", opts.NameSuffix) 73 cmd.Dir = k.path 74 _, err := executil.Run(cmd) 75 if err != nil { 76 return nil, nil, err 77 } 78 } 79 if len(opts.Images) > 0 { 80 // set image postgres=eu.gcr.io/my-project/postgres:latest my-app=my-registry/my-app@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 81 // set image node:8.15.0 mysql=mariadb alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 82 args := []string{"edit", "set", "image"} 83 for _, image := range opts.Images { 84 args = append(args, string(image)) 85 } 86 cmd := exec.Command(k.getBinaryPath(), args...) 87 cmd.Dir = k.path 88 _, err := executil.Run(cmd) 89 if err != nil { 90 return nil, nil, err 91 } 92 } 93 94 if len(opts.CommonLabels) > 0 { 95 // edit add label foo:bar 96 args := []string{"edit", "add", "label"} 97 arg := "" 98 for labelName, labelValue := range opts.CommonLabels { 99 if arg != "" { 100 arg += "," 101 } 102 arg += fmt.Sprintf("%s:%s", labelName, labelValue) 103 } 104 args = append(args, arg) 105 cmd := exec.Command(k.getBinaryPath(), args...) 106 cmd.Dir = k.path 107 _, err := executil.Run(cmd) 108 if err != nil { 109 return nil, nil, err 110 } 111 } 112 113 if len(opts.CommonAnnotations) > 0 { 114 // edit add annotation foo:bar 115 args := []string{"edit", "add", "annotation"} 116 arg := "" 117 for annotationName, annotationValue := range opts.CommonAnnotations { 118 if arg != "" { 119 arg += "," 120 } 121 arg += fmt.Sprintf("%s:%s", annotationName, annotationValue) 122 } 123 args = append(args, arg) 124 cmd := exec.Command(k.getBinaryPath(), args...) 125 cmd.Dir = k.path 126 _, err := executil.Run(cmd) 127 if err != nil { 128 return nil, nil, err 129 } 130 } 131 } 132 133 var cmd *exec.Cmd 134 if kustomizeOptions != nil && kustomizeOptions.BuildOptions != "" { 135 params := parseKustomizeBuildOptions(k.path, kustomizeOptions.BuildOptions) 136 cmd = exec.Command(k.getBinaryPath(), params...) 137 } else { 138 cmd = exec.Command(k.getBinaryPath(), "build", k.path) 139 } 140 141 cmd.Env = os.Environ() 142 closer, environ, err := k.creds.Environ() 143 if err != nil { 144 return nil, nil, err 145 } 146 defer func() { _ = closer.Close() }() 147 148 // If we were passed a HTTPS URL, make sure that we also check whether there 149 // is a custom CA bundle configured for connecting to the server. 150 if k.repo != "" && git.IsHTTPSURL(k.repo) { 151 parsedURL, err := url.Parse(k.repo) 152 if err != nil { 153 log.Warnf("Could not parse URL %s: %v", k.repo, err) 154 } else { 155 caPath, err := certutil.GetCertBundlePathForRepository(parsedURL.Host) 156 if err != nil { 157 // Some error while getting CA bundle 158 log.Warnf("Could not get CA bundle path for %s: %v", parsedURL.Host, err) 159 } else if caPath == "" { 160 // No cert configured 161 log.Debugf("No caCert found for repo %s", parsedURL.Host) 162 } else { 163 // Make Git use CA bundle 164 environ = append(environ, fmt.Sprintf("GIT_SSL_CAINFO=%s", caPath)) 165 } 166 } 167 } 168 169 cmd.Env = append(cmd.Env, environ...) 170 out, err := executil.Run(cmd) 171 if err != nil { 172 return nil, nil, err 173 } 174 175 objs, err := kube.SplitYAML([]byte(out)) 176 if err != nil { 177 return nil, nil, err 178 } 179 180 return objs, getImageParameters(objs), nil 181 } 182 183 func parseKustomizeBuildOptions(path, buildOptions string) []string { 184 return append([]string{"build", path}, strings.Split(buildOptions, " ")...) 185 } 186 187 var KustomizationNames = []string{"kustomization.yaml", "kustomization.yml", "Kustomization"} 188 189 // kustomization is a file that describes a configuration consumable by kustomize. 190 func (k *kustomize) findKustomization() (string, error) { 191 for _, file := range KustomizationNames { 192 kustomization := filepath.Join(k.path, file) 193 if _, err := os.Stat(kustomization); err == nil { 194 return kustomization, nil 195 } 196 } 197 return "", errors.New("did not find kustomization in " + k.path) 198 } 199 200 func IsKustomization(path string) bool { 201 for _, kustomization := range KustomizationNames { 202 if path == kustomization { 203 return true 204 } 205 } 206 return false 207 } 208 209 func Version(shortForm bool) (string, error) { 210 executable := "kustomize" 211 cmdArgs := []string{"version"} 212 if shortForm { 213 cmdArgs = append(cmdArgs, "--short") 214 } 215 cmd := exec.Command(executable, cmdArgs...) 216 // example version output: 217 // long: "{Version:kustomize/v3.8.1 GitCommit:0b359d0ef0272e6545eda0e99aacd63aef99c4d0 BuildDate:2020-07-16T00:58:46Z GoOs:linux GoArch:amd64}" 218 // short: "{kustomize/v3.8.1 2020-07-16T00:58:46Z }" 219 version, err := executil.Run(cmd) 220 if err != nil { 221 return "", fmt.Errorf("could not get kustomize version: %s", err) 222 } 223 version = strings.TrimSpace(version) 224 if shortForm { 225 // trim the curly braces 226 version = strings.TrimPrefix(version, "{") 227 version = strings.TrimSuffix(version, "}") 228 version = strings.TrimSpace(version) 229 230 // remove double space in middle 231 version = strings.ReplaceAll(version, " ", " ") 232 233 // remove extra 'kustomize/' before version 234 version = strings.TrimPrefix(version, "kustomize/") 235 236 } 237 return version, nil 238 } 239 240 func getImageParameters(objs []*unstructured.Unstructured) []Image { 241 var images []Image 242 for _, obj := range objs { 243 images = append(images, getImages(obj.Object)...) 244 } 245 sort.Slice(images, func(i, j int) bool { 246 return i < j 247 }) 248 return images 249 } 250 251 func getImages(object map[string]interface{}) []Image { 252 var images []Image 253 for k, v := range object { 254 if array, ok := v.([]interface{}); ok { 255 if k == "containers" || k == "initContainers" { 256 for _, obj := range array { 257 if mapObj, isMapObj := obj.(map[string]interface{}); isMapObj { 258 if image, hasImage := mapObj["image"]; hasImage { 259 images = append(images, fmt.Sprintf("%s", image)) 260 } 261 } 262 } 263 } else { 264 for i := range array { 265 if mapObj, isMapObj := array[i].(map[string]interface{}); isMapObj { 266 images = append(images, getImages(mapObj)...) 267 } 268 } 269 } 270 } else if objMap, ok := v.(map[string]interface{}); ok { 271 images = append(images, getImages(objMap)...) 272 } 273 } 274 return images 275 }