Skip to content

Commit

Permalink
feat(cmd): promote multi tenancy
Browse files Browse the repository at this point in the history
Closes #3890
  • Loading branch information
squakez committed Jul 31, 2023
1 parent 81a8df8 commit 2708820
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 11 deletions.
32 changes: 23 additions & 9 deletions pkg/cmd/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) (*cobra.Command, *promoteCmdO
RootCmdOptions: rootCmdOptions,
}
cmd := cobra.Command{
Use: "promote my-it --to [namespace]",
Use: "promote my-it [--to <namespace>] [-x <promoted-operator-id>]",
Short: "Promote an Integration/Pipe from an environment to another",
Long: "Promote an Integration/Pipe from an environment to another, for example from a Development environment to a Production environment",
PreRunE: decode(&options),
RunE: options.run,
}

cmd.Flags().String("to", "", "The namespace where to promote the Integration")
cmd.Flags().String("to", "", "The namespace where to promote the Integration/Pipe")
cmd.Flags().StringP("to-operator", "x", "", "The operator id which will reconcile the promoted Integration/Pipe")
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
cmd.Flags().BoolP("image", "i", false, "Output the container image only")

Expand All @@ -66,16 +67,20 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) (*cobra.Command, *promoteCmdO
type promoteCmdOptions struct {
*RootCmdOptions
To string `mapstructure:"to" yaml:",omitempty"`
ToOperator string `mapstructure:"to-operator" yaml:",omitempty"`
OutputFormat string `mapstructure:"output" yaml:",omitempty"`
Image bool `mapstructure:"image" yaml:",omitempty"`
}

func (o *promoteCmdOptions) validate(_ *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("promote expects an Integration/Pipe name argument")
return errors.New("promote requires an Integration/Pipe name argument")
}
if o.To == "" {
return errors.New("promote expects a destination namespace as --to argument")
return errors.New("promote requires a destination namespace as --to argument")
}
if o.To == o.Namespace {
return errors.New("source and destination namespaces must be different in order to avoid promoted Integration/Pipe clashes with the source Integration/Pipe")
}
return nil
}
Expand Down Expand Up @@ -457,7 +462,7 @@ func (o *promoteCmdOptions) editIntegration(it *v1.Integration) *v1.Integration
dst := v1.NewIntegration(o.To, it.Name)
contImage := it.Status.Image
dst.Spec = *it.Spec.DeepCopy()
dst.Annotations = cloneAnnotations(it.Annotations)
dst.Annotations = cloneAnnotations(it.Annotations, o.ToOperator)
dst.Labels = cloneLabels(it.Labels)
if dst.Spec.Traits.Container == nil {
dst.Spec.Traits.Container = &traitv1.ContainerTrait{}
Expand All @@ -466,14 +471,23 @@ func (o *promoteCmdOptions) editIntegration(it *v1.Integration) *v1.Integration
return &dst
}

// Return all annotations but the ones specific to source (ie, the operator).
func cloneAnnotations(ann map[string]string) map[string]string {
// Return all annotations overriding the operator Id if provided.
func cloneAnnotations(ann map[string]string, operatorID string) map[string]string {
operatorIDAnnotationSet := false
newMap := make(map[string]string)
for k, v := range ann {
if k != v1.OperatorIDAnnotation {
if k == v1.OperatorIDAnnotation {
if operatorID != "" {
newMap[v1.OperatorIDAnnotation] = operatorID
operatorIDAnnotationSet = true
}
} else {
newMap[k] = v
}
}
if !operatorIDAnnotationSet && operatorID != "" {
newMap[v1.OperatorIDAnnotation] = operatorID
}
return newMap
}

Expand All @@ -489,7 +503,7 @@ func cloneLabels(lbs map[string]string) map[string]string {
func (o *promoteCmdOptions) editPipe(kb *v1.Pipe, it *v1.Integration) *v1.Pipe {
dst := v1.NewPipe(o.To, kb.Name)
dst.Spec = *kb.Spec.DeepCopy()
dst.Annotations = cloneAnnotations(kb.Annotations)
dst.Annotations = cloneAnnotations(kb.Annotations, o.ToOperator)
dst.Labels = cloneLabels(kb.Labels)
contImage := it.Status.Image
if dst.Spec.Integration == nil {
Expand Down
60 changes: 58 additions & 2 deletions pkg/cmd/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func TestItImageOnly(t *testing.T) {
_, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultIntegration, &srcCatalog, &dstCatalog)
output, err := test.ExecuteCommand(promoteCmd, cmdPromote, "my-it-test", "--to", "prod-namespace", "-i", "-n", "default")
assert.Nil(t, err)
assert.Equal(t, fmt.Sprintf("my-special-image\n"), output)
assert.Equal(t, fmt.Sprint("my-special-image\n"), output)
}

func TestPipeImageOnly(t *testing.T) {
Expand All @@ -283,5 +283,61 @@ func TestPipeImageOnly(t *testing.T) {
_, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultKB, &defaultIntegration, &srcCatalog, &dstCatalog)
output, err := test.ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", "--to", "prod-namespace", "-i", "-n", "default")
assert.Nil(t, err)
assert.Equal(t, fmt.Sprintf("my-special-image\n"), output)
assert.Equal(t, fmt.Sprint("my-special-image\n"), output)
}

func TestIntegrationToOperatorId(t *testing.T) {
srcPlatform := v1.NewIntegrationPlatform("default", platform.DefaultPlatformName)
srcPlatform.Status.Version = defaults.Version
srcPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
srcPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
dstPlatform := v1.NewIntegrationPlatform("prod-namespace", platform.DefaultPlatformName)
dstPlatform.Status.Version = defaults.Version
dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
defaultIntegration := nominalIntegration("my-it-test")
srcCatalog := createTestCamelCatalog(srcPlatform)
dstCatalog := createTestCamelCatalog(dstPlatform)

// Verify default (missing) operator Id
promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultIntegration, &srcCatalog, &dstCatalog)
output, err := test.ExecuteCommand(promoteCmd, cmdPromote, "my-it-test", "-x", "my-prod-operator", "-o", "yaml", "--to", "prod")
assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat)
assert.Nil(t, err)
assert.Equal(t, `apiVersion: camel.apache.org/v1
kind: Integration
metadata:
annotations:
camel.apache.org/operator.id: my-prod-operator
creationTimestamp: null
name: my-it-test
namespace: prod
spec:
traits:
container:
image: my-special-image
status: {}
`, output)
// Verify also when the operator Id is set in the integration
defaultIntegration.Annotations = map[string]string{
"camel.apache.org/operator.id": "camel-k",
}
promoteCmdOptions, promoteCmd, _ = initializePromoteCmdOptions(t, &srcPlatform, &dstPlatform, &defaultIntegration, &srcCatalog, &dstCatalog)
output, err = test.ExecuteCommand(promoteCmd, cmdPromote, "my-it-test", "-x", "my-prod-operator", "-o", "yaml", "--to", "prod")
assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat)
assert.Nil(t, err)
assert.Equal(t, `apiVersion: camel.apache.org/v1
kind: Integration
metadata:
annotations:
camel.apache.org/operator.id: my-prod-operator
creationTimestamp: null
name: my-it-test
namespace: prod
spec:
traits:
container:
image: my-special-image
status: {}
`, output)
}

0 comments on commit 2708820

Please sign in to comment.