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:
|
// 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", "", "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("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().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().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).")
|
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
|
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})
|
function, err := functionWithOverrides(config.Path, functionOverrides{Namespace: config.Namespace, Image: config.Image})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save image digest if provided in --image
|
||||||
|
if imageDigestProvided {
|
||||||
|
function.ImageDigest = imageSplit[1]
|
||||||
|
}
|
||||||
|
|
||||||
function.Envs, _, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
|
function.Envs, _, err = mergeEnvs(function.Envs, config.EnvToUpdate, config.EnvToRemove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -500,3 +516,32 @@ func validateBuildType(buildType string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"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