diff --git a/baker/bake.go b/baker/bake.go new file mode 100644 index 0000000..53287ac --- /dev/null +++ b/baker/bake.go @@ -0,0 +1,211 @@ +/* +Copyright 2025 The cert-manager Authors. + +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. +*/ + +package baker + +import ( + "context" + "fmt" + "maps" + "slices" + "strings" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +type BakeReference struct { + Repository string + Tag string + Digest string +} + +func ParseBakeReference(value string) (bakeInput BakeReference) { + // extract digest from value + if digestRef, err := name.NewDigest(value); err == nil { + bakeInput.Repository = digestRef.Context().String() + bakeInput.Digest = digestRef.DigestStr() + } + + // extract tag from value + if tagRef, err := name.NewTag(value); err == nil { + bakeInput.Repository = tagRef.Context().String() + bakeInput.Tag = tagRef.TagStr() + } + + return bakeInput +} + +func (br BakeReference) Reference() name.Reference { + repo, _ := name.NewRepository(br.Repository) + if br.Digest != "" { + return repo.Digest(br.Digest) + } + return repo.Tag(br.Tag) +} + +func (br BakeReference) String() string { + var builder strings.Builder + _, _ = builder.WriteString(br.Repository) + if br.Tag != "" { + _, _ = builder.WriteString(":") + _, _ = builder.WriteString(br.Tag) + } + if br.Digest != "" { + _, _ = builder.WriteString("@") + _, _ = builder.WriteString(br.Digest) + } + return builder.String() +} + +type BakeInput = BakeReference + +func (bi BakeInput) Find(ctx context.Context) (BakeOutput, error) { + desc, err := remote.Head(bi.Reference(), remote.WithContext(ctx)) + if err != nil { + return BakeReference{}, fmt.Errorf("failed to pull %s", bi) + } + + return BakeReference{ + Repository: bi.Repository, + Digest: desc.Digest.String(), + Tag: bi.Tag, + }, nil +} + +type BakeOutput = BakeReference + +func Extract(ctx context.Context, inputPath string) (map[string]BakeInput, error) { + results := map[string]BakeInput{} + + values, err := readValuesYAML(inputPath) + if err != nil { + return nil, err + } + + if _, err := allNestedStringValues(values, nil, func(path []string, value string) (string, error) { + if path[len(path)-1] != "_defaultReference" { + return value, nil + } + + bakeInput := ParseBakeReference(value) + if bakeInput == (BakeInput{}) { + return "", fmt.Errorf("invalid _defaultReference value: %q", value) + } + + results[strings.Join(path, ".")] = bakeInput + + return value, nil + }); err != nil { + return nil, err + } + + return results, nil +} + +type BakeAction struct { + In BakeInput `json:"in"` + Out BakeOutput `json:"out"` +} + +func Bake(ctx context.Context, inputPath string, outputPath string, valuesPaths []string) (map[string]BakeAction, error) { + results := map[string]BakeAction{} + return results, modifyValuesYAML(inputPath, outputPath, func(values map[string]any) (map[string]any, error) { + replacedValuePaths := map[string]struct{}{} + newValues, err := allNestedStringValues(values, nil, func(path []string, value string) (string, error) { + if path[len(path)-1] != "_defaultReference" { + return value, nil + } + + bakeInput := ParseBakeReference(value) + if bakeInput == (BakeInput{}) { + return "", fmt.Errorf("invalid _defaultReference value: %q", value) + } + + bakeOutput, err := bakeInput.Find(ctx) + if err != nil { + return "", err + } + + pathString := strings.Join(path, ".") + replacedValuePaths[pathString] = struct{}{} + results[pathString] = BakeAction{ + In: bakeInput, + Out: bakeOutput, + } + + return bakeOutput.String(), nil + }) + if err != nil { + return nil, err + } + + if len(replacedValuePaths) > len(valuesPaths) { + return nil, fmt.Errorf("too many value paths were replaced: %v", slices.Collect(maps.Keys(replacedValuePaths))) + } + for _, valuesPath := range valuesPaths { + if _, ok := replacedValuePaths[valuesPath]; !ok { + return nil, fmt.Errorf("path was not replaced: %s", valuesPath) + } + } + + return newValues.(map[string]any), nil + }) +} + +func allNestedStringValues(object any, path []string, fn func(path []string, value string) (string, error)) (any, error) { + switch t := object.(type) { + case map[string]any: + for key, value := range t { + keyPath := append(path, key) + if stringValue, ok := value.(string); ok { + newValue, err := fn(slices.Clone(keyPath), stringValue) + if err != nil { + return nil, err + } + t[key] = newValue + } else { + newValue, err := allNestedStringValues(value, keyPath, fn) + if err != nil { + return nil, err + } + t[key] = newValue + } + } + case map[string]string: + for key, stringValue := range t { + keyPath := append(path, key) + newValue, err := fn(slices.Clone(keyPath), stringValue) + if err != nil { + return nil, err + } + t[key] = newValue + } + case []any: + for i, value := range t { + path = append(path, fmt.Sprintf("%d", i)) + newValue, err := allNestedStringValues(value, path, fn) + if err != nil { + return nil, err + } + t[i] = newValue + } + default: + // ignore object + } + + return object, nil +} diff --git a/baker/modify_values.go b/baker/modify_values.go new file mode 100644 index 0000000..d7d9b35 --- /dev/null +++ b/baker/modify_values.go @@ -0,0 +1,157 @@ +/* +Copyright 2025 The cert-manager Authors. + +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. +*/ + +package baker + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "os" + "strings" + + "gopkg.in/yaml.v3" +) + +// inplaceReadValuesYAML reads the provided chart tar file and returns the values +func readValuesYAML(inputPath string) (map[string]any, error) { + var result map[string]any + return result, modifyValuesYAML(inputPath, "", func(m map[string]any) (map[string]any, error) { + result = m + return m, nil + }) +} + +type modFunction func(map[string]any) (map[string]any, error) + +func modifyValuesYAML(inFilePath string, outFilePath string, modFn modFunction) error { + inReader, err := os.Open(inFilePath) + if err != nil { + return err + } + defer inReader.Close() + + outWriter := io.Discard + if outFilePath != "" { + outFile, err := os.Create(outFilePath) + if err != nil { + return err + } + defer outFile.Close() + + outWriter = outFile + } + + if strings.HasSuffix(inFilePath, ".tgz") { + if err := modifyTarStreamValuesYAML(inReader, outWriter, modFn); err != nil { + return err + } + } else { + if err := modifyStreamValuesYAML(inReader, outWriter, modFn); err != nil { + return err + } + } + + return nil +} + +func modifyTarStreamValuesYAML(in io.Reader, out io.Writer, modFn modFunction) error { + inFileDecompressed, err := gzip.NewReader(in) + if err != nil { + return err + } + defer inFileDecompressed.Close() + tr := tar.NewReader(inFileDecompressed) + + outFileCompressed, err := gzip.NewWriterLevel(out, gzip.BestCompression) + if err != nil { + return err + } + // see https://github.com/helm/helm/blob/b25a51b291b930ef05ed0f4a9ea7cce073c10f63/pkg/chart/v2/util/save.go#L35C28-L35C67 + // seems like an easter egg, but keep it for "backwards compatibility" :) + outFileCompressed.Extra = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") + outFileCompressed.Comment = "Helm" + defer outFileCompressed.Close() + tw := tar.NewWriter(outFileCompressed) + defer tw.Close() + + for { + hdr, err := tr.Next() + if err == io.EOF { + break // End of archive + } + if err != nil { + return err + } + + const maxValuesYAMLSize = 2 * 1024 * 1024 // 2MB + limitedReader := &io.LimitedReader{ + R: tr, + N: maxValuesYAMLSize, + } + + if strings.HasSuffix(hdr.Name, "/values.yaml") { + var modifiedContent bytes.Buffer + if err := modifyStreamValuesYAML(limitedReader, &modifiedContent, modFn); err != nil { + return err + } + + // Update header size + hdr.Size = int64(modifiedContent.Len()) + + // Write updated header and content + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if _, err := tw.Write(modifiedContent.Bytes()); err != nil { + return err + } + } else { + // Stream other files unchanged + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if _, err := io.Copy(tw, limitedReader); err != nil { + return err + } + } + + if limitedReader.N <= 0 { + return fmt.Errorf("values.yaml is larger than %v bytes", maxValuesYAMLSize) + } + } + + return nil +} + +func modifyStreamValuesYAML(in io.Reader, out io.Writer, modFn modFunction) error { + // Parse YAML + var data map[string]any + if err := yaml.NewDecoder(in).Decode(&data); err != nil { + return err + } + + // Modify YAML + data, err := modFn(data) + if err != nil { + return err + } + + // Marshal back to YAML + return yaml.NewEncoder(out).Encode(data) +} diff --git a/go.mod b/go.mod index ec86f58..cf603fa 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/cert-manager/helm-tool -go 1.23 +go 1.23.0 require ( github.com/Masterminds/sprig/v3 v3.3.0 + github.com/google/go-containerregistry v0.20.3 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 @@ -14,7 +15,11 @@ require ( dario.cat/mergo v1.0.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v27.5.0+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -24,13 +29,22 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.6 // indirect + github.com/vbatts/tar-split v0.11.6 // indirect golang.org/x/crypto v0.26.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect ) diff --git a/go.sum b/go.sum index 796a90a..a5c7d4f 100644 --- a/go.sum +++ b/go.sum @@ -6,11 +6,19 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= +github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -27,6 +35,8 @@ github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= +github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -37,6 +47,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -48,8 +60,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -57,6 +77,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -66,24 +88,34 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= +github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910 h1:1Rp/XEKP5uxPs6QrsngEHAxBjaAR78iJRiJq5Fi7LSU= k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/main.go b/main.go index 1a7206d..3caeeab 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,16 @@ limitations under the License. package main import ( + "context" + "encoding/json" "fmt" "os" "regexp" + "slices" "github.com/spf13/cobra" + "github.com/cert-manager/helm-tool/baker" "github.com/cert-manager/helm-tool/linter" "github.com/cert-manager/helm-tool/parser" "github.com/cert-manager/helm-tool/render" @@ -35,6 +39,7 @@ var ( exceptionsFile string targetFile string templateName string + imagePathsFile string headerSearch = regexValue{regexp.MustCompile(`(?m)^##\s+Parameters *$`)} footerSearch = regexValue{regexp.MustCompile(`(?m)^##?\s+.*$`)} ) @@ -118,6 +123,67 @@ var Lint = cobra.Command{ }, } +var ImagesExtract = cobra.Command{ + Use: "extract", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + inputPath := args[0] + + images, err := baker.Extract(context.TODO(), inputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not extract: %s\n", err) + os.Exit(1) + } + + imagePaths := []string{} + for imagePath := range images { + imagePaths = append(imagePaths, imagePath) + } + slices.Sort(imagePaths) + + if err := json.NewEncoder(os.Stdout).Encode(imagePaths); err != nil { + fmt.Fprintf(os.Stderr, "Could print found images: %s\n", err) + os.Exit(1) + } + }, +} + +var ImagesBake = cobra.Command{ + Use: "bake", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + inputPath := args[0] + outputPath := args[1] + + if len(imagePathsFile) == 0 { + fmt.Fprintf(os.Stderr, "--paths flag not provided.\n") + os.Exit(1) + } + + jsonBlob, err := os.ReadFile(imagePathsFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not read --paths file: %s\n", err) + os.Exit(1) + } + + imagePaths := []string{} + if err := json.Unmarshal(jsonBlob, &imagePaths); err != nil { + fmt.Fprintf(os.Stderr, "Could not parse --paths file: %s\n", err) + os.Exit(1) + } + + images, err := baker.Bake(context.TODO(), inputPath, outputPath, imagePaths) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not bake: %s\n", err) + os.Exit(1) + } + + for path, action := range images { + fmt.Fprintf(os.Stderr, "%s: %s -> %s\n", path, action.In, action.Out) + } + }, +} + func init() { Cmd.PersistentFlags().StringVarP(&valuesFile, "values", "i", "values.yaml", "values file used to generate the documentation") @@ -135,6 +201,15 @@ func init() { Cmd.AddCommand(&Lint) Lint.PersistentFlags().StringVarP(&templatesFolder, "templates", "d", "templates", "templates folder used to lint the values file") Lint.PersistentFlags().StringVarP(&exceptionsFile, "exceptions", "e", "", "file containing exceptions to the linting rules") + + images := cobra.Command{ + Use: "images", + } + Cmd.AddCommand(&images) + + images.AddCommand(&ImagesExtract) + images.AddCommand(&ImagesBake) + ImagesBake.PersistentFlags().StringVarP(&imagePathsFile, "paths", "p", "", "file containing paths of image._defaultReference in values.yaml (used as check)") } func main() {