Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding ClusterAnalysisTemplate support for Stage verifications #2673

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2178011
adding ClusterAnalysisTemplates support for verifications of stages
BenHesketh21 Oct 6, 2024
ddc0ca0
Adding ClusterAnalysisTemplates to docs
BenHesketh21 Oct 6, 2024
44f1f6b
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Oct 6, 2024
c1fd414
Improve docs on ClusterAnalysisTemplates
BenHesketh21 Oct 6, 2024
d04fe9a
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Oct 31, 2024
8552cbf
codegen
BenHesketh21 Oct 31, 2024
78f5c0d
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Nov 15, 2024
2cf23fc
Moving cluster analysis templates list outside of projects in UI
BenHesketh21 Nov 15, 2024
aba7dc4
lint fixes
BenHesketh21 Nov 15, 2024
84ef8cc
Adding settings page with tabs for cluster level settings, in this ca…
BenHesketh21 Nov 16, 2024
21134e2
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Nov 17, 2024
1d29cf8
Put tabs in vertically
BenHesketh21 Dec 1, 2024
86a22a2
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Dec 1, 2024
a87b7bc
Fixing controller changes since the refactor
BenHesketh21 Dec 1, 2024
5d26909
unit test and lint fixes
BenHesketh21 Dec 2, 2024
a2b1902
Move settings button underneath users as per diagram provided
BenHesketh21 Dec 2, 2024
042472c
Merge branch 'main' into cluster-analysis-templates
BenHesketh21 Dec 13, 2024
ea28bbc
fix settings title
BenHesketh21 Dec 13, 2024
b458aad
chore(ui): design updates
Marvin9 Jan 3, 2025
17466b0
Merge remote-tracking branch 'origin' into cluster-analysis-templates
Marvin9 Jan 3, 2025
765ff27
fix
Marvin9 Jan 3, 2025
1dea36c
fix
Marvin9 Jan 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ lint-go: install-golangci-lint

.PHONY: format-go
format-go:
golangci-lint run --fix
$(GOLANGCI_LINT) run --fix

.PHONY: lint-proto
lint-proto: install-buf
Expand Down
29 changes: 29 additions & 0 deletions api/service/v1alpha1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ service KargoService {
rpc ListAnalysisTemplates(ListAnalysisTemplatesRequest) returns (ListAnalysisTemplatesResponse);
rpc GetAnalysisTemplate(GetAnalysisTemplateRequest) returns (GetAnalysisTemplateResponse);
rpc DeleteAnalysisTemplate(DeleteAnalysisTemplateRequest) returns (DeleteAnalysisTemplateResponse);
rpc ListClusterAnalysisTemplates(ListClusterAnalysisTemplatesRequest) returns (ListClusterAnalysisTemplatesResponse);
rpc GetClusterAnalysisTemplate(GetClusterAnalysisTemplateRequest) returns (GetClusterAnalysisTemplateResponse);
rpc DeleteClusterAnalysisTemplate(DeleteClusterAnalysisTemplateRequest) returns (DeleteClusterAnalysisTemplateResponse);
rpc GetAnalysisRun(GetAnalysisRunRequest) returns (GetAnalysisRunResponse);

rpc ListAnalysisTemplateConfigMaps(ListAnalysisTemplateConfigMapsRequest) returns (ListAnalysisTemplateConfigMapsResponse);
Expand Down Expand Up @@ -613,6 +616,24 @@ message GetAnalysisTemplateResponse {
}
}

message ListClusterAnalysisTemplatesRequest {}

message ListClusterAnalysisTemplatesResponse {
repeated github.com.akuity.kargo.internal.controller.rollouts.api.v1alpha1.ClusterAnalysisTemplate cluster_analysis_templates = 1 [json_name = "clusteranalysisTemplates"];
}

message GetClusterAnalysisTemplateRequest {
string name = 2;
RawFormat format = 3;
}

message GetClusterAnalysisTemplateResponse {
oneof result {
github.com.akuity.kargo.internal.controller.rollouts.api.v1alpha1.ClusterAnalysisTemplate cluster_analysis_template = 1 [json_name = "clusterAnalysisTemplate"];
bytes raw = 2;
}
}

message GetAnalysisRunRequest {
string namespace = 1;
string name = 2;
Expand All @@ -635,6 +656,14 @@ message DeleteAnalysisTemplateResponse {
/* explicitly empty */
}

message DeleteClusterAnalysisTemplateRequest {
string name = 2;
}

message DeleteClusterAnalysisTemplateResponse {
/* explicitly empty */
}

message ListProjectEventsRequest {
string project = 1;
}
Expand Down
509 changes: 270 additions & 239 deletions api/v1alpha1/generated.pb.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions api/v1alpha1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/v1alpha1/stage_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,11 @@ type AnalysisTemplateReference struct {
//
// +kubebuilder:validation:Required
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// ClusterScope determines whether the template is an
// AnalysisTemplate or a ClusterAnalysisTemplate resource
//
// +kubebuilder:validation:Optional
ClusterScope bool `json:"clusterScope" protobuf:"varint,2,opt,name=clusterScope"`
}

// AnalysisRunMetadata contains optional metadata that should be applied to all
Expand Down
5 changes: 5 additions & 0 deletions charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ spec:
description: AnalysisTemplateReference is a reference to an
AnalysisTemplate.
properties:
clusterScope:
description: |-
ClusterScope determines whether the template is an
AnalysisTemplate or a ClusterAnalysisTemplate resource
type: boolean
name:
description: |-
Name is the name of the AnalysisTemplate in the same project/namespace as
Expand Down
1 change: 1 addition & 0 deletions charts/kargo/templates/api/cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ rules:
- argoproj.io
resources:
- analysistemplates
- clusteranalysistemplates
verbs:
- "*"
{{- end }}
Expand Down
8 changes: 8 additions & 0 deletions charts/kargo/templates/controller/cluster-roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ rules:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
- clusteranalysistemplates
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
Expand Down
7 changes: 7 additions & 0 deletions charts/kargo/templates/users/cluster-roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ rules:
- analysistemplates
verbs:
- "*" # full access to analysistemplates
- apiGroups:
- argoproj.io
resources:
- clusteranalysistemplates
verbs:
- "*" # full access to clusteranalysistemplates
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
Expand Down Expand Up @@ -120,6 +126,7 @@ rules:
resources:
- analysisruns
- analysistemplates
- clusteranalysistemplates
verbs:
- get
- list
Expand Down
23 changes: 20 additions & 3 deletions docs/docs/15-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,11 @@ processes that should be executed after a `Promotion` has successfully deployed
healthy state.

Verification processes are defined through _references_ to one or more
[Argo Rollouts `AnalysisTemplate` resources](https://argoproj.github.io/argo-rollouts/features/analysis/)
that reside in the same `Project`/`Namespace` as the `Stage` resource.
[Argo Rollouts `AnalysisTemplate` or `ClusterAnalysisTemplate` resources](https://argoproj.github.io/argo-rollouts/features/analysis/).
`AnalysisTemplate` resources must reside in the same `Project`/`Namespace` as the `Stage` resource but `ClusterAnalysisTemplate` can be referenced by any `Stage`.

:::info
Argo Rollouts `AnalysisTemplate` resources (and the `AnalysisRun` resources that
Argo Rollouts `AnalysisTemplate` and `ClusterAnalysisTemplate` resources (and the `AnalysisRun` resources that
are spawned from them) were intentionally built to be re-usable in contexts
other than Argo Rollouts. Re-using this resource type to define verification
processes means those processes benefit from this rich and battle-tested feature
Expand Down Expand Up @@ -344,6 +344,23 @@ spec:
value: bar
```

To refer to a `ClusterAnalysisTemplate` that exists across all namespaces,
use the `clusterScope` option.

```yaml
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: test
namespace: kargo-demo
spec:
# ...
verification:
analysisTemplates:
- name: kargo-demo
clusterScope: true
```

An `AnalysisTemplate` could be as simple as the following, which merely executes
a Kubernetes `Job` that is defined inline:

Expand Down
40 changes: 40 additions & 0 deletions internal/api/delete_clusteranalysistemplate_v1alpha1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package api

import (
"context"
"errors"
"fmt"

"connectrpc.com/connect"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/akuity/kargo/internal/controller/rollouts/api/v1alpha1"
svcv1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
)

func (s *server) DeleteClusterAnalysisTemplate(
ctx context.Context,
req *connect.Request[svcv1alpha1.DeleteClusterAnalysisTemplateRequest],
) (*connect.Response[svcv1alpha1.DeleteClusterAnalysisTemplateResponse], error) {
if !s.cfg.RolloutsIntegrationEnabled {
return nil, connect.NewError(
connect.CodeUnimplemented,
errors.New("Argo Rollouts integration is not enabled"),
)
}

name := req.Msg.GetName()
if err := validateFieldNotEmpty("name", name); err != nil {
return nil, err
}

if err := s.client.Delete(ctx, &v1alpha1.ClusterAnalysisTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}); err != nil {
return nil, fmt.Errorf("delete ClusterAnalysisTemplate: %w", err)
}

return connect.NewResponse(&svcv1alpha1.DeleteClusterAnalysisTemplateResponse{}), nil
}
108 changes: 108 additions & 0 deletions internal/api/delete_clusteranalysistemplate_v1alpha1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package api

import (
"context"
"fmt"
"testing"

"connectrpc.com/connect"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/akuity/kargo/internal/api/config"
"github.com/akuity/kargo/internal/api/kubernetes"
"github.com/akuity/kargo/internal/api/validation"
rollouts "github.com/akuity/kargo/internal/controller/rollouts/api/v1alpha1"
svcv1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
)

func TestDeleteClusterAnalysisTemplate(t *testing.T) {
testCases := map[string]struct {
req *svcv1alpha1.DeleteClusterAnalysisTemplateRequest
rolloutsDisabled bool
errExpected bool
expectedCode connect.Code
}{
"empty name": {
req: &svcv1alpha1.DeleteClusterAnalysisTemplateRequest{
Name: "",
},
errExpected: true,
expectedCode: connect.CodeInvalidArgument,
},
"existing ClusterAnalysisTemplate": {
req: &svcv1alpha1.DeleteClusterAnalysisTemplateRequest{
Name: "test",
},
},
"non-existing ClusterAnalysisTemplate": {
req: &svcv1alpha1.DeleteClusterAnalysisTemplateRequest{
Name: "non-existing",
},
errExpected: true,
expectedCode: connect.CodeUnknown,
},
"Argo Rollouts integration is not enabled": {
req: &svcv1alpha1.DeleteClusterAnalysisTemplateRequest{
Name: "test",
},
rolloutsDisabled: true,
errExpected: true,
expectedCode: connect.CodeUnimplemented,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

ctx := context.Background()

cfg := config.ServerConfigFromEnv()
if testCase.rolloutsDisabled {
cfg.RolloutsIntegrationEnabled = false
}

client, err := kubernetes.NewClient(
ctx,
&rest.Config{},
kubernetes.ClientOptions{
SkipAuthorization: true,
NewInternalClient: func(
_ context.Context,
_ *rest.Config,
scheme *runtime.Scheme,
) (client.Client, error) {
return fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(
mustNewObject[rollouts.ClusterAnalysisTemplate]("testdata/clusteranalysistemplate.yaml"),
).
Build(), nil
},
},
)
require.NoError(t, err)

svr := &server{
client: client,
cfg: cfg,
externalValidateProjectFn: validation.ValidateProject,
}
_, err = (svr).DeleteClusterAnalysisTemplate(ctx, connect.NewRequest(testCase.req))
if testCase.errExpected {
require.Error(t, err)
fmt.Printf("actual: %s, expected: %s", connect.CodeOf(err), testCase.expectedCode)
require.Equal(t, testCase.expectedCode, connect.CodeOf(err))
return
}
require.NoError(t, err)

at, err := rollouts.GetClusterAnalysisTemplate(ctx, client, testCase.req.Name)
require.NoError(t, err)
require.Nil(t, at)
})
}
}
Loading
Loading