From da37d72204c7d420a85739b5bb2c26a5fa02b9d3 Mon Sep 17 00:00:00 2001 From: Armando Miani Date: Mon, 18 Nov 2024 10:50:55 +0100 Subject: [PATCH] Add Labels and Annotations to cloudbuild and dockerfiles tools (#2783) * Add Labels to cloudbuild and dockerfiles tools * wip * feat: allow docker image parametrization * feat: add labels support * chore: add activemq example * feat: add buildx env setup and improve template dependencies logic * chore: revert activemq changes * fix: readme --- tools/dockertools.Dockerfile | 9 +- .../scripts/cloudbuild/main.go | 503 ++++++++++-------- tools/dockerversioning/versions/versions.go | 174 +++--- 3 files changed, 380 insertions(+), 306 deletions(-) diff --git a/tools/dockertools.Dockerfile b/tools/dockertools.Dockerfile index 2478e194cd..02321c03d3 100644 --- a/tools/dockertools.Dockerfile +++ b/tools/dockertools.Dockerfile @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,10 @@ FROM marketplace.gcr.io/google/debian11 -ENV BAZEL_VERSION 0.19.2 -ENV BAZEL_ARCH linux_amd64_stripped +ENV BAZEL_VERSION=0.19.2 +ENV BAZEL_ARCH=linux_amd64_stripped + +COPY ./ click-to-deploy/tools RUN set -eux \ && apt-get update \ @@ -35,7 +37,6 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | d && apt install gh RUN set -eux \ - && git clone https://github.com/GoogleCloudPlatform/click-to-deploy.git \ && cd click-to-deploy/tools \ && bazel build dockerversioning/scripts/dockerfiles:dockerfiles dockerversioning/scripts/cloudbuild:cloudbuild \ && cp bazel-bin/dockerversioning/scripts/dockerfiles/${BAZEL_ARCH}/dockerfiles /bin/dockerfiles \ diff --git a/tools/dockerversioning/scripts/cloudbuild/main.go b/tools/dockerversioning/scripts/cloudbuild/main.go index 14504afe71..b830388ae0 100644 --- a/tools/dockerversioning/scripts/cloudbuild/main.go +++ b/tools/dockerversioning/scripts/cloudbuild/main.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "log" + "math/rand" "os" "strings" "text/template" @@ -16,35 +17,39 @@ import ( ) type cloudBuildOptions struct { - // Whether to restrict to a particular set of Dockerfile directories. - // If empty, all directories are used. - Directories []string + // Whether to restrict to a particular set of Dockerfile directories. + // If empty, all directories are used. + Directories []string - // Whether to run tests as part of the build. - RunTests bool + // Whether to run tests as part of the build. + RunTests bool - // Whether to require that image tags do not already exist in the repo. - RequireNewTags bool + // Whether to require that image tags do not already exist in the repo. + RequireNewTags bool - // Whether to push to all declared tags - FirstTagOnly bool + // Whether to push to all declared tags + FirstTagOnly bool - // Optional timeout duration. If not specified, the Cloud Builder default timeout is used. - TimeoutSeconds int + // Optional timeout duration. If not specified, the Cloud Builder default timeout is used. + TimeoutSeconds int - // Optional machine type used to run the build, must be one of: N1_HIGHCPU_8, N1_HIGHCPU_32, E2_HIGHCPU_8, E2_HIGHCPU_32. If not specified, the default machine is used. - MachineType string + // Optional machine type used to run the build, must be one of: N1_HIGHCPU_8, N1_HIGHCPU_32, E2_HIGHCPU_8, E2_HIGHCPU_32. If not specified, the default machine is used. + MachineType string - // Optional parallel build. If specified, images can be build on bigger machines in parallel. - EnableParallel bool + // Optional parallel build. If specified, images can be build on bigger machines in parallel. + EnableParallel bool - // Forces parallel build. If specified, images are build on bigger machines in parallel. Overrides EnableParallel. - ForceParallel bool + // Forces parallel build. If specified, images are build on bigger machines in parallel. Overrides EnableParallel. + ForceParallel bool + + // Defines the reference for the docker Cloud Build builder (https://cloud.google.com/build/docs/cloud-builders#supported_builder_images_provided_by) + DockerImage string } // TODO(huyhg): Replace "gcr.io/$PROJECT_ID/functional_test" with gcp-runtimes one. const cloudBuildTemplateString = `steps: {{- $parallel := .Parallel }} +{{- $dockerImage := .DockerImage }} {{- if .RequireNewTags }} # Check if tags exist. {{- range .Images }} @@ -56,10 +61,29 @@ const cloudBuildTemplateString = `steps: {{- end }} {{- end }} -# Build images + # Build and push annotated image + - name: {{ $dockerImage }} + args: + - buildx + - create + - --name + - temp-builder + - --use + waitFor: ['-'] + id: docker-create-env + - name: {{ $dockerImage }} + args: + - buildx + - inspect + - temp-builder + - --bootstrap + waitFor: ['docker-create-env'] + id: docker-bootstrap-env + + # Build images {{- range .ImageBuilds }} {{- if .Builder }} - - name: gcr.io/cloud-builders/docker + - name: {{ $dockerImage }} args: - 'build' - '--tag={{ .Tag }}' @@ -77,81 +101,91 @@ const cloudBuildTemplateString = `steps: id: 'image-{{ .Tag }}' {{- end }} {{- else }} - - name: gcr.io/cloud-builders/docker + {{- $testCounter := 0 }} + {{- $primary := .Tag }} + # Build test target image: {{ $primary }} + - name: {{ $dockerImage }} args: - 'build' - - '--tag={{ .Tag }}' + - '-t' + - '{{ $primary }}' - '{{ .Directory }}' -{{- if $parallel }} - waitFor: ['-'] - id: 'image-{{ .Tag }}' -{{- end }} -{{- end }} -{{- end }} -{{- end }} + id: image-test-{{ $primary }} + {{- if $parallel }} + waitFor: ['docker-bootstrap-env'] + {{- end }} -{{- range $imageIndex, $image := .ImageBuilds }} -{{- $primary := $image.Tag }} -{{- range $testIndex, $test := $image.StructureTests }} -{{- if and (eq $imageIndex 0) (eq $testIndex 0) }} - -# Run structure tests -{{- end}} + {{- range $testIndex, $test := .StructureTests }} + # Run structure test: {{ $primary }} - name: gcr.io/gcp-runtimes/structure_test args: - '--image' - '{{ $primary }}' - '--config' - '{{ $test }}' -{{- end }} -{{- end }} + waitFor: ['image-test-{{ $primary }}'] + id: 'structure-test-{{ $primary }}-{{ $testIndex }}' -{{- range $imageIndex, $image := .ImageBuilds }} -{{- $primary := $image.Tag }} -{{- range $testIndex, $test := $image.FunctionalTests }} -{{- if and (eq $imageIndex 0) (eq $testIndex 0) }} + {{ end }} -# Run functional tests -{{- end }} + {{- range $testIndex, $test := .FunctionalTests }} + # Run functional test: {{ $primary }} - name: gcr.io/$PROJECT_ID/functional_test args: - '--verbose' - '--vars' - 'IMAGE={{ $primary }}' - '--vars' - - 'UNIQUE={{ $imageIndex }}-{{ $testIndex }}' + - 'UNIQUE={{ randomString 8 }}' - '--test_spec' - '{{ $test }}' -{{- if $parallel }} - waitFor: ['image-{{ $primary }}'] - id: 'test-{{ $primary }}-{{ $testIndex }}' + waitFor: ['image-test-{{ $primary }}'] + id: 'functional-test-{{ $primary }}-{{ $testIndex }}' + {{- end }} + + - name: {{ $dockerImage }} + args: + - 'buildx' + - 'build' + - '--push' + {{- range .Aliases }} + - '--tag' + - '{{ . }}' + {{- end }} + {{- range .Annotations }} + - '--annotation=index,manifest:{{ .Key }}={{ .Value }}' + {{- end }} + {{- range .Labels }} + - '--label={{ .Key }}={{ .Value }}' + {{- end }} + - '{{ .Directory }}' + id: build-and-push-image-{{ $primary }} + waitFor: + {{- range $testIndex, $test := .StructureTests }} + - 'structure-test-{{ $primary }}-{{ $testIndex }}' + {{- end}} + {{- range $testIndex, $test := .FunctionalTests }} + - 'functional-test-{{ $primary }}-{{ $testIndex }}' + {{- end}} + {{- end }} {{- end }} - {{- end }} -# Add alias tags {{- range $imageIndex, $image := .ImageBuilds }} {{- $primary := $image.Tag }} -{{- range .Aliases }} - - name: gcr.io/cloud-builders/docker +{{- range $testIndex, $test := $image.StructureTests }} +{{- if and (eq $imageIndex 0) (eq $testIndex 0) }} + +# Run structure tests +{{- end}} + - name: gcr.io/gcp-runtimes/structure_test args: - - 'tag' + - '--image' - '{{ $primary }}' - - '{{ . }}' -{{- if $parallel }} - waitFor: - - 'image-{{ $primary }}' -{{- range $testIndex, $test := $image.FunctionalTests }} - - 'test-{{ $primary }}-{{ $testIndex }}' -{{- end }} -{{- end }} -{{- end }} + - '--config' + - '{{ $test }}' {{- end }} - -images: -{{- range .AllImages }} - - '{{ . }}' {{- end }} {{- if not (eq .TimeoutSeconds 0) }} @@ -178,185 +212,208 @@ const testYamlSuffix = "_test.yaml" const workspacePrefix = "/workspace/" type imageBuildTemplateData struct { - Directory string - Tag string - Aliases []string - StructureTests []string - FunctionalTests []string - Builder bool - BuilderImage string - BuilderArgs []string - ImageNameFromBuilder string + Directory string + Tag string + Aliases []string + StructureTests []string + FunctionalTests []string + Builder bool + BuilderImage string + BuilderArgs []string + ImageNameFromBuilder string + Annotations []versions.Annotation + Labels []versions.Annotation } type cloudBuildTemplateData struct { - RequireNewTags bool - Parallel bool - ImageBuilds []imageBuildTemplateData - AllImages []string - TimeoutSeconds int - MachineType string + RequireNewTags bool + Parallel bool + DockerImage string + ImageBuilds []imageBuildTemplateData + AllImages []string + TimeoutSeconds int + MachineType string } func shouldParallelize(options cloudBuildOptions, numberOfVersions int, numberOfTests int) bool { - if options.ForceParallel { - return true - } - if !options.EnableParallel { - return false - } - return numberOfVersions > 1 || numberOfTests > 1 + if options.ForceParallel { + return true + } + if !options.EnableParallel { + return false + } + return numberOfVersions > 1 || numberOfTests > 1 } func newCloudBuildTemplateData( - registry string, spec versions.Spec, options cloudBuildOptions) cloudBuildTemplateData { - data := cloudBuildTemplateData{} - data.RequireNewTags = options.RequireNewTags - - // Determine the set of directories to operate on. - dirs := make(map[string]bool) - if len(options.Directories) > 0 { - for _, d := range options.Directories { - dirs[d] = true - } - } else { - for _, v := range spec.Versions { - dirs[v.Dir] = true - } - } - - // Extract tests to run. - var structureTests []string - var functionalTests []string - if options.RunTests { - // Legacy structure tests reside in the root tests/ directory. - structureTests = append(structureTests, readTests(testsDir)...) - structureTests = append(structureTests, readTests(structureTestsDir)...) - functionalTests = append(functionalTests, readTests(functionalTestsDir)...) - } - - // Extract a list of full image names to build. - for _, v := range spec.Versions { - if !dirs[v.Dir] { - continue - } - var images []string - for _, t := range v.Tags { - image := fmt.Sprintf("%v/%v:%v", registry, v.Repo, t) - images = append(images, image) - if options.FirstTagOnly { - break - } - } - // Ignore builder images from images list - if !v.Builder { - data.AllImages = append(data.AllImages, images...) - } - versionSTests, versionFTests := filterTests(structureTests, functionalTests, v) - // Enforce to use ImageNameFromBuilder as reference to create tags - if v.BuilderImage != "" { - BuilderImageFull := fmt.Sprintf("%v/%v", registry, v.BuilderImage) - data.ImageBuilds = append( - data.ImageBuilds, imageBuildTemplateData{v.Dir, v.ImageNameFromBuilder, images, versionSTests, versionFTests, v.Builder, BuilderImageFull, v.BuilderArgs, v.ImageNameFromBuilder}) - } else { - data.ImageBuilds = append( - data.ImageBuilds, imageBuildTemplateData{v.Dir, images[0], images[1:], versionSTests, versionFTests, v.Builder, v.BuilderImage, v.BuilderArgs, v.ImageNameFromBuilder}) - } - } - - data.TimeoutSeconds = options.TimeoutSeconds - data.MachineType = options.MachineType - data.Parallel = shouldParallelize(options, len(spec.Versions), len(functionalTests)) - return data + registry string, spec versions.Spec, options cloudBuildOptions) cloudBuildTemplateData { + data := cloudBuildTemplateData{} + data.RequireNewTags = options.RequireNewTags + + // Defines the default docker image, if its not set + if (options.DockerImage == "") { + data.DockerImage = "gcr.io/cloud-builders/docker" + } else { + data.DockerImage = options.DockerImage + } + + // Determine the set of directories to operate on. + dirs := make(map[string]bool) + if len(options.Directories) > 0 { + for _, d := range options.Directories { + dirs[d] = true + } + } else { + for _, v := range spec.Versions { + dirs[v.Dir] = true + } + } + + // Extract tests to run. + var structureTests []string + var functionalTests []string + if options.RunTests { + // Legacy structure tests reside in the root tests/ directory. + structureTests = append(structureTests, readTests(testsDir)...) + structureTests = append(structureTests, readTests(structureTestsDir)...) + functionalTests = append(functionalTests, readTests(functionalTestsDir)...) + } + + // Extract a list of full image names to build. + for _, v := range spec.Versions { + if !dirs[v.Dir] { + continue + } + var images []string + for _, t := range v.Tags { + image := fmt.Sprintf("%v/%v:%v", registry, v.Repo, t) + images = append(images, image) + if options.FirstTagOnly { + break + } + } + // Ignore builder images from images list + if !v.Builder { + data.AllImages = append(data.AllImages, images...) + } + versionSTests, versionFTests := filterTests(structureTests, functionalTests, v) + // Enforce to use ImageNameFromBuilder as reference to create tags + if v.BuilderImage != "" { + BuilderImageFull := fmt.Sprintf("%v/%v", registry, v.BuilderImage) + data.ImageBuilds = append( + data.ImageBuilds, imageBuildTemplateData{v.Dir, v.ImageNameFromBuilder, images, versionSTests, versionFTests, v.Builder, BuilderImageFull, v.BuilderArgs, v.ImageNameFromBuilder, v.Annotations, v.Labels}) + } else { + data.ImageBuilds = append( + data.ImageBuilds, imageBuildTemplateData{v.Dir, images[0], images[1:], versionSTests, versionFTests, v.Builder, v.BuilderImage, v.BuilderArgs, v.ImageNameFromBuilder, v.Annotations, v.Labels}) + } + } + + data.TimeoutSeconds = options.TimeoutSeconds + data.MachineType = options.MachineType + data.Parallel = shouldParallelize(options, len(spec.Versions), len(functionalTests)) + return data } func readTests(testsDir string) (tests []string) { - if info, err := os.Stat(testsDir); err == nil && info.IsDir() { - files, err := ioutil.ReadDir(testsDir) - check(err) - for _, f := range files { - if f.IsDir() { - continue - } - if strings.HasSuffix(f.Name(), testJsonSuffix) || strings.HasSuffix(f.Name(), testYamlSuffix) { - tests = append(tests, workspacePrefix+fmt.Sprintf("%s/%s", testsDir, f.Name())) - } - } - } - return + if info, err := os.Stat(testsDir); err == nil && info.IsDir() { + files, err := ioutil.ReadDir(testsDir) + check(err) + for _, f := range files { + if f.IsDir() { + continue + } + if strings.HasSuffix(f.Name(), testJsonSuffix) || strings.HasSuffix(f.Name(), testYamlSuffix) { + tests = append(tests, workspacePrefix+fmt.Sprintf("%s/%s", testsDir, f.Name())) + } + } + } + return } func filterTests(structureTests []string, functionalTests []string, version versions.Version) (outStructureTests []string, outFunctionalTests []string) { - included := make(map[string]bool, len(structureTests)+len(functionalTests)) - for _, test := range append(structureTests, functionalTests...) { - included[test] = true - } - for _, excluded := range version.ExcludeTests { - if !included[workspacePrefix+excluded] { - log.Fatalf("No such test to exclude: %s", excluded) - } - included[workspacePrefix+excluded] = false - } - - outStructureTests = make([]string, 0, len(structureTests)) - for _, test := range structureTests { - if included[test] { - outStructureTests = append(outStructureTests, test) - } - } - outFunctionalTests = make([]string, 0, len(functionalTests)) - for _, test := range functionalTests { - if included[test] { - outFunctionalTests = append(outFunctionalTests, test) - } - } - return + included := make(map[string]bool, len(structureTests)+len(functionalTests)) + for _, test := range append(structureTests, functionalTests...) { + included[test] = true + } + for _, excluded := range version.ExcludeTests { + if !included[workspacePrefix+excluded] { + log.Fatalf("No such test to exclude: %s", excluded) + } + included[workspacePrefix+excluded] = false + } + + outStructureTests = make([]string, 0, len(structureTests)) + for _, test := range structureTests { + if included[test] { + outStructureTests = append(outStructureTests, test) + } + } + outFunctionalTests = make([]string, 0, len(functionalTests)) + for _, test := range functionalTests { + if included[test] { + outFunctionalTests = append(outFunctionalTests, test) + } + } + return } func renderCloudBuildConfig( - registry string, spec versions.Spec, options cloudBuildOptions) string { - data := newCloudBuildTemplateData(registry, spec, options) - tmpl, _ := template. - New("cloudBuildTemplate"). - Parse(cloudBuildTemplateString) - var result bytes.Buffer - tmpl.Execute(&result, data) - return result.String() + registry string, spec versions.Spec, options cloudBuildOptions) string { + data := newCloudBuildTemplateData(registry, spec, options) + + funcMap := template.FuncMap{ + "randomString": func(length int) string { + bytes := make([]byte, length) + for i := 0; i < length; i++ { + bytes[i] = byte(rand.Intn(26) + 'a') + } + return string(bytes) + }, + } + + tmpl, _ := template. + New("cloudBuildTemplate"). + Funcs(funcMap). + Parse(cloudBuildTemplateString) + var result bytes.Buffer + tmpl.Execute(&result, data) + return result.String() } func check(e error) { - if e != nil { - panic(e) - } + if e != nil { + panic(e) + } } func main() { - config := versions.LoadConfig("versions.yaml", "cloudbuild") - registryPtr := config.StringOption("registry", "gcr.io/$PROJECT_ID", "Registry, e.g: 'gcr.io/my-project'") - dirsPtr := config.StringOption("dirs", "", "Comma separated list of Dockerfile dirs to use.") - testsPtr := config.BoolOption("tests", true, "Run tests.") - newTagsPtr := config.BoolOption("new_tags", false, "Require that image tags do not already exist.") - firstTagOnly := config.BoolOption("first_tag", false, "Build only the first per version.") - timeoutPtr := config.IntOption("timeout", 0, "Timeout in seconds. If not set, the default Cloud Build timeout is used.") - machineTypePtr := config.StringOption("machineType","", "Optional machine type used to run the build, , must be one of: N1_HIGHCPU_8, N1_HIGHCPU_32, E2_HIGHCPU_8, E2_HIGHCPU_32. If not specified, the default machine is used.") - enableParallel := config.BoolOption("enable_parallel", false, "Enable parallel build and bigger VM") - forceParallel := config.BoolOption("force_parallel", false, "Force parallel build and bigger VM") - config.Parse() - - if *registryPtr == "" { - log.Fatalf("--registry flag is required") - } - - if strings.Contains(*registryPtr, ":") { - *registryPtr = strings.Replace(*registryPtr, ":", "/", 1) - } - - var dirs []string - if *dirsPtr != "" { - dirs = strings.Split(*dirsPtr, ",") - } - spec := versions.LoadVersions("versions.yaml") - options := cloudBuildOptions{dirs, *testsPtr, *newTagsPtr, *firstTagOnly, *timeoutPtr, *machineTypePtr, *enableParallel, *forceParallel} - result := renderCloudBuildConfig(*registryPtr, spec, options) - fmt.Println(result) -} \ No newline at end of file + config := versions.LoadConfig("versions.yaml", "cloudbuild") + registryPtr := config.StringOption("registry", "gcr.io/$PROJECT_ID", "Registry, e.g: 'gcr.io/my-project'") + dirsPtr := config.StringOption("dirs", "", "Comma separated list of Dockerfile dirs to use.") + testsPtr := config.BoolOption("tests", true, "Run tests.") + newTagsPtr := config.BoolOption("new_tags", false, "Require that image tags do not already exist.") + firstTagOnly := config.BoolOption("first_tag", false, "Build only the first per version.") + timeoutPtr := config.IntOption("timeout", 0, "Timeout in seconds. If not set, the default Cloud Build timeout is used.") + machineTypePtr := config.StringOption("machineType","", "Optional machine type used to run the build, , must be one of: N1_HIGHCPU_8, N1_HIGHCPU_32, E2_HIGHCPU_8, E2_HIGHCPU_32. If not specified, the default machine is used.") + enableParallel := config.BoolOption("enable_parallel", false, "Enable parallel build and bigger VM") + forceParallel := config.BoolOption("force_parallel", false, "Force parallel build and bigger VM") + dockerImage := config.StringOption("docker_image", "gcr.io/cloud-builders/docker", "Optional docker builder reference") + config.Parse() + + if *registryPtr == "" { + log.Fatalf("--registry flag is required") + } + + if strings.Contains(*registryPtr, ":") { + *registryPtr = strings.Replace(*registryPtr, ":", "/", 1) + } + + var dirs []string + if *dirsPtr != "" { + dirs = strings.Split(*dirsPtr, ",") + } + spec := versions.LoadVersions("versions.yaml") + options := cloudBuildOptions{dirs, *testsPtr, *newTagsPtr, *firstTagOnly, *timeoutPtr, *machineTypePtr, *enableParallel, *forceParallel, *dockerImage} + result := renderCloudBuildConfig(*registryPtr, spec, options) + fmt.Println(result) +} diff --git a/tools/dockerversioning/versions/versions.go b/tools/dockerversioning/versions/versions.go index 64a4ba575c..47104bb2d2 100644 --- a/tools/dockerversioning/versions/versions.go +++ b/tools/dockerversioning/versions/versions.go @@ -9,58 +9,74 @@ import ( "io/ioutil" "log" "strconv" + "time" yaml "gopkg.in/yaml.v2" ) type Package struct { - Version string - Minor string - Major string - Gpg string - Sha1 string - Sha256 string - Sha512 string - Md5 string + Version string + Minor string + Major string + Gpg string + Sha1 string + Sha256 string + Sha512 string + Md5 string +} + +type Annotation struct { + Key string `yaml:"key"` + AnnotationValue string `yaml:"value"` + IsTimestamp bool `yaml:"timestamp"` +} + +func (l Annotation) Value() string { + if (l.IsTimestamp) { + return time.Now().Format(time.RFC3339) + } + return l.AnnotationValue } type Version struct { - Dir string - TemplateSubDir string `yaml:"templateSubDir"` - Repo string - Tags []string - From string - TemplateArgs map[string]string `yaml:"templateArgs"` - Packages map[string]Package - ExcludeTests []string `yaml:"excludeTests"` - Builder bool - BuilderImage string `yaml:"builderImage"` - BuilderArgs []string `yaml:"builderArgs"` - ImageNameFromBuilder string `yaml:"imageNameFromBuilder"` + Dir string + TemplateSubDir string `yaml:"templateSubDir"` + Repo string + Tags []string + From string + TemplateArgs map[string]string `yaml:"templateArgs"` + Packages map[string]Package + ExcludeTests []string `yaml:"excludeTests"` + Builder bool + BuilderImage string `yaml:"builderImage"` + BuilderArgs []string `yaml:"builderArgs"` + ImageNameFromBuilder string `yaml:"imageNameFromBuilder"` + Annotations []Annotation `yaml:"annotations"` + Labels []Annotation `yaml:"labels"` } type Spec struct { - Versions []Version + Versions []Version } func ReadFile(path string) []byte { - data, err := ioutil.ReadFile(path) - if err != nil { - log.Fatalf("error: %v", err) - } - return []byte(data) + data, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("error: %v", err) + } + return []byte(data) } func LoadVersions(path string) Spec { - spec := Spec{} - err := yaml.Unmarshal(ReadFile(path), &spec) - if err != nil { - log.Fatalf("error: %v", err) - } + spec := Spec{} + err := yaml.Unmarshal(ReadFile(path), &spec) + if err != nil { + log.Fatalf("error: %v", err) + } - validateUniqueTags(spec) + validateUniqueTags(spec) - return spec + return spec } // Config represents setting for a program call. Arguments can be provided in file, as a key-value @@ -68,65 +84,65 @@ func LoadVersions(path string) Spec { type Config map[string]string func LoadConfig(path, config string) Config { - var whole map[string]interface{} - err := yaml.Unmarshal(ReadFile(path), &whole) - if err != nil { - log.Fatalf("error: %v", err) - } - - if c, ok := whole[config]; ok { - configMap := map[string]string{} - mapInterface := c.(map[interface{}]interface{}) - for key, value := range mapInterface { - configMap[key.(string)] = fmt.Sprintf("%v", value) - } - return configMap - } - return map[string]string{} + var whole map[string]interface{} + err := yaml.Unmarshal(ReadFile(path), &whole) + if err != nil { + log.Fatalf("error: %v", err) + } + + if c, ok := whole[config]; ok { + configMap := map[string]string{} + mapInterface := c.(map[interface{}]interface{}) + for key, value := range mapInterface { + configMap[key.(string)] = fmt.Sprintf("%v", value) + } + return configMap + } + return map[string]string{} } func (c Config) StringOption(name, defaultVal, helper string) *string { - if configVal, ok := c[name]; ok { - defaultVal = configVal - } - return flag.String(name, defaultVal, helper) + if configVal, ok := c[name]; ok { + defaultVal = configVal + } + return flag.String(name, defaultVal, helper) } func (c Config) BoolOption(name string, defaultVal bool, helper string) *bool { - if configVal, ok := c[name]; ok { - b, err := strconv.ParseBool(configVal) - if err != nil { - log.Fatalf("error: %v", err) - } - defaultVal = b - } - return flag.Bool(name, defaultVal, helper) + if configVal, ok := c[name]; ok { + b, err := strconv.ParseBool(configVal) + if err != nil { + log.Fatalf("error: %v", err) + } + defaultVal = b + } + return flag.Bool(name, defaultVal, helper) } func (c Config) IntOption(name string, defaultVal int, helper string) *int { - if configVal, ok := c[name]; ok { - i, err := strconv.Atoi(configVal) - if err != nil { - log.Fatalf("error: %v", err) - } - defaultVal = i - } - return flag.Int(name, defaultVal, helper) + if configVal, ok := c[name]; ok { + i, err := strconv.Atoi(configVal) + if err != nil { + log.Fatalf("error: %v", err) + } + defaultVal = i + } + return flag.Int(name, defaultVal, helper) } func (c Config) Parse() { - flag.Parse() + flag.Parse() } func validateUniqueTags(spec Spec) { - repoTags := make(map[string]bool) - for _, version := range spec.Versions { - for _, tag := range version.Tags { - repoTag := fmt.Sprintf("%s:%s", version.Repo, tag) - if repoTags[repoTag] { - log.Fatalf("error: duplicate repo tag %v", repoTag) - } - repoTags[repoTag] = true - } - } + repoTags := make(map[string]bool) + for _, version := range spec.Versions { + for _, tag := range version.Tags { + repoTag := fmt.Sprintf("%s:%s", version.Repo, tag) + if repoTags[repoTag] { + log.Fatalf("error: duplicate repo tag %v", repoTag) + } + repoTags[repoTag] = true + } + } }