Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --digest flag to timoni apply and timoni build #452

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
matheuscscp marked this conversation as resolved.
Show resolved Hide resolved
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."
}
Loading