Skip to content

Commit

Permalink
feat: implement PromotionTemplate references
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco committed Nov 29, 2024
1 parent 146ab7d commit 45eabc1
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 40 deletions.
28 changes: 27 additions & 1 deletion internal/api/promote_downstream_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func (s *server) PromoteDownstream(
}
}

templates := make(map[string]*kargoapi.PromotionTemplate, len(downstreams))
promoteErrs := make([]error, 0, len(downstreams))
createdPromos := make([]*kargoapi.Promotion, 0, len(downstreams))
for _, downstream := range downstreams {
Expand All @@ -142,7 +143,32 @@ func (s *server) PromoteDownstream(
// flow Stage, as it does not have a template for a Promotion.
continue
}
newPromo := kargo.NewPromotion(ctx, downstream, freight.Name)

template := downstream.Spec.PromotionTemplate
if template == nil {
if downstream.Spec.PromotionTemplateRef == nil || downstream.Spec.PromotionTemplateRef.Name == "" {
promoteErrs = append(promoteErrs, fmt.Errorf("Stage %q has no PromotionTemplate", downstream.Name))
continue
}

var ok bool
if template, ok = templates[downstream.Spec.PromotionTemplateRef.Name]; !ok {
template = &kargoapi.PromotionTemplate{}
if err = s.client.Get(ctx, types.NamespacedName{
Namespace: downstream.Namespace,
Name: downstream.Spec.PromotionTemplateRef.Name,
}, template); err != nil {
promoteErrs = append(promoteErrs, fmt.Errorf("get PromotionTemplate %q: %w", downstream.Spec.PromotionTemplateRef.Name, err))
continue
}

templates[downstream.Spec.PromotionTemplateRef.Name] = template.DeepCopy()
} else {
template = template.DeepCopy()
}
}

newPromo := kargo.NewPromotion(ctx, *template, stage.Namespace, stage.Name, freight.Name)
if err = s.createPromotionFn(ctx, &newPromo); err != nil {
promoteErrs = append(promoteErrs, err)
continue
Expand Down
20 changes: 19 additions & 1 deletion internal/api/promote_to_stage_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,25 @@ func (s *server) PromoteToStage(
return nil, err
}

promotion := kargo.NewPromotion(ctx, *stage, freight.Name)
template := stage.Spec.PromotionTemplate
if template == nil {
if stage.Spec.PromotionTemplateRef == nil || stage.Spec.PromotionTemplateRef.Name == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
fmt.Errorf("Stage %q has no promotion template", stageName),
)
}

template = &kargoapi.PromotionTemplate{}
if err = s.client.Get(ctx, types.NamespacedName{
Namespace: project,
Name: stage.Spec.PromotionTemplateRef.Name,
}, template); err != nil {
return nil, fmt.Errorf("get PromotionTemplate %q: %w", stage.Spec.PromotionTemplateRef.Name, err)
}
}

promotion := kargo.NewPromotion(ctx, *template, project, stageName, freightName)
if err := s.createPromotionFn(ctx, &promotion); err != nil {
return nil, fmt.Errorf("create promotion: %w", err)
}
Expand Down
7 changes: 7 additions & 0 deletions internal/api/promote_to_stage_v1alpha1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import (

func TestPromoteToStage(t *testing.T) {
testStageSpec := kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{{
Origin: kargoapi.FreightOrigin{
Kind: kargoapi.FreightOriginKindWarehouse,
Expand Down
29 changes: 28 additions & 1 deletion internal/controller/stages/regular_stages.go
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,7 @@ func (r *RegularStageReconciler) autoPromoteFreight(
currentFreight := newStatus.FreightHistory.Current()

// Check if there is any new Freight which can be auto-promoted.
template := stage.Spec.PromotionTemplate.DeepCopy()
for origin, freight := range promotableFreight {
if len(freight) == 0 {
logger.Debug("no Freight from origin available for auto-promotion", "origin", origin)
Expand Down Expand Up @@ -1544,8 +1545,34 @@ func (r *RegularStageReconciler) autoPromoteFreight(
continue
}

// If we do not have a PromotionTemplate, then we should attempt to
// retrieve it based on the defined reference.
if template == nil {
if stage.Spec.PromotionTemplateRef == nil || stage.Spec.PromotionTemplateRef.Name == "" {
freightLogger.Debug("no PromotionTemplate defined for Stage")
// To be able to recover from this, we need a change to the Stage
// specification which will trigger a new reconciliation. Because
// of this, we swallow the error.
return newStatus, nil
}

// Get the PromotionTemplate based on the defined reference.
template = &kargoapi.PromotionTemplate{}
if err = r.client.Get(
ctx,
types.NamespacedName{
Namespace: stage.Namespace,
Name: stage.Spec.PromotionTemplateRef.Name,
},
template,
); err != nil {
// Return the error to attempt to recover from this.
return newStatus, err
}
}

// Auto promote the latest available Freight and record an event.
promotion := kargo.NewPromotion(ctx, *stage, latestFreight.Name)
promotion := kargo.NewPromotion(ctx, *template, stage.Namespace, stage.Name, latestFreight.Name)
if err := r.client.Create(ctx, &promotion); err != nil {
return newStatus, fmt.Errorf(
"error creating Promotion for Freight %q in namespace %q: %w",
Expand Down
132 changes: 127 additions & 5 deletions internal/controller/stages/regular_stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func TestRegularStageReconciler_Reconcile(t *testing.T) {
string,
client.Object,
client.Patch,
...client.SubResourcePatchOption,
...client.SubResourcePatchOption,
) error {
return fmt.Errorf("status update error")
},
Expand Down Expand Up @@ -287,7 +287,7 @@ func TestRegularStageReconciler_Reconcile(t *testing.T) {
string,
client.Object,
client.Patch,
...client.SubResourcePatchOption,
...client.SubResourcePatchOption,
) error {
return fmt.Errorf("status update error")
},
Expand Down Expand Up @@ -3653,7 +3653,7 @@ func TestRegularStageReconciler_abortVerification(t *testing.T) {
client.WithWatch,
client.Object,
client.Patch,
...client.PatchOption,
...client.PatchOption,
) error {
return fmt.Errorf("something went wrong")
},
Expand Down Expand Up @@ -3905,7 +3905,7 @@ func TestRegularStageReconciler_findExistingAnalysisRun(t *testing.T) {
context.Context,
client.WithWatch,
client.ObjectList,
...client.ListOption,
...client.ListOption,
) error {
return fmt.Errorf("list error")
},
Expand Down Expand Up @@ -4250,6 +4250,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4463,6 +4470,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4527,6 +4541,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4592,6 +4613,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4677,6 +4705,79 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
assert.True(t, freightNames["freight-2"])
},
},
{
name: "creates promotion using template reference",
stage: &kargoapi.Stage{
ObjectMeta: metav1.ObjectMeta{
Namespace: "fake-project",
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplateRef: &kargoapi.PromotionTemplateReference{
Name: "test-template",
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Kind: kargoapi.FreightOriginKindWarehouse,
Name: "test-warehouse",
},
Sources: kargoapi.FreightSources{
Direct: true,
},
},
},
},
},
objects: []client.Object{
&kargoapi.Project{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-project",
},
Spec: &kargoapi.ProjectSpec{
PromotionPolicies: []kargoapi.PromotionPolicy{
{
Stage: "test-stage",
AutoPromotionEnabled: true,
},
},
},
},
&kargoapi.Freight{
ObjectMeta: metav1.ObjectMeta{
Namespace: "fake-project",
Name: "test-freight",
CreationTimestamp: metav1.Time{Time: now},
},
Origin: kargoapi.FreightOrigin{
Kind: kargoapi.FreightOriginKindWarehouse,
Name: "test-warehouse",
},
Status: kargoapi.FreightStatus{},
},
&kargoapi.PromotionTemplate{
ObjectMeta: metav1.ObjectMeta{
Namespace: "fake-project",
Name: "test-template",
},
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
},
assertions: func(
t *testing.T,
e *fakeevent.EventRecorder,
_ client.Client,
_ kargoapi.StageStatus,
err error,
) {
require.NoError(t, err)
require.Len(t, e.Events, 1)
},
},
{
name: "creates promotion with events",
stage: &kargoapi.Stage{
Expand All @@ -4685,6 +4786,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4750,6 +4858,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4817,6 +4932,13 @@ func TestRegularStageReconciler_autoPromoteFreight(t *testing.T) {
Name: "test-stage",
},
Spec: kargoapi.StageSpec{
PromotionTemplate: &kargoapi.PromotionTemplate{
Spec: kargoapi.PromotionTemplateSpec{
Steps: []kargoapi.PromotionStep{
{},
},
},
},
RequestedFreight: []kargoapi.FreightRequest{
{
Origin: kargoapi.FreightOrigin{
Expand Down Expand Up @@ -4950,7 +5072,7 @@ func TestRegularStageReconciler_autoPromotionAllowed(t *testing.T) {
client.WithWatch,
client.ObjectKey,
client.Object,
...client.GetOption,
...client.GetOption,
) error {
return fmt.Errorf("something went wrong")
},
Expand Down
20 changes: 10 additions & 10 deletions internal/kargo/kargo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ const (
// naming convention.
func NewPromotion(
ctx context.Context,
stage kargoapi.Stage,
freight string,
template kargoapi.PromotionTemplate,
namespace, stage, freight string,
) kargoapi.Promotion {
shortHash := freight
if len(shortHash) > 7 {
shortHash = freight[0:7]
}
shortStageName := stage.Name
if len(stage.Name) > maxStageNamePrefixLength {
shortStageName := stage
if len(shortStageName) > maxStageNamePrefixLength {
shortStageName = shortStageName[0:maxStageNamePrefixLength]
}

Expand All @@ -51,18 +51,18 @@ func NewPromotion(
promotion := kargoapi.Promotion{
ObjectMeta: metav1.ObjectMeta{
Name: promoName,
Namespace: stage.Namespace,
Namespace: namespace,
Annotations: annotations,
},
Spec: kargoapi.PromotionSpec{
Stage: stage.Name,
Stage: stage,
Freight: freight,
},
}
if stage.Spec.PromotionTemplate != nil {
promotion.Spec.Vars = stage.Spec.PromotionTemplate.Spec.Vars
promotion.Spec.Steps = stage.Spec.PromotionTemplate.Spec.Steps
}

promotion.Spec.Vars = template.Spec.Vars
promotion.Spec.Steps = template.Spec.Steps

return promotion
}

Expand Down
Loading

0 comments on commit 45eabc1

Please sign in to comment.