diff --git a/pkg/kn/commands/configuration_edit_flags.go b/pkg/kn/commands/configuration_edit_flags.go index 816f27395..2cc4b7240 100644 --- a/pkg/kn/commands/configuration_edit_flags.go +++ b/pkg/kn/commands/configuration_edit_flags.go @@ -29,6 +29,7 @@ type ConfigurationEditFlags struct { Image string Env []string RequestsFlags, LimitsFlags ResourceFlags + ForceCreate bool } type ResourceFlags struct { @@ -49,6 +50,7 @@ func (p *ConfigurationEditFlags) AddUpdateFlags(command *cobra.Command) { func (p *ConfigurationEditFlags) AddCreateFlags(command *cobra.Command) { p.AddUpdateFlags(command) + command.Flags().BoolVar(&p.ForceCreate, "force", false, "Create service forcefully, replaces existing service if any.") command.MarkFlagRequired("image") } diff --git a/pkg/kn/commands/service_create.go b/pkg/kn/commands/service_create.go index 6b40e997d..9159f0718 100644 --- a/pkg/kn/commands/service_create.go +++ b/pkg/kn/commands/service_create.go @@ -22,6 +22,7 @@ import ( servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func NewServiceCreateCommand(p *KnParams) *cobra.Command { @@ -35,7 +36,19 @@ func NewServiceCreateCommand(p *KnParams) *cobra.Command { kn service create mysvc --image dev.local/ns/image:latest # Create a service with multiple environment variables - kn service create mysvc --env KEY1=VALUE1 --env KEY2=VALUE2 --image dev.local/ns/image:latest`, + kn service create mysvc --env KEY1=VALUE1 --env KEY2=VALUE2 --image dev.local/ns/image:latest + + # Create or replace a service 's1' with image dev.local/ns/image:v2 using --force flag + # if service 's1' doesn't exist, it's just a normal create operation + kn service create --force s1 --image dev.local/ns/image:v2 + + # Create or replace environment variables of service 's1' using --force flag + kn service create --force s1 --env KEY1=NEW_VALUE1 --env NEW_KEY2=NEW_VALUE2 --image dev.local/ns/image:v1 + + # Create or replace default resources of a service 's1' using --force flag + # (earlier configured resource requests and limits will be replaced with default) + # (earlier configured environment variables will be cleared too if any) + kn service create --force s1 --image dev.local/ns/image:v1`, RunE: func(cmd *cobra.Command, args []string) (err error) { if len(args) != 1 { @@ -70,11 +83,26 @@ func NewServiceCreateCommand(p *KnParams) *cobra.Command { if err != nil { return err } - _, err = client.Services(namespace).Create(&service) - if err != nil { - return err + var serviceExists bool = false + if editFlags.ForceCreate { + existingService, err := client.Services(namespace).Get(args[0], v1.GetOptions{}) + if err == nil { + serviceExists = true + service.ResourceVersion = existingService.ResourceVersion + _, err = client.Services(namespace).Update(&service) + if err != nil { + return err + } + fmt.Fprintf(cmd.OutOrStdout(), "Service '%s' successfully replaced in namespace '%s'.\n", args[0], namespace) + } + } + if !serviceExists { + _, err = client.Services(namespace).Create(&service) + if err != nil { + return err + } + fmt.Fprintf(cmd.OutOrStdout(), "Service '%s' successfully created in namespace '%s'.\n", args[0], namespace) } - fmt.Fprintf(cmd.OutOrStdout(), "Service '%s' successfully created in namespace '%s'.\n", args[0], namespace) return nil }, } diff --git a/pkg/kn/commands/service_create_test.go b/pkg/kn/commands/service_create_test.go index 8ee41e278..4cca9b59a 100644 --- a/pkg/kn/commands/service_create_test.go +++ b/pkg/kn/commands/service_create_test.go @@ -290,3 +290,63 @@ func parseQuantity(t *testing.T, quantityString string) resource.Quantity { } return quantity } + +func TestServiceCreateImageForce(t *testing.T) { + _, _, _, err := fakeServiceCreate([]string{ + "service", "create", "foo", "--image", "gcr.io/foo/bar:v1"}) + if err != nil { + t.Fatal(err) + } + action, created, output, err := fakeServiceCreate([]string{ + "service", "create", "foo", "--force", "--image", "gcr.io/foo/bar:v2"}) + if err != nil { + t.Fatal(err) + } else if !action.Matches("create", "services") { + t.Fatalf("Bad action %v", action) + } + conf, err := servinglib.GetConfiguration(created) + if err != nil { + t.Fatal(err) + } else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:v2" { + t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image) + } else if !strings.Contains(output, "foo") || !strings.Contains(output, "default") { + t.Fatalf("wrong output: %s", output) + } +} + +func TestServiceCreateEnvForce(t *testing.T) { + _, _, _, err := fakeServiceCreate([]string{ + "service", "create", "foo", "--image", "gcr.io/foo/bar:v1", "-e", "A=DOGS", "--env", "B=WOLVES"}) + if err != nil { + t.Fatal(err) + } + action, created, output, err := fakeServiceCreate([]string{ + "service", "create", "foo", "--force", "--image", "gcr.io/foo/bar:v2", "-e", "A=CATS", "--env", "B=LIONS"}) + + if err != nil { + t.Fatal(err) + } else if !action.Matches("create", "services") { + t.Fatalf("Bad action %v", action) + } + + expectedEnvVars := map[string]string{ + "A": "CATS", + "B": "LIONS"} + + conf, err := servinglib.GetConfiguration(created) + actualEnvVars, err := servinglib.EnvToMap(conf.RevisionTemplate.Spec.Container.Env) + if err != nil { + t.Fatal(err) + } + if err != nil { + t.Fatal(err) + } else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:v2" { + t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image) + } else if !reflect.DeepEqual( + actualEnvVars, + expectedEnvVars) { + t.Fatalf("wrong env vars:%v", conf.RevisionTemplate.Spec.Container.Env) + } else if !strings.Contains(output, "foo") || !strings.Contains(output, "default") { + t.Fatalf("wrong output: %s", output) + } +}