mirror of https://github.com/knative/func.git
feat: func deploy accepts image digest in --image (#1098)
* --image can be given with digest, created parser and edited some help text to reflect this * fixed small stuff * tests for deploy with --image * move parser to file, static test should be kept active * updated some error mesgs; now prints a warning if flags not set explicitly, if set to a wrong value, return an error; updated tests to fit new error messages * --image flag message edit * removed warning; instead print info about disabled push a build unconditionally
This commit is contained in:
parent
3b198cb781
commit
c57af36f74
|
@ -63,7 +63,7 @@ that is pushed to an image registry, and finally the function's Knative service
|
|||
// Flags shared with Build specifically related to building:
|
||||
cmd.Flags().StringP("builder", "", "pack", "build strategy to use when creating the underlying image. Currently supported build strategies are 'pack' and 's2i'.")
|
||||
cmd.Flags().StringP("builder-image", "", "", "builder image, either an as a an image name or a mapping name.\nSpecified value is stored in func.yaml (as 'builder' field) for subsequent builds. ($FUNC_BUILDER_IMAGE)")
|
||||
cmd.Flags().StringP("image", "i", "", "Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry (Env: $FUNC_IMAGE)")
|
||||
cmd.Flags().StringP("image", "i", "", "Full image name in the form [registry]/[namespace]/[name]:[tag]@[digest]. This option takes precedence over --registry. Specifying digest is optional, but if it is given, 'build' and 'push' phases are disabled. (Env: $FUNC_IMAGE)")
|
||||
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
|
||||
cmd.Flags().BoolP("push", "u", true, "Attempt to push the function image to registry before deploying (Env: $FUNC_PUSH)")
|
||||
cmd.Flags().StringP("platform", "", "", "Target platform to build (e.g. linux/amd64).")
|
||||
|
@ -104,11 +104,27 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
|
|||
return
|
||||
}
|
||||
|
||||
//if --image contains '@', validate image digest and disable build and push if not set, otherwise return an error
|
||||
imageSplit := strings.Split(config.Image, "@")
|
||||
imageDigestProvided := false
|
||||
|
||||
if len(imageSplit) == 2 {
|
||||
if config, err = parseImageDigest(imageSplit, config, cmd); err != nil {
|
||||
return
|
||||
}
|
||||
imageDigestProvided = true
|
||||
}
|
||||
|
||||
function, err := functionWithOverrides(config.Path, functionOverrides{Namespace: config.Namespace, Image: config.Image})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// save image digest if provided in --image
|
||||
if imageDigestProvided {
|
||||
function.ImageDigest = imageSplit[1]
|
||||
}
|
||||
|
||||
function.Envs, _, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -500,3 +516,32 @@ func validateBuildType(buildType string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseImageDigest(imageSplit []string, config deployConfig, cmd *cobra.Command) (deployConfig, error) {
|
||||
|
||||
if !strings.HasPrefix(imageSplit[1], "sha256:") {
|
||||
return config, fmt.Errorf("value '%s' in --image has invalid prefix syntax for digest (should be 'sha256:')", config.Image)
|
||||
}
|
||||
|
||||
if len(imageSplit[1][7:]) != 64 {
|
||||
return config, fmt.Errorf("sha256 hash in '%s' from --image has the wrong length (%d), should be 64", imageSplit[1], len(imageSplit[1][7:]))
|
||||
}
|
||||
|
||||
// if --build was set but not as 'disabled', return an error
|
||||
if cmd.Flags().Changed("build") && config.BuildType != "disabled" {
|
||||
return config, fmt.Errorf("the --build flag '%s' is not valid when using --image with digest", config.BuildType)
|
||||
}
|
||||
|
||||
// if the --push flag was set by a user to 'true', return an error
|
||||
if cmd.Flags().Changed("push") && config.Push {
|
||||
return config, fmt.Errorf("the --push flag '%v' is not valid when using --image with digest", config.Push)
|
||||
}
|
||||
|
||||
fmt.Printf("Deploying existing image with digest %s. Build and push are disabled.\n", imageSplit[1])
|
||||
|
||||
config.BuildType = "disabled"
|
||||
config.Push = false
|
||||
config.Image = imageSplit[0]
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
@ -180,3 +181,92 @@ created: 2009-11-10 23:00:00`,
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_imageWithDigest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
image string
|
||||
buildType string
|
||||
pushBool bool
|
||||
funcFile string
|
||||
errString string
|
||||
}{
|
||||
{
|
||||
name: "valid full name with digest, expect success",
|
||||
image: "docker.io/4141gauron3268/static_test_digest:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
|
||||
errString: "",
|
||||
funcFile: `name: test-func
|
||||
runtime: go`,
|
||||
},
|
||||
{
|
||||
name: "valid image name, build not 'disabled', expect error",
|
||||
image: "docker.io/4141gauron3268/static_test_digest:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
|
||||
buildType: "local",
|
||||
errString: "the --build flag 'local' is not valid when using --image with digest",
|
||||
funcFile: `name: test-func
|
||||
runtime: go`,
|
||||
},
|
||||
{
|
||||
name: "valid image name, --push specified, expect error",
|
||||
image: "docker.io/4141gauron3268/static_test_digest:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
|
||||
pushBool: true,
|
||||
errString: "the --push flag 'true' is not valid when using --image with digest",
|
||||
funcFile: `name: test-func
|
||||
runtime: go`,
|
||||
},
|
||||
{
|
||||
name: "invalid digest prefix, expect error",
|
||||
image: "docker.io/4141gauron3268/static_test_digest:latest@Xsha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
|
||||
errString: "value 'docker.io/4141gauron3268/static_test_digest:latest@Xsha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e' in --image has invalid prefix syntax for digest (should be 'sha256:')",
|
||||
funcFile: `name: test-func
|
||||
runtime: go`,
|
||||
},
|
||||
{
|
||||
name: "invalid sha hash length(added X at the end), expect error",
|
||||
image: "docker.io/4141gauron3268/static_test_digest:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4eX",
|
||||
errString: "sha256 hash in 'sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4eX' from --image has the wrong length (65), should be 64",
|
||||
funcFile: `name: test-func
|
||||
runtime: go`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deployer := mock.NewDeployer()
|
||||
cmd := NewDeployCmd(NewClientFactory(func() *fn.Client {
|
||||
return fn.New(
|
||||
fn.WithDeployer(deployer))
|
||||
}))
|
||||
|
||||
// Set flags manually & reset after.
|
||||
// Differs whether build was set via CLI (gives an error if not 'disabled')
|
||||
// or not (prints just a warning)
|
||||
if tt.buildType == "" {
|
||||
cmd.SetArgs([]string{
|
||||
fmt.Sprintf("--image=%s", tt.image),
|
||||
fmt.Sprintf("--push=%t", tt.pushBool),
|
||||
})
|
||||
} else {
|
||||
cmd.SetArgs([]string{
|
||||
fmt.Sprintf("--image=%s", tt.image),
|
||||
fmt.Sprintf("--build=%s", tt.buildType),
|
||||
fmt.Sprintf("--push=%t", tt.pushBool),
|
||||
})
|
||||
}
|
||||
defer cmd.ResetFlags()
|
||||
|
||||
// set test case's func.yaml
|
||||
if err := os.WriteFile("func.yaml", []byte(tt.funcFile), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
_, err := cmd.ExecuteContextC(ctx)
|
||||
if err != nil {
|
||||
if err := err.Error(); tt.errString != err {
|
||||
t.Fatalf("Error expected to be (%v) but was (%v)", tt.errString, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue