Skip to content

Commit

Permalink
Merge pull request #452 from matheuscscp/apply-digest
Browse files Browse the repository at this point in the history
Add --digest flag to `timoni apply` and `timoni build`
  • Loading branch information
stefanprodan authored Dec 15, 2024
2 parents 6fe5724 + 5c8ced5 commit 565a5b9
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 1 deletion.
16 changes: 15 additions & 1 deletion cmd/timoni/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ type applyFlags struct {
module string
version flags.Version
pkg flags.Package
digest flags.Digest
valuesFiles []string
dryrun bool
diff bool
Expand All @@ -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,
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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,
Expand Down
71 changes: 71 additions & 0 deletions cmd/timoni/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions cmd/timoni/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
71 changes: 71 additions & 0 deletions cmd/timoni/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package main

import (
"encoding/json"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -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)))
})
}
35 changes: 35 additions & 0 deletions internal/flags/digest.go
Original file line number Diff line number Diff line change
@@ -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 <sha-type>:<hex>")
}
}
*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."
}

0 comments on commit 565a5b9

Please sign in to comment.