From bcac8f1853656f222611edd4415bc05aad2cf346 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Tue, 3 Dec 2024 18:49:47 +0000 Subject: [PATCH 1/2] Add --digest flag to timoni apply Signed-off-by: Matheus Pimenta --- cmd/timoni/apply.go | 16 ++++++++- cmd/timoni/apply_test.go | 71 ++++++++++++++++++++++++++++++++++++++++ internal/flags/digest.go | 35 ++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 internal/flags/digest.go diff --git a/cmd/timoni/apply.go b/cmd/timoni/apply.go index 28a0274d..97a34042 100644 --- a/cmd/timoni/apply.go +++ b/cmd/timoni/apply.go @@ -102,6 +102,7 @@ type applyFlags struct { module string version flags.Version pkg flags.Package + digest flags.Digest valuesFiles []string dryrun bool diff bool @@ -116,6 +117,7 @@ var applyArgs applyFlags func init() { applyCmd.Flags().VarP(&applyArgs.version, applyArgs.version.Type(), applyArgs.version.Shorthand(), applyArgs.version.Description()) applyCmd.Flags().VarP(&applyArgs.pkg, applyArgs.pkg.Type(), applyArgs.pkg.Shorthand(), applyArgs.pkg.Description()) + applyCmd.Flags().VarP(&applyArgs.digest, applyArgs.digest.Type(), applyArgs.digest.Shorthand(), applyArgs.digest.Description()) applyCmd.Flags().StringSliceVarP(&applyArgs.valuesFiles, "values", "f", nil, "The local path to values files (cue, yaml or json format).") applyCmd.Flags().BoolVar(&applyArgs.force, "force", false, @@ -143,12 +145,20 @@ func runApplyCmd(cmd *cobra.Command, args []string) error { log := loggerInstance(cmd.Context(), applyArgs.name, true) version := applyArgs.version.String() + digest := applyArgs.digest.String() if version == "" { version = apiv1.LatestVersion + if digest != "" { + version = fmt.Sprintf("@%s", digest) + } } if strings.HasPrefix(applyArgs.module, apiv1.ArtifactPrefix) { - log.Info(fmt.Sprintf("pulling %s:%s", applyArgs.module, version)) + img := fmt.Sprintf("%s:%s", applyArgs.module, version) + if strings.HasPrefix(version, "@") { + img = fmt.Sprintf("%s%s", applyArgs.module, version) + } + log.Info(fmt.Sprintf("pulling %s", img)) } else { log.Info(fmt.Sprintf("building %s", applyArgs.module)) } @@ -179,6 +189,10 @@ func runApplyCmd(cmd *cobra.Command, args []string) error { return err } + if digest != "" && mod.Digest != digest { + return fmt.Errorf("digest mismatch, expected %s got %s", digest, mod.Digest) + } + cuectx := cuecontext.New() builder := engine.NewModuleBuilder( cuectx, diff --git a/cmd/timoni/apply_test.go b/cmd/timoni/apply_test.go index f4ffc104..259d28fa 100644 --- a/cmd/timoni/apply_test.go +++ b/cmd/timoni/apply_test.go @@ -18,6 +18,7 @@ package main import ( "context" + "encoding/json" "fmt" "os" "path/filepath" @@ -181,6 +182,76 @@ func TestApply(t *testing.T) { }) } +func TestApply_WithDigest(t *testing.T) { + g := NewWithT(t) + + instanceName := "frontend" + modPath := "testdata/module" + namespace := rnd("my-namespace", 5) + modName := rnd("my-mod", 5) + modURL := fmt.Sprintf("%s/%s", dockerRegistry, modName) + modVer := "1.0.0" + + // Push the module to registry + pushOut, err := executeCommand(fmt.Sprintf( + "mod push %s oci://%s -v %s -o json", + modPath, + modURL, + modVer, + )) + g.Expect(err).ToNot(HaveOccurred()) + + // Parse digest + var mod struct { + Digest string `json:"digest"` + } + err = json.Unmarshal([]byte(pushOut), &mod) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(mod.Digest).ToNot(BeEmpty()) + + t.Run("apply succeeds if digest matches", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "apply -n %s %s oci://%s -v %s -d %s -p main --wait", + namespace, + instanceName, + modURL, + modVer, + mod.Digest, + )) + g.Expect(err).NotTo(HaveOccurred()) + }) + + t.Run("apply with digest succeeds without version", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "apply -n %s %s oci://%s -d %s -p main --wait", + namespace, + instanceName, + modURL, + mod.Digest, + )) + g.Expect(err).NotTo(HaveOccurred()) + }) + + t.Run("apply errors out if digest differs", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "apply -n %s %s oci://%s -v %s -d %s -p main --wait", + namespace, + instanceName, + modURL, + modVer, + "sha256:123456", // wrong digest + )) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("digest mismatch, expected sha256:123456 got %s", mod.Digest))) + }) +} + func TestApply_WithBundleConflicts(t *testing.T) { g := NewWithT(t) diff --git a/internal/flags/digest.go b/internal/flags/digest.go new file mode 100644 index 00000000..8c8449db --- /dev/null +++ b/internal/flags/digest.go @@ -0,0 +1,35 @@ +package flags + +import ( + "fmt" + "strings" +) + +type Digest string + +func (f *Digest) String() string { + return string(*f) +} + +func (f *Digest) Set(str string) error { + if str != "" { + s := strings.Split(str, ":") + if len(s) != 2 || s[0] == "" || s[1] == "" { + return fmt.Errorf("digest must be in the format :") + } + } + *f = Digest(str) + return nil +} + +func (f *Digest) Type() string { + return "digest" +} + +func (f *Digest) Shorthand() string { + return "d" +} + +func (f *Digest) Description() string { + return "The digest of the module e.g. sha256:3f29e1b2b05f8371595dc761fed8e8b37544b38d56dfce81a551b46c82f2f56b." +} From 5c8ced589410b50acc89faa7b9fc8ad0f6145ef1 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 15 Dec 2024 01:02:58 +0000 Subject: [PATCH 2/2] Add --digest flag to timoni build Signed-off-by: Matheus Pimenta --- cmd/timoni/build.go | 10 ++++++ cmd/timoni/build_test.go | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/cmd/timoni/build.go b/cmd/timoni/build.go index bf41f6af..71b68f3b 100644 --- a/cmd/timoni/build.go +++ b/cmd/timoni/build.go @@ -72,6 +72,7 @@ type buildFlags struct { module string version flags.Version pkg flags.Package + digest flags.Digest valuesFiles []string output string creds flags.Credentials @@ -82,6 +83,7 @@ var buildArgs buildFlags func init() { buildCmd.Flags().VarP(&buildArgs.version, buildArgs.version.Type(), buildArgs.version.Shorthand(), buildArgs.version.Description()) buildCmd.Flags().VarP(&buildArgs.pkg, buildArgs.pkg.Type(), buildArgs.pkg.Shorthand(), buildArgs.pkg.Description()) + buildCmd.Flags().VarP(&buildArgs.digest, buildArgs.digest.Type(), buildArgs.digest.Shorthand(), buildArgs.digest.Description()) buildCmd.Flags().StringSliceVarP(&buildArgs.valuesFiles, "values", "f", nil, "The local path to values files (cue, yaml or json format).") buildCmd.Flags().StringVarP(&buildArgs.output, "output", "o", "yaml", @@ -100,8 +102,12 @@ func runBuildCmd(cmd *cobra.Command, args []string) error { buildArgs.module = args[1] version := buildArgs.version.String() + digest := buildArgs.digest.String() if version == "" { version = apiv1.LatestVersion + if digest != "" { + version = fmt.Sprintf("@%s", digest) + } } ctx := cuecontext.New() @@ -132,6 +138,10 @@ func runBuildCmd(cmd *cobra.Command, args []string) error { return err } + if digest != "" && mod.Digest != digest { + return fmt.Errorf("digest mismatch, expected %s got %s", digest, mod.Digest) + } + builder := engine.NewModuleBuilder( ctx, buildArgs.name, diff --git a/cmd/timoni/build_test.go b/cmd/timoni/build_test.go index a61899e5..4b2def56 100644 --- a/cmd/timoni/build_test.go +++ b/cmd/timoni/build_test.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "encoding/json" "fmt" "strings" "testing" @@ -229,3 +230,73 @@ func TestBuild(t *testing.T) { g.Expect(err.Error()).To(ContainSubstring("timoni.kubeMinorVersion: invalid value")) }) } + +func TestBuild_WithDigest(t *testing.T) { + g := NewWithT(t) + + instanceName := "frontend" + modPath := "testdata/module" + namespace := rnd("my-namespace", 5) + modName := rnd("my-mod", 5) + modURL := fmt.Sprintf("%s/%s", dockerRegistry, modName) + modVer := "1.0.0" + + // Push the module to registry + pushOut, err := executeCommand(fmt.Sprintf( + "mod push %s oci://%s -v %s -o json", + modPath, + modURL, + modVer, + )) + g.Expect(err).ToNot(HaveOccurred()) + + // Parse digest + var mod struct { + Digest string `json:"digest"` + } + err = json.Unmarshal([]byte(pushOut), &mod) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(mod.Digest).ToNot(BeEmpty()) + + t.Run("build succeeds if digest matches", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "build -n %s %s oci://%s -v %s -d %s -p main -o yaml", + namespace, + instanceName, + modURL, + modVer, + mod.Digest, + )) + g.Expect(err).NotTo(HaveOccurred()) + }) + + t.Run("build with digest succeeds without version", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "build -n %s %s oci://%s -d %s -p main -o yaml", + namespace, + instanceName, + modURL, + mod.Digest, + )) + g.Expect(err).NotTo(HaveOccurred()) + }) + + t.Run("build errors out if digest differs", func(t *testing.T) { + g := NewWithT(t) + + _, err := executeCommand(fmt.Sprintf( + "build -n %s %s oci://%s -v %s -d %s -p main -o yaml", + namespace, + instanceName, + modURL, + modVer, + "sha256:123456", // wrong digest + )) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("digest mismatch, expected sha256:123456 got %s", mod.Digest))) + }) +}