diff --git a/pkg/build/build.go b/pkg/build/build.go index 60d2563..507475a 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -2,6 +2,7 @@ package build import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -289,11 +290,24 @@ func Build(_ context.Context, c *cli.Command) error { } } + err = writeOutputs(imageName, additionalTags, cacheTag) + if err != nil { + return cli.Exit(err, 1) + } + + return nil +} + +func writeOutputs( + imageName string, + additionalTags []string, + cacheTag string, +) error { outputDirectory := os.TempDir() + "/3lv-cli-output" if _, err := os.Stat(outputDirectory); os.IsNotExist(err) { err := os.Mkdir(outputDirectory, 0o700) if err != nil { - return cli.Exit(err, 1) + return err } } @@ -307,13 +321,29 @@ func Build(_ context.Context, c *cli.Command) error { return cacheTag }() + imageNameWithTag := imageName + ":" + firstAdditionalTagThatsNotCacheTag + + err := os.WriteFile( + outputDirectory+"/image-name-tag", + []byte(imageNameWithTag), + 0o700, + ) + if err != nil { + return err + } + + imageDigest, err := getImageDigest(imageNameWithTag) + if err != nil { + return err + } + err = os.WriteFile( - outputDirectory+"/image-name", - []byte(imageName+":"+firstAdditionalTagThatsNotCacheTag), + outputDirectory+"/image-digest", + []byte(imageDigest), 0o700, ) if err != nil { - return cli.Exit(err, 1) + return err } return nil @@ -479,3 +509,42 @@ func copyDockerfileToCurrentDirectory(dockerfilePath string, nonInteractive bool return newDockerfilePath, nil } + +// Incomplete, since we only need digest. +type DockerManifest struct { + Descriptor struct { + Digest string `json:"digest"` + } `json:"Descriptor"` +} + +func getImageDigest(imageNameWithTag string) (string, error) { + dockerManifestInspectCommand := dockerManifestInspectCommand(imageNameWithTag, nil) + if command.IsError(dockerManifestInspectCommand) { + return "", dockerManifestInspectCommand.Error + } + + var manifest DockerManifest + + err := json.Unmarshal([]byte(dockerManifestInspectCommand.Output), &manifest) + if err != nil { + return "", err + } + + return manifest.Descriptor.Digest, nil +} + +func dockerManifestInspectCommand( + imageNameWithTag string, + options *command.RunOptions, +) command.Output { + return command.Run( + *exec.Command( + "docker", + "manifest", + "inspect", + "-v", + imageNameWithTag, + ), + options, + ) +} diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 45ec055..8b37658 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -2,7 +2,6 @@ package deploy import ( "context" - "encoding/json" "fmt" "log" "os/exec" @@ -10,7 +9,6 @@ import ( "strings" "github.com/3lvia/cli/pkg/auth" - "github.com/3lvia/cli/pkg/build" "github.com/3lvia/cli/pkg/command" "github.com/3lvia/cli/pkg/shared" "github.com/3lvia/cli/pkg/style" @@ -35,6 +33,13 @@ func Command() *cli.Command { Name: "image-tag", Aliases: []string{"i"}, Usage: "The image tag to deploy.", + Sources: cli.EnvVars("3LV_IMAGE_TAG"), + }, + &cli.StringFlag{ + Name: "image-digest", + Aliases: []string{"I"}, + Usage: "The image digest to deploy.", + Sources: cli.EnvVars("3LV_IMAGE_DIGEST"), }, &cli.StringFlag{ Name: "environment", @@ -200,8 +205,9 @@ func Deploy(ctx context.Context, c *cli.Command) error { return configForApplication.HelmValuesFile }() + imageTag := c.String("image-tag") - imageDigest := getImageDigest(systemName, applicationName, imageTag) + imageDigest := c.String("image-digest") commitHash, err := utils.ResolveCommitHash(c.String("commit-hash")) if err != nil { @@ -432,56 +438,3 @@ func setupKubernetes( return nil } - -func dockerManifestInspectCommand( - imageNameWithTag string, - runOptions *command.RunOptions, -) command.Output { - return command.Run( - *exec.Command( - "docker", - "manifest", - "inspect", - imageNameWithTag, - "-v", - ), - runOptions, - ) -} - -// Incomplete, since we only need digest. -type DockerManifest struct { - Ref string `json:"Ref"` - Descriptor struct { - Digest string `json:"digest"` - } `json:"Descriptor"` -} - -func getImageDigest( - systemName string, - applicationName string, - imageTag string, -) string { - imageName, err := build.GetImageName( - build.DefaultElviaContainerRegistry, - systemName, - applicationName, - ) - if err != nil { - return "" - } - - dockerManifestInspectCommand := dockerManifestInspectCommand(imageName+":"+imageTag, nil) - if command.IsError(dockerManifestInspectCommand) { - return "" - } - - var manifest DockerManifest - - err = json.Unmarshal([]byte(dockerManifestInspectCommand.Output), &manifest) - if err != nil { - return "" - } - - return manifest.Descriptor.Digest -} diff --git a/pkg/deploy/deploy_test.go b/pkg/deploy/deploy_test.go deleted file mode 100644 index 8a7dc07..0000000 --- a/pkg/deploy/deploy_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package deploy - -import ( - "strings" - "testing" - - "github.com/3lvia/cli/pkg/build" - "github.com/3lvia/cli/pkg/command" -) - -func TestDockerInspectCommand(t *testing.T) { - t.Parallel() - - const imageName = "ghcr.io/3lvia/core/demo-api" + build.DefaultCacheTag - - expectedCommandString := strings.Join( - []string{ - "docker", - "manifest", - "inspect", - imageName, - "-v", - }, - " ", - ) - - actualCommand := dockerManifestInspectCommand( - imageName, - &command.RunOptions{DryRun: true}, - ) - - command.ExpectedCommandStringEqualsActualCommand( - t, - expectedCommandString, - actualCommand, - ) -} diff --git a/pkg/deploy/helm_test.go b/pkg/deploy/helm_test.go index f0bb933..965500d 100644 --- a/pkg/deploy/helm_test.go +++ b/pkg/deploy/helm_test.go @@ -154,7 +154,7 @@ func TestHelmDeployCommand1(t *testing.T) { ) } -func TestHelmDeployCommand2(t *testing.T) { +func TestHelmDeployCommandWithDigestAndStatefulSet(t *testing.T) { t.Parallel() const ( @@ -217,7 +217,7 @@ func TestHelmDeployCommand2(t *testing.T) { ) } -func TestHelmDeployCommand3(t *testing.T) { +func TestHelmDeployCommandWithInvalidWorkloadType(t *testing.T) { t.Parallel() const ( @@ -252,7 +252,7 @@ func TestHelmDeployCommand3(t *testing.T) { } } -func TestHelmDeployCommand4(t *testing.T) { +func TestHelmDeployCommandWithIssRepository(t *testing.T) { t.Parallel() const ( @@ -313,7 +313,7 @@ func TestHelmDeployCommand4(t *testing.T) { ) } -func TestHelmDeployCommand5(t *testing.T) { +func TestHelmDeployCommandWithDigestAndStatefulSetAndIssRepository(t *testing.T) { t.Parallel() const ( @@ -322,7 +322,7 @@ func TestHelmDeployCommand5(t *testing.T) { applicationName = "demo-api" environment = "prod" workloadType = "statefulset" - imageTag = "v420" + imageTag = "" imageDigest = "sha256:abcdef" repositoryName = "iss-demo-api" commitHash = "abcdef" diff --git a/setup/action.yml b/setup/action.yml index 23a9c82..933bf6e 100644 --- a/setup/action.yml +++ b/setup/action.yml @@ -2,7 +2,7 @@ name: 'Setup 3lv' description: 'Sets up the Elvia CLI on your GitHub Actions runner.' inputs: version: - description: 'The version of 3lv to install. Specify `git` to build and install the latest version from the default branch.' + description: 'The version of 3lv to install. Specify a branch name to build and install from git.' required: false default: 'latest' diff --git a/tests/deploy/run_tests.sh b/tests/deploy/run_tests.sh index f3b234d..72c434d 100755 --- a/tests/deploy/run_tests.sh +++ b/tests/deploy/run_tests.sh @@ -6,7 +6,7 @@ test_disabled_deploy() { if 3lv deploy \ -s core \ -f tests/deploy/values.yml \ - -i latest-cache \ + -i containerregistryelvia.azurecr.io/core-demo-api:latest-cache \ demo-api; then echo "Should fail since CI is not set" exit 1