From ec27c1ce4b957a076670fd71163ab72a6175f075 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Tue, 9 Jan 2024 15:50:08 -0500 Subject: [PATCH 01/10] resource type changes Signed-off-by: Kent Rancourt --- api/v1alpha1/stage_types.go | 52 +++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/stage_types.go b/api/v1alpha1/stage_types.go index 3c1a52cbb..26401277a 100644 --- a/api/v1alpha1/stage_types.go +++ b/api/v1alpha1/stage_types.go @@ -20,6 +20,44 @@ const ( StagePhaseVerifying StagePhase = "Verifying" ) +type VerificationPhase string + +// Note: VerificationPhases are identical to AnalysisRunPhases. In almost all +// cases, the VerificationPhase will be a reflection of the underlying +// AnalysisRunPhase. There are exceptions to this, such as in the case where an +// AnalysisRun cannot be launched successfully. + +const ( + // VerificationPhasePending denotes a verification process that has not yet + // started yet. + VerificationPhasePending VerificationPhase = "Pending" + // VerificationPhaseRunning denotes a verification that is currently running. + VerificationPhaseRunning VerificationPhase = "Running" + // VerificationPhaseSuccessful denotes a verification process that has + // completed successfully. + VerificationPhaseSuccessful VerificationPhase = "Successful" + // VerificationPhaseFailed denotes a verification process that has completed + // with a failure. + VerificationPhaseFailed VerificationPhase = "Failed" + // VerificationPhaseError denotes a verification process that has completed + // with an error. + VerificationPhaseError VerificationPhase = "Error" + // VerificationPhaseInconclusive denotes a verification process that has + // completed with an inconclusive result. + VerificationPhaseInconclusive VerificationPhase = "Inconclusive" +) + +// IsTerminal returns true if the VerificationPhase is a terminal one. +func (v *VerificationPhase) IsTerminal() bool { + switch *v { + case VerificationPhaseSuccessful, VerificationPhaseFailed, + VerificationPhaseError, VerificationPhaseInconclusive: + return true + default: + return false + } +} + // +kubebuilder:validation:Enum={ImageAndTag,Tag,ImageAndDigest,Digest} type ImageUpdateValueType string @@ -485,7 +523,7 @@ type FreightReference struct { Charts []Chart `json:"charts,omitempty"` // VerificationInfo is information about any verification process that was // associated with this Freight for this Stage. - VerificationInfo *VerificationInfo `json:"verificationResult,omitempty"` + VerificationInfo *VerificationInfo `json:"verificationInfo,omitempty"` } type FreightReferenceStack []FreightReference @@ -673,7 +711,17 @@ type AnalysisRunArgument struct { // VerificationInfo contains information about the currently running // Verification process. type VerificationInfo struct { - AnalysisRun AnalysisRunReference `json:"analysisRun"` + // Phase describes the current phase of the Verification process. Generally, + // this will be a reflection of the underlying AnalysisRun's phase, however, + // there are exceptions to this, such as in the case where an AnalysisRun + // cannot be launched successfully. + Phase VerificationPhase `json:"phase,omitempty"` + // Message may contain additional information about why the verification + // process is in its current phase. + Message string `json:"message,omitempty"` + // AnalysisRun is a reference to the Argo Rollouts AnalysisRun that implements + // the Verification process. + AnalysisRun *AnalysisRunReference `json:"analysisRun,omitempty"` } // AnalysisRunReference is a reference to an AnalysisRun. From 4f7ac7e37699870a130778f272856cca39aa45ff Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Tue, 9 Jan 2024 15:51:24 -0500 Subject: [PATCH 02/10] corresponding protobuf changes Signed-off-by: Kent Rancourt --- api/v1alpha1/types.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/v1alpha1/types.proto b/api/v1alpha1/types.proto index 02e4bd8c5..4b3122563 100644 --- a/api/v1alpha1/types.proto +++ b/api/v1alpha1/types.proto @@ -313,6 +313,8 @@ message AnalysisRunArgument { message VerificationInfo { AnalysisRunReference analysis_run = 1 [json_name = "analysisRun"]; + string phase = 2 [json_name = "phase"]; + string message = 3 [json_name = "message"]; } message AnalysisRunReference { From 18f373cf72d3fb7964dcc39519a0c58ae5afadf5 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Tue, 9 Jan 2024 15:53:34 -0500 Subject: [PATCH 03/10] run codegen Signed-off-by: Kent Rancourt --- api/v1alpha1/zz_generated.deepcopy.go | 8 +- charts/kargo/README.md | 3 + charts/kargo/crds/kargo.akuity.io_stages.yaml | 56 +++++++++--- pkg/api/v1alpha1/types.pb.go | 87 +++++++++++-------- .../stages.kargo.akuity.io_v1alpha1.json | 45 ++++++---- ui/src/gen/v1alpha1/types_pb.ts | 12 +++ 6 files changed, 147 insertions(+), 64 deletions(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 65cb39cba..c98a86b4c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -390,7 +390,7 @@ func (in *FreightReference) DeepCopyInto(out *FreightReference) { if in.VerificationInfo != nil { in, out := &in.VerificationInfo, &out.VerificationInfo *out = new(VerificationInfo) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -1264,7 +1264,11 @@ func (in *Verification) DeepCopy() *Verification { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VerificationInfo) DeepCopyInto(out *VerificationInfo) { *out = *in - out.AnalysisRun = in.AnalysisRun + if in.AnalysisRun != nil { + in, out := &in.AnalysisRun, &out.AnalysisRun + *out = new(AnalysisRunReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VerificationInfo. diff --git a/charts/kargo/README.md b/charts/kargo/README.md index 2d5e101c4..e2642a956 100644 --- a/charts/kargo/README.md +++ b/charts/kargo/README.md @@ -89,6 +89,7 @@ the Kargo controller is running. | `api.oidc.dex.tolerations` | Tolerations for Dex server pods. | `[]` | | `api.oidc.dex.affinity` | Specifies pod affinity for the Dex server pods. | `{}` | | `api.argocd.urls` | Mapping of Argo CD shards names to URLs to support deep links to Argo CD URLs. If sharding is not used, map the empty string to the single Argo CD URL. | `nil` | +| `api.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. | `true` | ### Controller @@ -97,9 +98,11 @@ the Kargo controller is running. | `controller.enabled` | Whether the controller is enabled. | `true` | | `controller.globalCredentials.namespaces` | List of namespaces to look for shared credentials. | `[]` | | `controller.shardName` | Set a shard name only if you are running multiple controllers backed by a single underlying control plane. Setting a shard name will cause this controller to operate **only** on resources with a matching shard name. Leaving the shard name undefined will designate this controller as the default controller that is responsible exclusively for resources that are **not** assigned to a specific shard. Leaving this undefined is the correct choice when you are not using sharding at all. It is also the correct setting if you are using sharding and want to designate a controller as the default for handling resources not assigned to a specific shard. In most cases, this setting should simply be left alone. | `undefined` | +| `controller.argocd.integrationEnabled` | Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. | `true` | | `controller.argocd.namespace` | The namespace into which Argo CD is installed. | `argocd` | | `controller.argocd.watchArgocdNamespaceOnly` | Specifies whether the reconciler that watches Argo CD Applications for the sake of forcing related Stages to reconcile should only watch Argo CD Application resources residing in Argo CD's own namespace. Note: Older versions of Argo CD only supported Argo CD Application resources in Argo CD's own namespace, but newer versions support Argo CD Application resources in any namespace. This should usually be left as `false`. | `false` | | `controller.argocd.enableCredentialBorrowing` | Specifies whether Kargo may borrow repository credentials (specially formatted and specially annotated Secrets) from Argo CD. | `true` | +| `controller.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. | `true` | | `controller.logLevel` | The log level for the controller. | `INFO` | | `controller.resources` | Resources limits and requests for the controller containers. | `{}` | | `controller.nodeSelector` | Node selector for controller pods. | `{}` | diff --git a/charts/kargo/crds/kargo.akuity.io_stages.yaml b/charts/kargo/crds/kargo.akuity.io_stages.yaml index 16a036b93..9d46cbb73 100644 --- a/charts/kargo/crds/kargo.akuity.io_stages.yaml +++ b/charts/kargo/crds/kargo.akuity.io_stages.yaml @@ -602,12 +602,13 @@ spec: type: string type: object type: array - verificationResult: + verificationInfo: description: VerificationInfo is information about any verification process that was associated with this Freight for this Stage. properties: analysisRun: - description: AnalysisRunReference is a reference to an AnalysisRun. + description: AnalysisRun is a reference to the Argo Rollouts + AnalysisRun that implements the Verification process. properties: name: description: Name is the name of the AnalysisRun. @@ -624,8 +625,17 @@ spec: - namespace - phase type: object - required: - - analysisRun + message: + description: Message may contain additional information about + why the verification process is in its current phase. + type: string + phase: + description: Phase describes the current phase of the Verification + process. Generally, this will be a reflection of the underlying + AnalysisRun's phase, however, there are exceptions to this, + such as in the case where an AnalysisRun cannot be launched + successfully. + type: string type: object type: object currentPromotion: @@ -724,13 +734,13 @@ spec: type: string type: object type: array - verificationResult: + verificationInfo: description: VerificationInfo is information about any verification process that was associated with this Freight for this Stage. properties: analysisRun: - description: AnalysisRunReference is a reference to an - AnalysisRun. + description: AnalysisRun is a reference to the Argo Rollouts + AnalysisRun that implements the Verification process. properties: name: description: Name is the name of the AnalysisRun. @@ -747,8 +757,18 @@ spec: - namespace - phase type: object - required: - - analysisRun + message: + description: Message may contain additional information + about why the verification process is in its current + phase. + type: string + phase: + description: Phase describes the current phase of the + Verification process. Generally, this will be a reflection + of the underlying AnalysisRun's phase, however, there + are exceptions to this, such as in the case where an + AnalysisRun cannot be launched successfully. + type: string type: object type: object name: @@ -915,12 +935,13 @@ spec: type: string type: object type: array - verificationResult: + verificationInfo: description: VerificationInfo is information about any verification process that was associated with this Freight for this Stage. properties: analysisRun: - description: AnalysisRunReference is a reference to an AnalysisRun. + description: AnalysisRun is a reference to the Argo Rollouts + AnalysisRun that implements the Verification process. properties: name: description: Name is the name of the AnalysisRun. @@ -937,8 +958,17 @@ spec: - namespace - phase type: object - required: - - analysisRun + message: + description: Message may contain additional information + about why the verification process is in its current phase. + type: string + phase: + description: Phase describes the current phase of the Verification + process. Generally, this will be a reflection of the underlying + AnalysisRun's phase, however, there are exceptions to + this, such as in the case where an AnalysisRun cannot + be launched successfully. + type: string type: object type: object type: array diff --git a/pkg/api/v1alpha1/types.pb.go b/pkg/api/v1alpha1/types.pb.go index 42f150b1d..fff3c072c 100644 --- a/pkg/api/v1alpha1/types.pb.go +++ b/pkg/api/v1alpha1/types.pb.go @@ -3236,6 +3236,8 @@ type VerificationInfo struct { unknownFields protoimpl.UnknownFields AnalysisRun *AnalysisRunReference `protobuf:"bytes,1,opt,name=analysis_run,json=analysisRun,proto3" json:"analysis_run,omitempty"` + Phase string `protobuf:"bytes,2,opt,name=phase,proto3" json:"phase,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` } func (x *VerificationInfo) Reset() { @@ -3277,6 +3279,20 @@ func (x *VerificationInfo) GetAnalysisRun() *AnalysisRunReference { return nil } +func (x *VerificationInfo) GetPhase() string { + if x != nil { + return x.Phase + } + return "" +} + +func (x *VerificationInfo) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + type AnalysisRunReference struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3978,40 +3994,43 @@ var file_v1alpha1_types_proto_rawDesc = []byte{ 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x10, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x61, 0x0a, 0x0c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, - 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, - 0x52, 0x75, 0x6e, 0x22, 0x5e, 0x0a, 0x14, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, - 0x75, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, - 0x61, 0x73, 0x65, 0x42, 0xad, 0x02, 0x0a, 0x2c, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, - 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, - 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2f, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x06, 0x47, - 0x43, 0x41, 0x4b, 0x50, 0x41, 0xaa, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x43, - 0x6f, 0x6d, 0x2e, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x2e, - 0x50, 0x6b, 0x67, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0xca, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b, - 0x75, 0x69, 0x74, 0x79, 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41, - 0x70, 0x69, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x34, 0x47, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x5c, - 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0xea, 0x02, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, - 0x3a, 0x3a, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x3a, 0x3a, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x3a, - 0x3a, 0x50, 0x6b, 0x67, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xa5, 0x01, 0x0a, + 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x61, 0x0a, 0x0c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, + 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x52, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, + 0x73, 0x52, 0x75, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x5e, 0x0a, 0x14, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, + 0x52, 0x75, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x68, 0x61, 0x73, 0x65, 0x42, 0xad, 0x02, 0x0a, 0x2c, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, + 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2f, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x06, + 0x47, 0x43, 0x41, 0x4b, 0x50, 0x41, 0xaa, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x43, 0x6f, 0x6d, 0x2e, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x4b, 0x61, 0x72, 0x67, 0x6f, + 0x2e, 0x50, 0x6b, 0x67, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0xca, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, + 0x6b, 0x75, 0x69, 0x74, 0x79, 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, + 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x34, 0x47, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, + 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x3a, 0x3a, 0x43, 0x6f, + 0x6d, 0x3a, 0x3a, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x3a, 0x3a, 0x4b, 0x61, 0x72, 0x67, 0x6f, + 0x3a, 0x3a, 0x50, 0x6b, 0x67, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json index d5bbe9aaf..3ce5578cd 100644 --- a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json +++ b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json @@ -533,11 +533,11 @@ }, "type": "array" }, - "verificationResult": { + "verificationInfo": { "description": "VerificationInfo is information about any verification process that was associated with this Freight for this Stage.", "properties": { "analysisRun": { - "description": "AnalysisRunReference is a reference to an AnalysisRun.", + "description": "AnalysisRun is a reference to the Argo Rollouts AnalysisRun that implements the Verification process.", "properties": { "name": { "description": "Name is the name of the AnalysisRun.", @@ -558,11 +558,16 @@ "phase" ], "type": "object" + }, + "message": { + "description": "Message may contain additional information about why the verification process is in its current phase.", + "type": "string" + }, + "phase": { + "description": "Phase describes the current phase of the Verification process. Generally, this will be a reflection of the underlying AnalysisRun's phase, however, there are exceptions to this, such as in the case where an AnalysisRun cannot be launched successfully.", + "type": "string" } }, - "required": [ - "analysisRun" - ], "type": "object" } }, @@ -660,11 +665,11 @@ }, "type": "array" }, - "verificationResult": { + "verificationInfo": { "description": "VerificationInfo is information about any verification process that was associated with this Freight for this Stage.", "properties": { "analysisRun": { - "description": "AnalysisRunReference is a reference to an AnalysisRun.", + "description": "AnalysisRun is a reference to the Argo Rollouts AnalysisRun that implements the Verification process.", "properties": { "name": { "description": "Name is the name of the AnalysisRun.", @@ -685,11 +690,16 @@ "phase" ], "type": "object" + }, + "message": { + "description": "Message may contain additional information about why the verification process is in its current phase.", + "type": "string" + }, + "phase": { + "description": "Phase describes the current phase of the Verification process. Generally, this will be a reflection of the underlying AnalysisRun's phase, however, there are exceptions to this, such as in the case where an AnalysisRun cannot be launched successfully.", + "type": "string" } }, - "required": [ - "analysisRun" - ], "type": "object" } }, @@ -876,11 +886,11 @@ }, "type": "array" }, - "verificationResult": { + "verificationInfo": { "description": "VerificationInfo is information about any verification process that was associated with this Freight for this Stage.", "properties": { "analysisRun": { - "description": "AnalysisRunReference is a reference to an AnalysisRun.", + "description": "AnalysisRun is a reference to the Argo Rollouts AnalysisRun that implements the Verification process.", "properties": { "name": { "description": "Name is the name of the AnalysisRun.", @@ -901,11 +911,16 @@ "phase" ], "type": "object" + }, + "message": { + "description": "Message may contain additional information about why the verification process is in its current phase.", + "type": "string" + }, + "phase": { + "description": "Phase describes the current phase of the Verification process. Generally, this will be a reflection of the underlying AnalysisRun's phase, however, there are exceptions to this, such as in the case where an AnalysisRun cannot be launched successfully.", + "type": "string" } }, - "required": [ - "analysisRun" - ], "type": "object" } }, diff --git a/ui/src/gen/v1alpha1/types_pb.ts b/ui/src/gen/v1alpha1/types_pb.ts index 878771eb5..4fa45e322 100644 --- a/ui/src/gen/v1alpha1/types_pb.ts +++ b/ui/src/gen/v1alpha1/types_pb.ts @@ -2516,6 +2516,16 @@ export class VerificationInfo extends Message { */ analysisRun?: AnalysisRunReference; + /** + * @generated from field: string phase = 2; + */ + phase = ""; + + /** + * @generated from field: string message = 3; + */ + message = ""; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -2525,6 +2535,8 @@ export class VerificationInfo extends Message { static readonly typeName = "github.com.akuity.kargo.pkg.api.v1alpha1.VerificationInfo"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "analysis_run", kind: "message", T: AnalysisRunReference }, + { no: 2, name: "phase", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): VerificationInfo { From 1e0b661125fe7ed0624e02d26eabe5cadbc65d93 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Tue, 9 Jan 2024 15:55:31 -0500 Subject: [PATCH 04/10] corresponding type conversion changes Signed-off-by: Kent Rancourt --- internal/api/types/v1alpha1/types.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/api/types/v1alpha1/types.go b/internal/api/types/v1alpha1/types.go index d65d2f598..883e15ceb 100644 --- a/internal/api/types/v1alpha1/types.go +++ b/internal/api/types/v1alpha1/types.go @@ -143,7 +143,7 @@ func FromFreightReferenceProto(s *v1alpha1.FreightReference) *kargoapi.FreightRe Commits: commits, Images: images, Charts: charts, - VerificationInfo: FromVerificationInfo(s.VerificationInfo), + VerificationInfo: FromVerificationInfoProto(s.VerificationInfo), } } @@ -654,13 +654,16 @@ func FromAnalysisRunArgumentProto( } } -func FromVerificationInfo(v *v1alpha1.VerificationInfo) *kargoapi.VerificationInfo { +func FromVerificationInfoProto(v *v1alpha1.VerificationInfo) *kargoapi.VerificationInfo { if v == nil { return nil } - k := &kargoapi.VerificationInfo{} + k := &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhase(v.Phase), + Message: v.Message, + } if v.AnalysisRun != nil { - k.AnalysisRun = *FromAnalysisRunReferenceProto(v.AnalysisRun) + k.AnalysisRun = FromAnalysisRunReferenceProto(v.AnalysisRun) } return k } @@ -1227,7 +1230,9 @@ func ToVerificationInfoProto(v *kargoapi.VerificationInfo) *v1alpha1.Verificatio return nil } return &v1alpha1.VerificationInfo{ - AnalysisRun: ToAnalysisRunReferenceProto(&v.AnalysisRun), + Phase: string(v.Phase), + Message: v.Message, + AnalysisRun: ToAnalysisRunReferenceProto(v.AnalysisRun), } } From 294c2ac1e9b9ed8871651fb0cbee5d13889609f5 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Mon, 8 Jan 2024 19:00:10 -0500 Subject: [PATCH 05/10] corresponding api server changes Signed-off-by: Kent Rancourt --- cmd/controlplane/api.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/controlplane/api.go b/cmd/controlplane/api.go index 49e5c6d02..81ce7f75d 100644 --- a/cmd/controlplane/api.go +++ b/cmd/controlplane/api.go @@ -19,8 +19,10 @@ import ( "github.com/akuity/kargo/internal/api" "github.com/akuity/kargo/internal/api/config" "github.com/akuity/kargo/internal/api/kubernetes" + rollouts "github.com/akuity/kargo/internal/controller/rollouts/api/v1alpha1" "github.com/akuity/kargo/internal/kubeclient" "github.com/akuity/kargo/internal/os" + "github.com/akuity/kargo/internal/types" versionpkg "github.com/akuity/kargo/internal/version" ) @@ -156,6 +158,11 @@ func newSchemeForAPI() (*runtime.Scheme, error) { if err := kubescheme.AddToScheme(scheme); err != nil { return nil, errors.Wrap(err, "add Kubernetes api to scheme") } + if types.MustParseBool(os.GetEnv("ROLLOUTS_INTEGRATION_ENABLED", "true")) { + if err := rollouts.AddToScheme(scheme); err != nil { + return nil, errors.Wrap(err, "add argo rollouts api to scheme") + } + } if err := kargoapi.AddToScheme(scheme); err != nil { return nil, errors.Wrap(err, "add kargo api to scheme") } From e71ffc57affb33a0ce7e9f171facd2ff43575815 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Tue, 9 Jan 2024 17:18:39 -0500 Subject: [PATCH 06/10] corresponding controller changes Signed-off-by: Kent Rancourt --- cmd/controlplane/controller.go | 68 +++--- .../controller/applications/applications.go | 4 +- internal/controller/promotion/argocd.go | 27 ++- internal/controller/promotion/argocd_test.go | 28 +++ internal/controller/promotion/root.go | 4 +- internal/controller/promotions/promotions.go | 13 +- internal/controller/stages/health.go | 9 + internal/controller/stages/health_test.go | 221 ++++++++++-------- internal/controller/stages/stages.go | 70 +++--- internal/controller/stages/stages_test.go | 61 +++-- internal/controller/stages/verification.go | 140 +++++++---- .../controller/stages/verification_test.go | 120 ++++++---- internal/credentials/credentials.go | 20 +- internal/credentials/credentials_test.go | 2 +- internal/kubeclient/indexer_test.go | 4 +- 15 files changed, 481 insertions(+), 310 deletions(-) diff --git a/cmd/controlplane/controller.go b/cmd/controlplane/controller.go index 7f457a5bd..6baafc0ad 100644 --- a/cmd/controlplane/controller.go +++ b/cmd/controlplane/controller.go @@ -106,7 +106,7 @@ func newControllerCommand() *cobra.Command { } var argocdMgr manager.Manager - { + if types.MustParseBool(os.GetEnv("ARGOCD_INTEGRATION_ENABLED", "true")) { // If the env var is undefined, this will resolve to kubeconfig for the // cluster the controller is running in. // @@ -169,7 +169,7 @@ func newControllerCommand() *cobra.Command { } var rolloutsMgr manager.Manager - { + if types.MustParseBool(os.GetEnv("ROLLOUTS_INTEGRATION_ENABLED", "true")) { // If the env var is undefined, this will resolve to kubeconfig for the // cluster the controller is running in. // @@ -245,22 +245,26 @@ func newControllerCommand() *cobra.Command { credentials.KubernetesDatabaseConfigFromEnv(), ) - if err := analysis.SetupReconcilerWithManager( - ctx, - kargoMgr, - rolloutsMgr, - shardName, - ); err != nil { - return errors.Wrap(err, "error setting up AnalysisRuns reconciler") + if rolloutsMgr != nil { + if err := analysis.SetupReconcilerWithManager( + ctx, + kargoMgr, + rolloutsMgr, + shardName, + ); err != nil { + return errors.Wrap(err, "error setting up AnalysisRuns reconciler") + } } - if err := applications.SetupReconcilerWithManager( - ctx, - kargoMgr, - argocdMgr, - shardName, - ); err != nil { - return errors.Wrap(err, "error setting up Applications reconciler") + if argocdMgr != nil { + if err := applications.SetupReconcilerWithManager( + ctx, + kargoMgr, + argocdMgr, + shardName, + ); err != nil { + return errors.Wrap(err, "error setting up Applications reconciler") + } } if err := promotions.SetupReconcilerWithManager( @@ -295,13 +299,15 @@ func newControllerCommand() *cobra.Command { wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - if err := argocdMgr.Start(ctx); err != nil { - errChan <- errors.Wrap(err, "error starting argo cd manager") - } - }() + if argocdMgr != nil { + wg.Add(1) + go func() { + defer wg.Done() + if err := argocdMgr.Start(ctx); err != nil { + errChan <- errors.Wrap(err, "error starting argo cd manager") + } + }() + } wg.Add(1) go func() { @@ -311,13 +317,15 @@ func newControllerCommand() *cobra.Command { } }() - wg.Add(1) - go func() { - defer wg.Done() - if err := rolloutsMgr.Start(ctx); err != nil { - errChan <- errors.Wrap(err, "error starting rollouts manager") - } - }() + if rolloutsMgr != nil { + wg.Add(1) + go func() { + defer wg.Done() + if err := rolloutsMgr.Start(ctx); err != nil { + errChan <- errors.Wrap(err, "error starting rollouts manager") + } + }() + } // Adapt wg to a channel that can be used in a select doneCh := make(chan struct{}) diff --git a/internal/controller/applications/applications.go b/internal/controller/applications/applications.go index fb074aa22..ddd32e4bd 100644 --- a/internal/controller/applications/applications.go +++ b/internal/controller/applications/applications.go @@ -32,7 +32,7 @@ type reconciler struct { func SetupReconcilerWithManager( ctx context.Context, kargoMgr manager.Manager, - argoMgr manager.Manager, + argocdMgr manager.Manager, shardName string, ) error { // Index Stages by Argo CD Applications @@ -40,7 +40,7 @@ func SetupReconcilerWithManager( return errors.Wrap(err, "index Stages by Argo CD Applications") } logger := logging.LoggerFromContext(ctx) - return ctrl.NewControllerManagedBy(argoMgr). + return ctrl.NewControllerManagedBy(argocdMgr). For(&argocd.Application{}). WithEventFilter(AppHealthSyncStatusChangePredicate{logger: logger}). WithOptions(controller.CommonOptions()). diff --git a/internal/controller/promotion/argocd.go b/internal/controller/promotion/argocd.go index c63a539fe..28b110751 100644 --- a/internal/controller/promotion/argocd.go +++ b/internal/controller/promotion/argocd.go @@ -20,6 +20,7 @@ const authorizedStageAnnotationKey = "kargo.akuity.io/authorized-stage" // argoCDMechanism is an implementation of the Mechanism interface that updates // Argo CD Application resources. type argoCDMechanism struct { + argocdClient client.Client // These behaviors are overridable for testing purposes: doSingleUpdateFn func( ctx context.Context, @@ -47,14 +48,16 @@ type argoCDMechanism struct { // newArgoCDMechanism returns an implementation of the Mechanism interface that // updates Argo CD Application resources. -func newArgoCDMechanism( - argoClient client.Client, -) Mechanism { - a := &argoCDMechanism{} +func newArgoCDMechanism(argocdClient client.Client) Mechanism { + a := &argoCDMechanism{ + argocdClient: argocdClient, + } a.doSingleUpdateFn = a.doSingleUpdate - a.getArgoCDAppFn = getApplicationFn(argoClient) + a.getArgoCDAppFn = getApplicationFn(argocdClient) a.applyArgoCDSourceUpdateFn = applyArgoCDSourceUpdate - a.argoCDAppPatchFn = argoClient.Patch + if argocdClient != nil { + a.argoCDAppPatchFn = argocdClient.Patch + } return a } @@ -76,6 +79,14 @@ func (a *argoCDMechanism) Promote( return promo.Status.WithPhase(kargoapi.PromotionPhaseSucceeded), newFreight, nil } + if a.argocdClient == nil { + return promo.Status.WithPhase(kargoapi.PromotionPhaseFailed), newFreight, + errors.New( + "Argo CD integration is disabled on this controller; cannot perform " + + "promotion", + ) + } + logger := logging.LoggerFromContext(ctx) logger.Debug("executing Argo CD-based promotion mechanisms") @@ -202,7 +213,7 @@ func (a *argoCDMechanism) doSingleUpdate( } func getApplicationFn( - argoClient client.Client, + argocdClient client.Client, ) func( ctx context.Context, namespace string, @@ -213,7 +224,7 @@ func getApplicationFn( namespace string, name string, ) (*argocd.Application, error) { - return argocd.GetApplication(ctx, argoClient, namespace, name) + return argocd.GetApplication(ctx, argocdClient, namespace, name) } } diff --git a/internal/controller/promotion/argocd_test.go b/internal/controller/promotion/argocd_test.go index 27f97bcc8..af6aa6f4a 100644 --- a/internal/controller/promotion/argocd_test.go +++ b/internal/controller/promotion/argocd_test.go @@ -61,9 +61,36 @@ func TestArgoCDPromote(t *testing.T) { require.Equal(t, newFreightIn, newFreightOut) }, }, + { + name: "argo cd integration disabled", + promoMech: &argoCDMechanism{}, + stage: &kargoapi.Stage{ + Spec: &kargoapi.StageSpec{ + PromotionMechanisms: &kargoapi.PromotionMechanisms{ + ArgoCDAppUpdates: []kargoapi.ArgoCDAppUpdate{ + {}, + }, + }, + }, + }, + assertions: func( + newStatus *kargoapi.PromotionStatus, + newFreightIn kargoapi.FreightReference, + newFreightOut kargoapi.FreightReference, + err error, + ) { + require.Error(t, err) + require.Contains( + t, + err.Error(), + "Argo CD integration is disabled on this controller", + ) + }, + }, { name: "error applying update", promoMech: &argoCDMechanism{ + argocdClient: fake.NewClientBuilder().Build(), doSingleUpdateFn: func( context.Context, metav1.ObjectMeta, @@ -100,6 +127,7 @@ func TestArgoCDPromote(t *testing.T) { { name: "success", promoMech: &argoCDMechanism{ + argocdClient: fake.NewClientBuilder().Build(), doSingleUpdateFn: func( context.Context, metav1.ObjectMeta, diff --git a/internal/controller/promotion/root.go b/internal/controller/promotion/root.go index d49591e1f..703e66883 100644 --- a/internal/controller/promotion/root.go +++ b/internal/controller/promotion/root.go @@ -27,7 +27,7 @@ type Mechanism interface { // NewMechanisms returns the entrypoint to a hierarchical tree of promotion // mechanisms. func NewMechanisms( - argoClient client.Client, + argocdClient client.Client, credentialsDB credentials.Database, ) Mechanism { return newCompositeMechanism( @@ -39,6 +39,6 @@ func NewMechanisms( newKustomizeMechanism(credentialsDB), newHelmMechanism(credentialsDB), ), - newArgoCDMechanism(argoClient), + newArgoCDMechanism(argocdClient), ) } diff --git a/internal/controller/promotions/promotions.go b/internal/controller/promotions/promotions.go index 57bd68ded..146fecfcd 100644 --- a/internal/controller/promotions/promotions.go +++ b/internal/controller/promotions/promotions.go @@ -43,7 +43,7 @@ type reconciler struct { func SetupReconcilerWithManager( ctx context.Context, kargoMgr manager.Manager, - argoMgr manager.Manager, + argocdMgr manager.Manager, credentialsDB credentials.Database, shardName string, ) error { @@ -53,9 +53,14 @@ func SetupReconcilerWithManager( return errors.Wrap(err, "error creating shard selector predicate") } + var argocdClient client.Client + if argocdMgr != nil { + argocdClient = argocdMgr.GetClient() + } + reconciler := newReconciler( kargoMgr.GetClient(), - argoMgr.GetClient(), + argocdClient, credentialsDB, ) @@ -99,7 +104,7 @@ func SetupReconcilerWithManager( func newReconciler( kargoClient client.Client, - argoClient client.Client, + argocdClient client.Client, credentialsDB credentials.Database, ) *reconciler { pqs := promoQueues{ @@ -110,7 +115,7 @@ func newReconciler( kargoClient: kargoClient, pqs: &pqs, promoMechanisms: promotion.NewMechanisms( - argoClient, + argocdClient, credentialsDB, ), } diff --git a/internal/controller/stages/health.go b/internal/controller/stages/health.go index 3aa407bfc..86b2f9508 100644 --- a/internal/controller/stages/health.go +++ b/internal/controller/stages/health.go @@ -24,6 +24,15 @@ func (r *reconciler) checkHealth( Issues: []string{}, } + if r.argocdClient == nil && len(argoCDAppUpdates) > 0 { + h.Status = kargoapi.HealthStateUnknown + h.Issues = []string{ + "Argo CD integration is disabled on this controller; cannot assess" + + " the health or sync status of Argo CD Applications", + } + return &h + } + for i, updates := range argoCDAppUpdates { h.ArgoCDApps[i] = kargoapi.ArgoCDAppStatus{ Namespace: updates.AppNamespaceOrDefault(), diff --git a/internal/controller/stages/health_test.go b/internal/controller/stages/health_test.go index de451f1aa..c3d3ea3f8 100644 --- a/internal/controller/stages/health_test.go +++ b/internal/controller/stages/health_test.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/require" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" kargoapi "github.com/akuity/kargo/api/v1alpha1" argocd "github.com/akuity/kargo/internal/controller/argocd/api/v1alpha1" @@ -17,20 +18,25 @@ func TestCheckHealth(t *testing.T) { name string freight kargoapi.FreightReference argoCDAppUpdates []kargoapi.ArgoCDAppUpdate - getArgoCDAppFn func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) - assertions func(*kargoapi.Health) + reconciler *reconciler + assertions func(*kargoapi.Health) }{ { - name: "no argoCDAppUpdates are defined", + name: "no argoCDAppUpdates are defined", + reconciler: &reconciler{}, assertions: func(health *kargoapi.Health) { require.Nil(t, health) }, }, + { + name: "argo cd integration is not enabled", + argoCDAppUpdates: []kargoapi.ArgoCDAppUpdate{{}}, + reconciler: &reconciler{}, + assertions: func(health *kargoapi.Health) { + require.NotNil(t, health) + require.Equal(t, kargoapi.HealthStateUnknown, health.Status) + }, + }, { name: "error finding Argo CD App", argoCDAppUpdates: []kargoapi.ArgoCDAppUpdate{ @@ -39,13 +45,16 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return nil, errors.New("something went wrong") + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return nil, errors.New("something went wrong") + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateUnknown, health.Status) @@ -87,13 +96,16 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return nil, nil + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return nil, nil + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateUnknown, health.Status) @@ -133,27 +145,30 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return &argocd.Application{ - Spec: argocd.ApplicationSpec{ - Sources: argocd.ApplicationSources{ - {}, - }, - }, - Status: argocd.ApplicationStatus{ - Health: argocd.HealthStatus{ - Status: argocd.HealthStatusHealthy, + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return &argocd.Application{ + Spec: argocd.ApplicationSpec{ + Sources: argocd.ApplicationSources{ + {}, + }, }, - Sync: argocd.SyncStatus{ - Status: argocd.SyncStatusCodeSynced, + Status: argocd.ApplicationStatus{ + Health: argocd.HealthStatus{ + Status: argocd.HealthStatusHealthy, + }, + Sync: argocd.SyncStatus{ + Status: argocd.SyncStatusCodeSynced, + }, }, - }, - }, nil + }, nil + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateUnknown, health.Status) @@ -191,22 +206,25 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return &argocd.Application{ - Status: argocd.ApplicationStatus{ - Health: argocd.HealthStatus{ - Status: argocd.HealthStatusDegraded, - }, - Sync: argocd.SyncStatus{ - Status: argocd.SyncStatusCodeSynced, + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return &argocd.Application{ + Status: argocd.ApplicationStatus{ + Health: argocd.HealthStatus{ + Status: argocd.HealthStatusDegraded, + }, + Sync: argocd.SyncStatus{ + Status: argocd.SyncStatusCodeSynced, + }, }, - }, - }, nil + }, nil + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateUnhealthy, health.Status) @@ -248,28 +266,31 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return &argocd.Application{ - Spec: argocd.ApplicationSpec{ - Source: &argocd.ApplicationSource{ - RepoURL: "fake-url", - }, - }, - Status: argocd.ApplicationStatus{ - Health: argocd.HealthStatus{ - Status: argocd.HealthStatusHealthy, + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return &argocd.Application{ + Spec: argocd.ApplicationSpec{ + Source: &argocd.ApplicationSource{ + RepoURL: "fake-url", + }, }, - Sync: argocd.SyncStatus{ - Status: argocd.SyncStatusCodeSynced, - Revision: "not-the-right-commit", + Status: argocd.ApplicationStatus{ + Health: argocd.HealthStatus{ + Status: argocd.HealthStatusHealthy, + }, + Sync: argocd.SyncStatus{ + Status: argocd.SyncStatusCodeSynced, + Revision: "not-the-right-commit", + }, }, - }, - }, nil + }, nil + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateUnhealthy, health.Status) @@ -311,28 +332,31 @@ func TestCheckHealth(t *testing.T) { AppNamespace: "fake-namespace", }, }, - getArgoCDAppFn: func( - context.Context, - client.Client, - string, - string, - ) (*argocd.Application, error) { - return &argocd.Application{ - Spec: argocd.ApplicationSpec{ - Source: &argocd.ApplicationSource{ - RepoURL: "fake-url", - }, - }, - Status: argocd.ApplicationStatus{ - Health: argocd.HealthStatus{ - Status: argocd.HealthStatusHealthy, + reconciler: &reconciler{ + argocdClient: fake.NewClientBuilder().Build(), + getArgoCDAppFn: func( + context.Context, + client.Client, + string, + string, + ) (*argocd.Application, error) { + return &argocd.Application{ + Spec: argocd.ApplicationSpec{ + Source: &argocd.ApplicationSource{ + RepoURL: "fake-url", + }, }, - Sync: argocd.SyncStatus{ - Status: argocd.SyncStatusCodeSynced, - Revision: "fake-commit", + Status: argocd.ApplicationStatus{ + Health: argocd.HealthStatus{ + Status: argocd.HealthStatusHealthy, + }, + Sync: argocd.SyncStatus{ + Status: argocd.SyncStatusCodeSynced, + Revision: "fake-commit", + }, }, - }, - }, nil + }, nil + }, }, assertions: func(health *kargoapi.Health) { require.Equal(t, kargoapi.HealthStateHealthy, health.Status) @@ -359,11 +383,8 @@ func TestCheckHealth(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - reconciler := &reconciler{ - getArgoCDAppFn: testCase.getArgoCDAppFn, - } testCase.assertions( - reconciler.checkHealth( + testCase.reconciler.checkHealth( context.Background(), testCase.freight, testCase.argoCDAppUpdates, diff --git a/internal/controller/stages/stages.go b/internal/controller/stages/stages.go index 42963b1ec..90ee154dd 100644 --- a/internal/controller/stages/stages.go +++ b/internal/controller/stages/stages.go @@ -70,12 +70,12 @@ type reconciler struct { startVerificationFn func( context.Context, *kargoapi.Stage, - ) (*kargoapi.VerificationInfo, error) + ) *kargoapi.VerificationInfo getVerificationInfoFn func( context.Context, *kargoapi.Stage, - ) (*kargoapi.VerificationInfo, error) + ) *kargoapi.VerificationInfo getAnalysisTemplateFn func( context.Context, @@ -247,6 +247,14 @@ func SetupReconcilerWithManager( return errors.Wrap(err, "error creating shard predicate") } + var argocdClient, rolloutsClient client.Client + if argocdMgr != nil { + argocdClient = argocdMgr.GetClient() + } + if rolloutsMgr != nil { + rolloutsClient = rolloutsMgr.GetClient() + } + c, err := ctrl.NewControllerManagedBy(kargoMgr). For(&kargoapi.Stage{}). WithEventFilter( @@ -269,8 +277,8 @@ func SetupReconcilerWithManager( Build( newReconciler( kargoMgr.GetClient(), - argocdMgr.GetClient(), - rolloutsMgr.GetClient(), + argocdClient, + rolloutsClient, shardName, ), ) @@ -367,7 +375,9 @@ func newReconciler( r.getAnalysisTemplateFn = rollouts.GetAnalysisTemplate r.listAnalysisRunsFn = r.kargoClient.List r.buildAnalysisRunFn = r.buildAnalysisRun - r.createAnalysisRunFn = r.rolloutsClient.Create + if rolloutsClient != nil { + r.createAnalysisRunFn = r.rolloutsClient.Create + } r.getAnalysisRunFn = rollouts.GetAnalysisRun r.getFreightFn = kargoapi.GetFreight r.verifyFreightInStageFn = r.verifyFreightInStage @@ -613,45 +623,21 @@ func (r *reconciler) syncNormalStage( if status.Phase == kargoapi.StagePhaseVerifying && stage.Spec.Verification != nil { if status.CurrentFreight.VerificationInfo == nil { if status.Health == nil || status.Health.Status == kargoapi.HealthStateHealthy { - // Start verification - verInfo, err := r.startVerificationFn(ctx, stage) - if err != nil { - return status, errors.Wrapf( - err, - "error starting verification process for Stage %q and Freight %q in namespace %q", - stage.Name, - status.CurrentFreight.ID, - stage.Namespace, - ) - } - status.CurrentFreight.VerificationInfo = verInfo + log.Debug("starting verification") + status.CurrentFreight.VerificationInfo = r.startVerificationFn(ctx, stage) } } else { log.Debug("checking verification results") - verInfo, err := r.getVerificationInfoFn(ctx, stage) - if err != nil { - return status, errors.Wrapf( - err, - "error getting verification result for Stage %q and Freight %q in namespace %q", - stage.Name, - status.CurrentFreight.ID, - stage.Namespace, - ) - } - status.CurrentFreight.VerificationInfo = verInfo - switch rollouts.AnalysisPhase(status.CurrentFreight.VerificationInfo.AnalysisRun.Phase) { - case rollouts.AnalysisPhasePending: - log.Debug("verification is pending") - case rollouts.AnalysisPhaseRunning: - log.Debug("verification is running") - case rollouts.AnalysisPhaseSuccessful, - rollouts.AnalysisPhaseFailed, - rollouts.AnalysisPhaseError, - rollouts.AnalysisPhaseInconclusive: - // Verification is complete - status.Phase = kargoapi.StagePhaseSteady - log.Debug("verification is complete") - } + status.CurrentFreight.VerificationInfo = r.getVerificationInfoFn(ctx, stage) + } + log.Debugf( + "verification phase is %s", + status.CurrentFreight.VerificationInfo.Phase, + ) + if status.CurrentFreight.VerificationInfo.Phase.IsTerminal() { + // Verification is complete + status.Phase = kargoapi.StagePhaseSteady + log.Debug("verification is complete") } } @@ -663,7 +649,7 @@ func (r *reconciler) syncNormalStage( if (status.Health == nil || status.Health.Status == kargoapi.HealthStateHealthy) && (stage.Spec.Verification == nil || (status.CurrentFreight.VerificationInfo != nil && - status.CurrentFreight.VerificationInfo.AnalysisRun.Phase == string(rollouts.AnalysisPhaseSuccessful))) { + status.CurrentFreight.VerificationInfo.Phase == kargoapi.VerificationPhaseSuccessful)) { if err := r.verifyFreightInStageFn( ctx, stage.Namespace, diff --git a/internal/controller/stages/stages_test.go b/internal/controller/stages/stages_test.go index e7608e535..d8e3a845a 100644 --- a/internal/controller/stages/stages_test.go +++ b/internal/controller/stages/stages_test.go @@ -332,8 +332,11 @@ func TestSyncNormalStage(t *testing.T) { startVerificationFn: func( context.Context, *kargoapi.Stage, - ) (*kargoapi.VerificationInfo, error) { - return nil, errors.New("something went wrong") + ) *kargoapi.VerificationInfo { + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "something went wrong", + } }, }, assertions: func( @@ -341,10 +344,20 @@ func TestSyncNormalStage(t *testing.T) { newStatus kargoapi.StageStatus, err error, ) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error starting verification process") - // Status should be returned unchanged + require.NoError(t, err) + require.NotNil(t, newStatus.CurrentFreight) + require.Equal(t, kargoapi.StagePhaseSteady, newStatus.Phase) + require.Equal( + t, + &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "something went wrong", + }, + newStatus.CurrentFreight.VerificationInfo, + ) + // Everything else should be returned unchanged + newStatus.CurrentFreight.VerificationInfo = nil + newStatus.Phase = initialStatus.Phase require.Equal(t, initialStatus, newStatus) }, }, @@ -372,8 +385,11 @@ func TestSyncNormalStage(t *testing.T) { ) *kargoapi.Health { return nil }, - getVerificationInfoFn: func(ctx context.Context, s *kargoapi.Stage) (*kargoapi.VerificationInfo, error) { - return nil, errors.New("something went wrong") + getVerificationInfoFn: func(ctx context.Context, s *kargoapi.Stage) *kargoapi.VerificationInfo { + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "something went wrong", + } }, }, assertions: func( @@ -381,10 +397,21 @@ func TestSyncNormalStage(t *testing.T) { newStatus kargoapi.StageStatus, err error, ) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error getting verification result") - // Status should be returned unchanged + require.NoError(t, err) + require.NotNil(t, newStatus.CurrentFreight) + require.Equal( + t, + &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "something went wrong", + }, + newStatus.CurrentFreight.VerificationInfo, + ) + // Phase should be changed to Steady + require.Equal(t, kargoapi.StagePhaseSteady, newStatus.Phase) + // Everything else should be unchanged + newStatus.Phase = initialStatus.Phase + newStatus.CurrentFreight = initialStatus.CurrentFreight require.Equal(t, initialStatus, newStatus) }, }, @@ -862,14 +889,15 @@ func TestSyncNormalStage(t *testing.T) { getVerificationInfoFn: func( context.Context, *kargoapi.Stage, - ) (*kargoapi.VerificationInfo, error) { + ) *kargoapi.VerificationInfo { return &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhaseSuccessful, + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-analysis-run", Namespace: "fake-namespace", Phase: string(rollouts.AnalysisPhaseSuccessful), }, - }, nil + } }, verifyFreightInStageFn: func(context.Context, string, string, string) error { return nil @@ -921,7 +949,8 @@ func TestSyncNormalStage(t *testing.T) { require.Equal( t, &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhaseSuccessful, + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-analysis-run", Namespace: "fake-namespace", Phase: string(rollouts.AnalysisPhaseSuccessful), diff --git a/internal/controller/stages/verification.go b/internal/controller/stages/verification.go index e66e0d766..8c7a0f8da 100644 --- a/internal/controller/stages/verification.go +++ b/internal/controller/stages/verification.go @@ -20,7 +20,15 @@ import ( func (r *reconciler) startVerification( ctx context.Context, stage *kargoapi.Stage, -) (*kargoapi.VerificationInfo, error) { +) *kargoapi.VerificationInfo { + if r.rolloutsClient == nil { + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "Rollouts integration is disabled on this controller; " + + "cannot start verification", + } + } + logger := logging.LoggerFromContext(ctx) namespace := r.getAnalysisRunNamespace(stage) @@ -40,23 +48,27 @@ func (r *reconciler) startVerification( ), }, ); err != nil { - return nil, errors.Wrapf( - err, - "error listing AnalysisRuns for Stage %q and Freight %q in namespace %q", - stage.Name, - stage.Status.CurrentFreight.ID, - namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Wrapf( + err, + "error listing AnalysisRuns for Stage %q and Freight %q in namespace %q", + stage.Name, + stage.Status.CurrentFreight.ID, + namespace, + ).Error(), + } } if len(analysisRuns.Items) > 0 { logger.Debug("AnalysisRun already exists for Freight") return &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhase(analysisRuns.Items[0].Status.Phase), + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: analysisRuns.Items[0].Name, Namespace: analysisRuns.Items[0].Namespace, Phase: string(analysisRuns.Items[0].Status.Phase), }, - }, nil + } } ver := stage.Spec.Verification @@ -72,54 +84,75 @@ func (r *reconciler) startVerification( }, ) if err != nil { - return nil, errors.Wrapf( - err, - "error getting AnalysisTemplate %q in namespace %q", - templateRef.Name, - stage.Namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Wrapf( + err, + "error getting AnalysisTemplate %q in namespace %q", + templateRef.Name, + stage.Namespace, + ).Error(), + } } if template == nil { - return nil, errors.Errorf( - "AnalysisTemplate %q in namespace %q not found", - templateRef.Name, - stage.Namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Errorf( + "AnalysisTemplate %q in namespace %q not found", + templateRef.Name, + stage.Namespace, + ).Error(), + } } templates[i] = template } run, err := r.buildAnalysisRunFn(stage, templates) if err != nil { - return nil, errors.Wrapf( - err, - "error building AnalysisRun for Stage %q and Freight %q in namespace %q", - stage.Name, - stage.Status.CurrentFreight.ID, - stage.Namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Wrapf( + err, + "error building AnalysisRun for Stage %q and Freight %q in namespace %q", + stage.Name, + stage.Status.CurrentFreight.ID, + stage.Namespace, + ).Error(), + } } if err := r.createAnalysisRunFn(ctx, run); err != nil { - return nil, errors.Wrapf( - err, - "error creating AnalysisRun %q in namespace %q", - run.Name, - run.Namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Wrapf( + err, + "error creating AnalysisRun %q in namespace %q", + run.Name, + run.Namespace, + ).Error(), + } } return &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhasePending, + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: run.Name, Namespace: run.Namespace, }, - }, nil + } } func (r *reconciler) getVerificationInfo( ctx context.Context, stage *kargoapi.Stage, -) (*kargoapi.VerificationInfo, error) { +) *kargoapi.VerificationInfo { + if r.rolloutsClient == nil { + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: "Rollouts integration is disabled on this controller; cannot " + + "get verification info", + } + } + namespace := r.getAnalysisRunNamespace(stage) analysisRunName := stage.Status.CurrentFreight.VerificationInfo.AnalysisRun.Name analysisRun, err := r.getAnalysisRunFn( @@ -131,27 +164,34 @@ func (r *reconciler) getVerificationInfo( }, ) if err != nil { - return nil, errors.Wrapf( - err, - "error getting AnalysisRun %q in namespace %q", - analysisRunName, - namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Wrapf( + err, + "error getting AnalysisRun %q in namespace %q", + analysisRunName, + namespace, + ).Error(), + } } if analysisRun == nil { - return nil, errors.Errorf( - "AnalysisRun %q in namespace %q not found", - analysisRunName, - namespace, - ) + return &kargoapi.VerificationInfo{ + Phase: kargoapi.VerificationPhaseError, + Message: errors.Errorf( + "AnalysisRun %q in namespace %q not found", + analysisRunName, + namespace, + ).Error(), + } } return &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhase(analysisRun.Status.Phase), + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: analysisRun.Name, Namespace: analysisRun.Namespace, Phase: string(analysisRun.Status.Phase), }, - }, nil + } } // getAnalysisRunNamespace infers whether this controller is running in a shard. diff --git a/internal/controller/stages/verification_test.go b/internal/controller/stages/verification_test.go index 7a8c327e8..3aac42f09 100644 --- a/internal/controller/stages/verification_test.go +++ b/internal/controller/stages/verification_test.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" kargoapi "github.com/akuity/kargo/api/v1alpha1" rollouts "github.com/akuity/kargo/internal/controller/rollouts/api/v1alpha1" @@ -21,8 +22,19 @@ func TestStarVerification(t *testing.T) { name string stage *kargoapi.Stage reconciler *reconciler - assertions func(*kargoapi.VerificationInfo, error) + assertions func(*kargoapi.VerificationInfo) }{ + { + name: "rollouts integration not enabled", + reconciler: &reconciler{}, + assertions: func(vi *kargoapi.VerificationInfo) { + require.Contains( + t, + vi.Message, + "Rollouts integration is disabled on this controller", + ) + }, + }, { name: "error listing AnalysisRuns", stage: &kargoapi.Stage{ @@ -33,6 +45,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -41,10 +54,9 @@ func TestStarVerification(t *testing.T) { return errors.New("something went wrong") }, }, - assertions: func(vi *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error listing AnalysisRuns for Stage") + assertions: func(vi *kargoapi.VerificationInfo) { + require.Contains(t, vi.Message, "something went wrong") + require.Contains(t, vi.Message, "error listing AnalysisRuns for Stage") }, }, { @@ -57,6 +69,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( _ context.Context, objList client.ObjectList, @@ -68,8 +81,9 @@ func TestStarVerification(t *testing.T) { return nil }, }, - assertions: func(_ *kargoapi.VerificationInfo, err error) { - require.NoError(t, err) + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Empty(t, vi.Message) }, }, { @@ -87,6 +101,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -102,10 +117,10 @@ func TestStarVerification(t *testing.T) { return nil, errors.New("something went wrong") }, }, - assertions: func(_ *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error getting AnalysisTemplate") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "something went wrong") + require.Contains(t, vi.Message, "error getting AnalysisTemplate") }, }, { @@ -123,6 +138,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -138,10 +154,10 @@ func TestStarVerification(t *testing.T) { return nil, nil }, }, - assertions: func(_ *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "AnalysisTemplate") - require.Contains(t, err.Error(), "not found") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "AnalysisTemplate") + require.Contains(t, vi.Message, "not found") }, }, { @@ -159,6 +175,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -180,10 +197,10 @@ func TestStarVerification(t *testing.T) { return nil, errors.New("something went wrong") }, }, - assertions: func(_ *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error building AnalysisRun for Stage") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "something went wrong") + require.Contains(t, vi.Message, "error building AnalysisRun for Stage") }, }, { @@ -201,6 +218,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -229,10 +247,10 @@ func TestStarVerification(t *testing.T) { return errors.New("something went wrong") }, }, - assertions: func(_ *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error creating AnalysisRun") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "something went wrong") + require.Contains(t, vi.Message, "error creating AnalysisRun") }, }, { @@ -250,6 +268,7 @@ func TestStarVerification(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), listAnalysisRunsFn: func( context.Context, client.ObjectList, @@ -283,17 +302,17 @@ func TestStarVerification(t *testing.T) { return nil }, }, - assertions: func(ver *kargoapi.VerificationInfo, err error) { - require.NoError(t, err) + assertions: func(vi *kargoapi.VerificationInfo) { require.Equal( t, &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhasePending, + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-run", Namespace: "fake-namespace", }, }, - ver, + vi, ) }, }, @@ -315,15 +334,27 @@ func TestGetVerificationInfo(t *testing.T) { name string stage *kargoapi.Stage reconciler *reconciler - assertions func(*kargoapi.VerificationInfo, error) + assertions func(*kargoapi.VerificationInfo) }{ + { + name: "rollouts integration not enabled", + reconciler: &reconciler{}, + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains( + t, + vi.Message, + "Rollouts integration is disabled on this controller", + ) + }, + }, { name: "error getting AnalysisRun", stage: &kargoapi.Stage{ Status: kargoapi.StageStatus{ CurrentFreight: &kargoapi.FreightReference{ VerificationInfo: &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-run", Namespace: "fake-namespace", }, @@ -332,6 +363,7 @@ func TestGetVerificationInfo(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), getAnalysisRunFn: func( context.Context, client.Client, @@ -340,10 +372,10 @@ func TestGetVerificationInfo(t *testing.T) { return nil, errors.New("something went wrong") }, }, - assertions: func(vi *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "something went wrong") - require.Contains(t, err.Error(), "error getting AnalysisRun") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "something went wrong") + require.Contains(t, vi.Message, "error getting AnalysisRun") }, }, { @@ -352,7 +384,7 @@ func TestGetVerificationInfo(t *testing.T) { Status: kargoapi.StageStatus{ CurrentFreight: &kargoapi.FreightReference{ VerificationInfo: &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-run", Namespace: "fake-namespace", }, @@ -361,6 +393,7 @@ func TestGetVerificationInfo(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), getAnalysisRunFn: func( context.Context, client.Client, @@ -369,10 +402,10 @@ func TestGetVerificationInfo(t *testing.T) { return nil, nil }, }, - assertions: func(vi *kargoapi.VerificationInfo, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "AnalysisRun") - require.Contains(t, err.Error(), "not found") + assertions: func(vi *kargoapi.VerificationInfo) { + require.NotNil(t, vi) + require.Contains(t, vi.Message, "AnalysisRun") + require.Contains(t, vi.Message, "not found") }, }, { @@ -381,7 +414,7 @@ func TestGetVerificationInfo(t *testing.T) { Status: kargoapi.StageStatus{ CurrentFreight: &kargoapi.FreightReference{ VerificationInfo: &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-run", Namespace: "fake-namespace", }, @@ -390,6 +423,7 @@ func TestGetVerificationInfo(t *testing.T) { }, }, reconciler: &reconciler{ + rolloutsClient: fake.NewClientBuilder().Build(), getAnalysisRunFn: func( context.Context, client.Client, @@ -406,18 +440,18 @@ func TestGetVerificationInfo(t *testing.T) { }, nil }, }, - assertions: func(ver *kargoapi.VerificationInfo, err error) { - require.NoError(t, err) + assertions: func(vi *kargoapi.VerificationInfo) { require.Equal( t, &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + Phase: kargoapi.VerificationPhaseSuccessful, + AnalysisRun: &kargoapi.AnalysisRunReference{ Name: "fake-run", Namespace: "fake-namespace", Phase: string(rollouts.AnalysisPhaseSuccessful), }, }, - ver, + vi, ) }, }, diff --git a/internal/credentials/credentials.go b/internal/credentials/credentials.go index 5def67880..b3b7a3053 100644 --- a/internal/credentials/credentials.go +++ b/internal/credentials/credentials.go @@ -71,9 +71,9 @@ type Database interface { // utilizes a Kubernetes controller runtime client to retrieve credentials // stored in Kubernetes Secrets. type kubernetesDatabase struct { - kargoClient client.Client - argoClient client.Client // nil if credential borrowing is not enabled - cfg KubernetesDatabaseConfig + kargoClient client.Client + argocdClient client.Client // nil if credential borrowing is not enabled + cfg KubernetesDatabaseConfig } // KubernetesDatabaseConfig represents configuration for a Kubernetes based @@ -94,13 +94,13 @@ func KubernetesDatabaseConfigFromEnv() KubernetesDatabaseConfig { // retrieve Credentials stored in Kubernetes Secrets. func NewKubernetesDatabase( kargoClient client.Client, - argoClient client.Client, + argocdClient client.Client, cfg KubernetesDatabaseConfig, ) Database { return &kubernetesDatabase{ - kargoClient: kargoClient, - argoClient: argoClient, - cfg: cfg, + kargoClient: kargoClient, + argocdClient: argocdClient, + cfg: cfg, } } @@ -195,7 +195,7 @@ func (k *kubernetesDatabase) Get( } } - if k.argoClient == nil { + if k.argocdClient == nil { // We cannot borrow creds from from Argo CD return creds, false, nil } @@ -203,7 +203,7 @@ func (k *kubernetesDatabase) Get( // Check Argo CD's namespace for credentials if secret, err = getCredentialsSecret( ctx, - k.argoClient, + k.argocdClient, k.cfg.ArgoCDNamespace, labels.Set(map[string]string{ argoCDSecretTypeLabelKey: repositorySecretTypeLabelValue, @@ -219,7 +219,7 @@ func (k *kubernetesDatabase) Get( // Check Argo CD's namespace for credentials template if secret, err = getCredentialsSecret( ctx, - k.argoClient, + k.argocdClient, k.cfg.ArgoCDNamespace, labels.Set(map[string]string{ argoCDSecretTypeLabelKey: repoCredsSecretTypeLabelValue, diff --git a/internal/credentials/credentials_test.go b/internal/credentials/credentials_test.go index 59386f5f9..59e325ec6 100644 --- a/internal/credentials/credentials_test.go +++ b/internal/credentials/credentials_test.go @@ -22,7 +22,7 @@ func TestNewKubernetesDatabase(t *testing.T) { k, ok := d.(*kubernetesDatabase) require.True(t, ok) require.Same(t, testClient, k.kargoClient) - require.Same(t, testClient, k.argoClient) + require.Same(t, testClient, k.argocdClient) require.Equal(t, testCfg, k.cfg) } diff --git a/internal/kubeclient/indexer_test.go b/internal/kubeclient/indexer_test.go index e5941aef8..9ced91665 100644 --- a/internal/kubeclient/indexer_test.go +++ b/internal/kubeclient/indexer_test.go @@ -44,7 +44,7 @@ func TestIndexStagesByAnalysisRun(t *testing.T) { Status: kargoapi.StageStatus{ CurrentFreight: &kargoapi.FreightReference{ VerificationInfo: &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + AnalysisRun: &kargoapi.AnalysisRunReference{ Namespace: "fake-namespace", Name: "fake-analysis-run", }, @@ -77,7 +77,7 @@ func TestIndexStagesByAnalysisRun(t *testing.T) { Status: kargoapi.StageStatus{ CurrentFreight: &kargoapi.FreightReference{ VerificationInfo: &kargoapi.VerificationInfo{ - AnalysisRun: kargoapi.AnalysisRunReference{ + AnalysisRun: &kargoapi.AnalysisRunReference{ Namespace: "fake-namespace", Name: "fake-analysis-run", }, From 68d27ee7fd47cd821eee5b8a96d665830ca8eb25 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Mon, 8 Jan 2024 17:11:05 -0500 Subject: [PATCH 07/10] corresponding chart changes Signed-off-by: Kent Rancourt --- charts/kargo/templates/api/cluster-role.yaml | 8 ++++++++ charts/kargo/templates/api/configmap.yaml | 1 + .../kargo/templates/argocd/role-binding.yaml | 2 +- charts/kargo/templates/argocd/role.yaml | 2 +- .../controller/cluster-role-bindings.yaml | 20 ++++++++++++++++++- .../templates/controller/cluster-roles.yaml | 15 ++++++++++++-- .../kargo/templates/controller/configmap.yaml | 10 +++++++--- charts/kargo/values.yaml | 15 +++++++++++++- 8 files changed, 64 insertions(+), 9 deletions(-) diff --git a/charts/kargo/templates/api/cluster-role.yaml b/charts/kargo/templates/api/cluster-role.yaml index 4f6cda115..16dc65e3e 100644 --- a/charts/kargo/templates/api/cluster-role.yaml +++ b/charts/kargo/templates/api/cluster-role.yaml @@ -76,4 +76,12 @@ rules: verbs: - patch - update +{{- if .Values.api.rollouts.integrationEnabled }} + - apiGroups: + - argoproj.io + resources: + - analysistemplates + verbs: + - "*" +{{- end }} {{- end }} diff --git a/charts/kargo/templates/api/configmap.yaml b/charts/kargo/templates/api/configmap.yaml index be76848be..c711bd2f2 100644 --- a/charts/kargo/templates/api/configmap.yaml +++ b/charts/kargo/templates/api/configmap.yaml @@ -55,4 +55,5 @@ data: ARGOCD_NAMESPACE: {{ .Values.controller.argocd.namespace }} ARGOCD_URLS: {{ range $key, $val := .Values.api.argocd.urls }}{{ $key }}={{ $val }},{{- end }} {{- end }} + ROLLOUTS_INTEGRATION_ENABLED: {{ quote .Values.api.rollouts.integrationEnabled }} {{- end }} diff --git a/charts/kargo/templates/argocd/role-binding.yaml b/charts/kargo/templates/argocd/role-binding.yaml index cd217ca47..956b1b822 100644 --- a/charts/kargo/templates/argocd/role-binding.yaml +++ b/charts/kargo/templates/argocd/role-binding.yaml @@ -1,4 +1,4 @@ -{{- if or .Values.controller.argocd.watchArgocdNamespaceOnly .Values.controller.argocd.enableCredentialBorrowing }} +{{- if and .Values.controller.argocd.integrationEnabled (or .Values.controller.argocd.watchArgocdNamespaceOnly .Values.controller.argocd.enableCredentialBorrowing) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/charts/kargo/templates/argocd/role.yaml b/charts/kargo/templates/argocd/role.yaml index 32ec23644..1215ac65f 100644 --- a/charts/kargo/templates/argocd/role.yaml +++ b/charts/kargo/templates/argocd/role.yaml @@ -1,4 +1,4 @@ -{{- if or .Values.controller.argocd.watchArgocdNamespaceOnly .Values.controller.argocd.enableCredentialBorrowing }} +{{- if and .Values.controller.argocd.integrationEnabled (or .Values.controller.argocd.watchArgocdNamespaceOnly .Values.controller.argocd.enableCredentialBorrowing) }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/charts/kargo/templates/controller/cluster-role-bindings.yaml b/charts/kargo/templates/controller/cluster-role-bindings.yaml index 6f720d407..790f77b6e 100644 --- a/charts/kargo/templates/controller/cluster-role-bindings.yaml +++ b/charts/kargo/templates/controller/cluster-role-bindings.yaml @@ -14,8 +14,8 @@ subjects: - kind: ServiceAccount namespace: {{ .Release.Namespace }} name: kargo-controller +{{- if and .Values.controller.argocd.integrationEnabled (not .Values.controller.argocd.watchArgocdNamespaceOnly) }} --- -{{- if not .Values.controller.argocd.watchArgocdNamespaceOnly }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -32,4 +32,22 @@ subjects: namespace: {{ .Release.Namespace }} name: kargo-controller {{- end }} +{{- if .Values.controller.rollouts.integrationEnabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kargo-controller-rollouts + labels: + {{- include "kargo.labels" . | nindent 4 }} + {{- include "kargo.controller.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kargo-controller-rollouts +subjects: +- kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: kargo-controller +{{- end }} {{- end }} diff --git a/charts/kargo/templates/controller/cluster-roles.yaml b/charts/kargo/templates/controller/cluster-roles.yaml index 18d50c011..a676b46fd 100644 --- a/charts/kargo/templates/controller/cluster-roles.yaml +++ b/charts/kargo/templates/controller/cluster-roles.yaml @@ -72,8 +72,8 @@ rules: - get - list - watch +{{- if and .Values.controller.argocd.integrationEnabled (not .Values.controller.argocd.watchArgocdNamespaceOnly) }} --- -{{- if not .Values.controller.argocd.watchArgocdNamespaceOnly }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -91,6 +91,17 @@ rules: - list - patch - watch +{{- end }} +{{- if .Values.controller.rollouts.integrationEnabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kargo-controller-rollouts + labels: + {{- include "kargo.labels" . | nindent 4 }} + {{- include "kargo.controller.labels" . | nindent 4 }} +rules: - apiGroups: - argoproj.io resources: @@ -100,5 +111,5 @@ rules: - get - list - watch -{{- end }} +{{- end }} {{- end }} diff --git a/charts/kargo/templates/controller/configmap.yaml b/charts/kargo/templates/controller/configmap.yaml index 5ff7e3488..bec2239b3 100644 --- a/charts/kargo/templates/controller/configmap.yaml +++ b/charts/kargo/templates/controller/configmap.yaml @@ -16,13 +16,17 @@ data: KUBECONFIG: /etc/kargo/kubeconfigs/kubeconfig.yaml {{- end }} GLOBAL_CREDENTIALS_NAMESPACES: {{ join "," .Values.controller.globalCredentials.namespaces }} + ARGOCD_INTEGRATION_ENABLED: {{ quote .Values.controller.argocd.integrationEnabled }} + {{- if .Values.controller.argocd.integrationEnabled }} {{- if .Values.kubeconfigSecrets.argocd }} ARGOCD_KUBECONFIG: /etc/kargo/kubeconfigs/argocd-kubeconfig.yaml {{- end }} - {{- if .Values.kubeconfigSecrets.rollouts }} - ROLLOUTS_KUBECONFIG: /etc/kargo/kubeconfigs/rollouts-kubeconfig.yaml - {{- end }} ARGOCD_NAMESPACE: {{ .Values.controller.argocd.namespace }} ARGOCD_ENABLE_CREDENTIAL_BORROWING: {{ quote .Values.controller.argocd.enableCredentialBorrowing }} ARGOCD_WATCH_ARGOCD_NAMESPACE_ONLY: {{ quote .Values.controller.argocd.watchArgocdNamespaceOnly }} + {{- end }} + ROLLOUTS_INTEGRATION_ENABLED: {{ quote .Values.controller.rollouts.integrationEnabled }} + {{- if and .Values.controller.rollouts.integrationEnabled .Values.kubeconfigSecrets.rollouts }} + ROLLOUTS_KUBECONFIG: /etc/kargo/kubeconfigs/rollouts-kubeconfig.yaml + {{- end }} {{- end }} diff --git a/charts/kargo/values.yaml b/charts/kargo/values.yaml index a354bf7e8..ecc4c6ed9 100755 --- a/charts/kargo/values.yaml +++ b/charts/kargo/values.yaml @@ -199,6 +199,11 @@ api: # "": https://argocd.example.com # "shard2": https://argocd2.example.com + ## All settings relating to the use of Argo Rollouts by the API Server. + rollouts: + ## @param api.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. + integrationEnabled: true + ## @section Controller ## All settings for the controller component controller: @@ -213,9 +218,11 @@ controller: ## @param controller.shardName [nullable] Set a shard name only if you are running multiple controllers backed by a single underlying control plane. Setting a shard name will cause this controller to operate **only** on resources with a matching shard name. Leaving the shard name undefined will designate this controller as the default controller that is responsible exclusively for resources that are **not** assigned to a specific shard. Leaving this undefined is the correct choice when you are not using sharding at all. It is also the correct setting if you are using sharding and want to designate a controller as the default for handling resources not assigned to a specific shard. In most cases, this setting should simply be left alone. # shardName: - ## All settings relating to the Argo CD control plane this controller will + ## All settings relating to the Argo CD control plane this controller might ## integrate with. argocd: + ## @param controller.argocd.integrationEnabled Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. + integrationEnabled: true ## @param controller.argocd.namespace The namespace into which Argo CD is installed. namespace: argocd ## @param controller.argocd.watchArgocdNamespaceOnly Specifies whether the reconciler that watches Argo CD Applications for the sake of forcing related Stages to reconcile should only watch Argo CD Application resources residing in Argo CD's own namespace. Note: Older versions of Argo CD only supported Argo CD Application resources in Argo CD's own namespace, but newer versions support Argo CD Application resources in any namespace. This should usually be left as `false`. @@ -223,6 +230,12 @@ controller: ## @param controller.argocd.enableCredentialBorrowing Specifies whether Kargo may borrow repository credentials (specially formatted and specially annotated Secrets) from Argo CD. enableCredentialBorrowing: true + ## All settings relating to the use of Argo Rollouts AnalysisTemplates and + ## AnalysisRuns as a means of verifying Stages after a Promotion. + rollouts: + ## @param controller.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. + integrationEnabled: true + ## @param controller.logLevel The log level for the controller. logLevel: INFO From 50a496c7ee0aba0cdb1e89a0b1c4596438e66664 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Wed, 10 Jan 2024 15:07:43 -0500 Subject: [PATCH 08/10] corresponding Tiltfile changes Signed-off-by: Kent Rancourt --- Tiltfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tiltfile b/Tiltfile index a32c47149..885651374 100644 --- a/Tiltfile +++ b/Tiltfile @@ -91,7 +91,9 @@ k8s_resource( 'kargo-controller:rolebinding', 'kargo-controller:serviceaccount', 'kargo-controller-argocd:clusterrole', - 'kargo-controller-argocd:clusterrolebinding' + 'kargo-controller-argocd:clusterrolebinding', + 'kargo-controller-rollouts:clusterrole', + 'kargo-controller-rollouts:clusterrolebinding' ], resource_deps=['back-end-compile'] ) From 4c219ab630e25b3f44d5b29238ffd0df04c43ff3 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Wed, 10 Jan 2024 18:49:15 -0500 Subject: [PATCH 09/10] address @jessesuen feedback Signed-off-by: Kent Rancourt --- charts/kargo/values.yaml | 6 +- cmd/controlplane/api.go | 42 ++++---- cmd/controlplane/controller.go | 174 ++++++++++++++++++--------------- cmd/controlplane/utils.go | 44 +++++++++ 4 files changed, 167 insertions(+), 99 deletions(-) create mode 100644 cmd/controlplane/utils.go diff --git a/charts/kargo/values.yaml b/charts/kargo/values.yaml index ecc4c6ed9..311d447a4 100755 --- a/charts/kargo/values.yaml +++ b/charts/kargo/values.yaml @@ -201,7 +201,7 @@ api: ## All settings relating to the use of Argo Rollouts by the API Server. rollouts: - ## @param api.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. + ## @param api.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. When enabled, the API server will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the API server will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the API server. integrationEnabled: true ## @section Controller @@ -221,7 +221,7 @@ controller: ## All settings relating to the Argo CD control plane this controller might ## integrate with. argocd: - ## @param controller.argocd.integrationEnabled Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. + ## @param controller.argocd.integrationEnabled Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. When enabled, the controller will perform a sanity check at startup. If Argo CD CRDs are not found, the controller will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the controller. integrationEnabled: true ## @param controller.argocd.namespace The namespace into which Argo CD is installed. namespace: argocd @@ -233,7 +233,7 @@ controller: ## All settings relating to the use of Argo Rollouts AnalysisTemplates and ## AnalysisRuns as a means of verifying Stages after a Promotion. rollouts: - ## @param controller.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. + ## @param controller.rollouts.integrationEnabled Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. When enabled, the controller will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the controller will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the controller. integrationEnabled: true ## @param controller.logLevel The log level for the controller. diff --git a/cmd/controlplane/api.go b/cmd/controlplane/api.go index 81ce7f75d..b1edd5ac6 100644 --- a/cmd/controlplane/api.go +++ b/cmd/controlplane/api.go @@ -46,10 +46,30 @@ func newAPICommand() *cobra.Command { if err != nil { return errors.Wrap(err, "error loading REST config") } - scheme, err := newSchemeForAPI() - if err != nil { - return errors.Wrap(err, "new scheme for API") + + scheme := runtime.NewScheme() + if err = kubescheme.AddToScheme(scheme); err != nil { + return errors.Wrap(err, "add Kubernetes api to scheme") + } + if types.MustParseBool(os.GetEnv("ROLLOUTS_INTEGRATION_ENABLED", "true")) { + if argoRolloutsExists(ctx, restCfg) { + log.Info("Argo Rollouts integration is enabled") + if err = rollouts.AddToScheme(scheme); err != nil { + return errors.Wrap(err, "add argo rollouts api to scheme") + } + } else { + log.Warn( + "Argo Rollouts integration was enabled, but no Argo Rollouts " + + "CRDs were found. Proceeding without Argo Rollouts integration.", + ) + } + } else { + log.Info("Argo Rollouts integration is disabled") } + if err = kargoapi.AddToScheme(scheme); err != nil { + return errors.Wrap(err, "add kargo api to scheme") + } + internalClient, err := newClientForAPI(ctx, restCfg, scheme) if err != nil { return errors.Wrap(err, "create internal Kubernetes client") @@ -152,19 +172,3 @@ func newClientForAPI(ctx context.Context, r *rest.Config, scheme *runtime.Scheme return mgr.GetClient(), nil } - -func newSchemeForAPI() (*runtime.Scheme, error) { - scheme := runtime.NewScheme() - if err := kubescheme.AddToScheme(scheme); err != nil { - return nil, errors.Wrap(err, "add Kubernetes api to scheme") - } - if types.MustParseBool(os.GetEnv("ROLLOUTS_INTEGRATION_ENABLED", "true")) { - if err := rollouts.AddToScheme(scheme); err != nil { - return nil, errors.Wrap(err, "add argo rollouts api to scheme") - } - } - if err := kargoapi.AddToScheme(scheme); err != nil { - return nil, errors.Wrap(err, "add kargo api to scheme") - } - return scheme, nil -} diff --git a/cmd/controlplane/controller.go b/cmd/controlplane/controller.go index 6baafc0ad..08e33eb80 100644 --- a/cmd/controlplane/controller.go +++ b/cmd/controlplane/controller.go @@ -125,47 +125,58 @@ func newControllerCommand() *cobra.Command { } restCfg.ContentType = runtime.ContentTypeJSON - scheme := runtime.NewScheme() - if err = corev1.AddToScheme(scheme); err != nil { - return errors.Wrap( - err, - "error adding Kubernetes core API to Argo CD controller "+ - "manager scheme", - ) - } - if err = argocd.AddToScheme(scheme); err != nil { - return errors.Wrap( - err, - "error adding Argo CD API to Argo CD controller manager scheme", - ) - } + argocdNamespace := os.GetEnv("ARGOCD_NAMESPACE", "argocd") - cacheOpts := cache.Options{} // Watches all namespaces by default - if types.MustParseBool( - os.GetEnv("ARGOCD_WATCH_ARGOCD_NAMESPACE_ONLY", "false"), - ) { - watchNamespace := os.GetEnv("ARGOCD_NAMESPACE", "argocd") - if watchNamespace != "" { + // There's a chance there is only permission to interact with Argo CD + // Application resources in a single namespace, so we will use that + // namespace when attempting to determine if Argo CD CRDs are installed. + if argoCDExists(ctx, restCfg, argocdNamespace) { + log.Info("Argo CD integration is enabled") + scheme := runtime.NewScheme() + if err = corev1.AddToScheme(scheme); err != nil { + return errors.Wrap( + err, + "error adding Kubernetes core API to Argo CD controller "+ + "manager scheme", + ) + } + if err = argocd.AddToScheme(scheme); err != nil { + return errors.Wrap( + err, + "error adding Argo CD API to Argo CD controller manager scheme", + ) + } + cacheOpts := cache.Options{} // Watches all namespaces by default + if types.MustParseBool( + os.GetEnv("ARGOCD_WATCH_ARGOCD_NAMESPACE_ONLY", "false"), + ) { cacheOpts.DefaultNamespaces = map[string]cache.Config{ - watchNamespace: {}, + argocdNamespace: {}, } } - } - if argocdMgr, err = ctrl.NewManager( - restCfg, - ctrl.Options{ - Scheme: scheme, - Metrics: server.Options{ - BindAddress: "0", + if argocdMgr, err = ctrl.NewManager( + restCfg, + ctrl.Options{ + Scheme: scheme, + Metrics: server.Options{ + BindAddress: "0", + }, + Cache: cacheOpts, }, - Cache: cacheOpts, - }, - ); err != nil { - return errors.Wrap( - err, - "error initializing Argo CD Application controller manager", + ); err != nil { + return errors.Wrap( + err, + "error initializing Argo CD Application controller manager", + ) + } + } else { + log.Warn( + "ARGO CD integration was enabled, but no Argo CD CRDs were " + + "found. Proceeding without Argo CD integration.", ) } + } else { + log.Info("Argo CD integration is disabled") } var rolloutsMgr manager.Manager @@ -188,60 +199,69 @@ func newControllerCommand() *cobra.Command { } restCfg.ContentType = runtime.ContentTypeJSON - scheme := runtime.NewScheme() - if err = rollouts.AddToScheme(scheme); err != nil { - return errors.Wrap( - err, - "error adding Argo Rollouts API to Argo Rollouts controller "+ - "manager scheme", - ) - } - - cacheOpts := cache.Options{} // Watches all namespaces by default - if shardName != "" { - // TODO: When NOT sharded, Kargo can simply create AnalysisRun - // resources in the project namespaces. When sharded, AnalysisRun - // resources must be created IN the shard clusters (not the Kargo - // control plane cluster) and project namespaces do not exist in the - // shard clusters. We need a place to put them, so for now we allow - // the user to specify a namespace that that exists on each shard for - // this purpose. Note that the namespace does not need to be the same - // on every shard. This may be one of the weaker points in our tenancy - // model and can stand to be improved. - watchNamespace := os.GetEnv( - "ARGO_ROLLOUTS_ANALYSIS_RUNS_NAMESPACE", - "kargo-analysis-runs", - ) - cacheOpts.DefaultNamespaces = map[string]cache.Config{ - watchNamespace: {}, + if argoRolloutsExists(ctx, restCfg) { + log.Info("Argo Rollouts integration is enabled") + scheme := runtime.NewScheme() + if err = rollouts.AddToScheme(scheme); err != nil { + return errors.Wrap( + err, + "error adding Argo Rollouts API to Argo Rollouts controller "+ + "manager scheme", + ) } - } - if rolloutsMgr, err = ctrl.NewManager( - restCfg, - ctrl.Options{ - Scheme: scheme, - Metrics: server.Options{ - BindAddress: "0", + cacheOpts := cache.Options{} // Watches all namespaces by default + if shardName != "" { + // TODO: When NOT sharded, Kargo can simply create AnalysisRun + // resources in the project namespaces. When sharded, AnalysisRun + // resources must be created IN the shard clusters (not the Kargo + // control plane cluster) and project namespaces do not exist in the + // shard clusters. We need a place to put them, so for now we allow + // the user to specify a namespace that that exists on each shard for + // this purpose. Note that the namespace does not need to be the same + // on every shard. This may be one of the weaker points in our tenancy + // model and can stand to be improved. + watchNamespace := os.GetEnv( + "ARGO_ROLLOUTS_ANALYSIS_RUNS_NAMESPACE", + "kargo-analysis-runs", + ) + cacheOpts.DefaultNamespaces = map[string]cache.Config{ + watchNamespace: {}, + } + } + if rolloutsMgr, err = ctrl.NewManager( + restCfg, + ctrl.Options{ + Scheme: scheme, + Metrics: server.Options{ + BindAddress: "0", + }, + Cache: cacheOpts, }, - Cache: cacheOpts, - }, - ); err != nil { - return errors.Wrap( - err, - "error initializing Argo Rollouts AnalysisRun controller manager", + ); err != nil { + return errors.Wrap( + err, + "error initializing Argo Rollouts AnalysisRun controller manager", + ) + } + } else { + log.Warn( + "Argo Rollouts integration was enabled, but no Argo Rollouts " + + "CRDs were found. Proceeding without Argo Rollouts integration.", ) } + } else { + log.Info("Argo Rollouts integration is disabled") } - var argoClientForCreds client.Client + var argocdClientForCreds client.Client if types.MustParseBool( os.GetEnv("ARGOCD_ENABLE_CREDENTIAL_BORROWING", "false"), - ) { - argoClientForCreds = argocdMgr.GetClient() + ) && argocdMgr != nil { + argocdClientForCreds = argocdMgr.GetClient() } credentialsDB := credentials.NewKubernetesDatabase( kargoMgr.GetClient(), - argoClientForCreds, + argocdClientForCreds, credentials.KubernetesDatabaseConfigFromEnv(), ) diff --git a/cmd/controlplane/utils.go b/cmd/controlplane/utils.go new file mode 100644 index 000000000..1d31b5390 --- /dev/null +++ b/cmd/controlplane/utils.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +func argoCDExists( + ctx context.Context, + restCfg *rest.Config, + namespace string, +) bool { + if client, err := dynamic.NewForConfig(restCfg); err == nil { + if _, err = client.Resource( + schema.GroupVersionResource{ + Group: "argoproj.io", + Version: "v1alpha1", + Resource: "applications", + }, + ).Namespace(namespace).List(ctx, metav1.ListOptions{Limit: 1}); err == nil { + return true + } + } + return false +} + +func argoRolloutsExists(ctx context.Context, restCfg *rest.Config) bool { + if client, err := dynamic.NewForConfig(restCfg); err == nil { + if _, err = client.Resource( + schema.GroupVersionResource{ + Group: "argoproj.io", + Version: "v1alpha1", + Resource: "analysistemplates", + }, + ).List(ctx, metav1.ListOptions{Limit: 1}); err == nil { + return true + } + } + return false +} From 12bfb2883cfc8b95a8a3b3bc5cf77cd32853cbbd Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Wed, 10 Jan 2024 19:07:27 -0500 Subject: [PATCH 10/10] re-run codegen Signed-off-by: Kent Rancourt --- charts/kargo/README.md | 94 +++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/charts/kargo/README.md b/charts/kargo/README.md index e2642a956..62392fcef 100644 --- a/charts/kargo/README.md +++ b/charts/kargo/README.md @@ -45,51 +45,51 @@ the Kargo controller is running. ### API -| Name | Description | Value | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `api.enabled` | Whether the API server is enabled. | `true` | -| `api.replicas` | The number of API server pods. | `1` | -| `api.host` | The domain name where Kargo's API server will be accessible. When applicable, this is used for generation of an Ingress resource, certificates, and the OpenID Connect issuer and callback URLs. Note: The value in this field MAY include a port number and MUST NOT specify the protocol (http vs https), which is automatically inferred from other configuration options. | `localhost` | -| `api.logLevel` | The log level for the API server. | `INFO` | -| `api.resources` | Resources limits and requests for the api containers. | `{}` | -| `api.nodeSelector` | Node selector for api pods. | `{}` | -| `api.tolerations` | Tolerations for api pods. | `[]` | -| `api.affinity` | Specifies pod affinity for api pods. | `{}` | -| `api.probes.enabled` | Whether liveness and readiness probes should be included in the API server deployment. It is sometimes advantageous to disable these during local development. | `true` | -| `api.tls.enabled` | Whether to enable TLS directly on the API server. This is helpful if you do not intend to use an ingress controller or if you require TLS end-to-end. All other settings in this section will be ignored when this is set to `false`. | `true` | -| `api.tls.selfSignedCert` | Whether to generate a self-signed certificate for use by the API server. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-cert` **must** be provided in the same namespace as Kargo. | `true` | -| `api.enablePermissiveCORSPolicy` | Whether to enable a permissive CORS (Cross Origin Resource Sharing) policy. This is sometimes advantageous during local development, but otherwise, should generally be left disabled. | `false` | -| `api.ingress.enabled` | Whether to enable ingress. By default, this is disabled. Enabling ingress is advanced usage. | `false` | -| `api.ingress.annotations` | Annotations specified by your ingress controller to customize the behavior of the ingress resource. | `nil` | -| `api.ingress.ingressClassName` | From Kubernetes 1.18+, this field is supported if implemented by your ingress controller. When set, you do not need to add the ingress class as annotation. | `nil` | -| `api.ingress.tls.enabled` | Whether to enable TLS for the ingress. All other settings in this section will be ignored when this is set to `false`. | `true` | -| `api.ingress.tls.selfSignedCert` | Whether to generate a self-signed certificate for use with the API server's Ingress resource. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-ingress-cert` **must** be provided in the same namespace as Kargo. | `true` | -| `api.service.type` | If you're not going to use an ingress controller, you may want to change this value to `LoadBalancer` for production deployments. If running locally, you may want to change it to `NodePort` OR leave it as `ClusterIP` and use `kubectl port-forward` to map a port on the local network interface to the service. | `ClusterIP` | -| `api.service.nodePort` | Host port the `Service` will be mapped to when `type` is either `NodePort` or `LoadBalancer`. If not specified, Kubernetes chooses. | `undefined` | -| `api.adminAccount.enabled` | Whether to enable the admin account. | `true` | -| `api.adminAccount.passwordHash` | Bcrypt password hash for the admin account. If specified, will ignore `password`. A value **must** be provided for either this field or `password`. | `""` | -| `api.adminAccount.password` | A password for the admin account. Ignored if `passwordHash` is set. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut -c1-25`. A value **must** be provided for either this field or `passwordHash`. | `""` | -| `api.adminAccount.tokenSigningKey` | Key used to sign ID tokens (JWTs) for the admin account. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut`. A value **must** be provided for this field. | `""` | -| `api.adminAccount.tokenTTL` | Specifies how long ID tokens for the admin account are valid. (i.e. The expiry will be the time of issue plus this duration.) | `24h` | -| `api.oidc.enabled` | Whether to enable authentication using Open ID Connect. | `false` | -| `api.oidc.issuerURL` | The issuer URL for the identity provider. If Dex is enabled, this value will be ignored and the issuer URL will be automatically configured. If Dex is not enabled, this should be set to the issuer URL provided to you by your identity provider. | `nil` | -| `api.oidc.clientID` | The client ID for the OIDC client. If Dex is enabled, this value will be ignored and the client ID will be automatically configured. If Dex is not enabled, this should be set to the client ID provided to you by your identity provider. | `nil` | -| `api.oidc.cliClientID` | The client ID for the OIDC client used by CLI (optional). Needed by some OIDC providers (such as Dex) that require a separate Client ID for web app login vs. CLI login (`http://localhost`). If Dex is enabled, this value will be ignored and cli client ID will be automatically configured. If Dex is not enabled, and a different client app is configured for localhost CLI login, this should be the client ID configured in the IdP. | `nil` | -| `api.oidc.globalServiceAccounts.namespaces` | List of namespaces to look for shared service accounts. | `[]` | -| `api.oidc.dex.enabled` | Whether to enable Dex as the identity provider. When set to true, the Kargo installation will include a Dex server and the Kargo API server will be configured to make the /dex endpoint a reverse proxy for the Dex server. | `false` | -| `api.oidc.dex.image.repository` | Image repository of Dex | `ghcr.io/dexidp/dex` | -| `api.oidc.dex.image.tag` | Image tag for Dex. | `v2.37.0` | -| `api.oidc.dex.image.pullPolicy` | Image pull policy for Dex. | `IfNotPresent` | -| `api.oidc.dex.probes.enabled` | Whether liveness and readiness probes should be included in the Dex server deployment. It is sometimes advantageous to disable these during local development. | `true` | -| `api.oidc.dex.tls.selfSignedCert` | Whether to generate a self-signed certificate for use with Dex. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-dex-server-cert` **must** be provided in the same namespace as Kargo. There is no provision for running Dex without TLS. | `true` | -| `api.oidc.dex.skipApprovalScreen` | Whether to skip Dex's own approval screen. Since upstream identity providers will already request user consent, this second approval screen from Dex can be both superfluous and confusing. | `true` | -| `api.oidc.dex.connectors` | Configure [Dex connectors](https://dexidp.io/docs/connectors/) to one or more upstream identity providers. | `[]` | -| `api.oidc.dex.resources` | Resources limits and requests for the Dex server containers. | `{}` | -| `api.oidc.dex.nodeSelector` | Node selector for Dex server pods. | `{}` | -| `api.oidc.dex.tolerations` | Tolerations for Dex server pods. | `[]` | -| `api.oidc.dex.affinity` | Specifies pod affinity for the Dex server pods. | `{}` | -| `api.argocd.urls` | Mapping of Argo CD shards names to URLs to support deep links to Argo CD URLs. If sharding is not used, map the empty string to the single Argo CD URL. | `nil` | -| `api.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. | `true` | +| Name | Description | Value | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `api.enabled` | Whether the API server is enabled. | `true` | +| `api.replicas` | The number of API server pods. | `1` | +| `api.host` | The domain name where Kargo's API server will be accessible. When applicable, this is used for generation of an Ingress resource, certificates, and the OpenID Connect issuer and callback URLs. Note: The value in this field MAY include a port number and MUST NOT specify the protocol (http vs https), which is automatically inferred from other configuration options. | `localhost` | +| `api.logLevel` | The log level for the API server. | `INFO` | +| `api.resources` | Resources limits and requests for the api containers. | `{}` | +| `api.nodeSelector` | Node selector for api pods. | `{}` | +| `api.tolerations` | Tolerations for api pods. | `[]` | +| `api.affinity` | Specifies pod affinity for api pods. | `{}` | +| `api.probes.enabled` | Whether liveness and readiness probes should be included in the API server deployment. It is sometimes advantageous to disable these during local development. | `true` | +| `api.tls.enabled` | Whether to enable TLS directly on the API server. This is helpful if you do not intend to use an ingress controller or if you require TLS end-to-end. All other settings in this section will be ignored when this is set to `false`. | `true` | +| `api.tls.selfSignedCert` | Whether to generate a self-signed certificate for use by the API server. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-cert` **must** be provided in the same namespace as Kargo. | `true` | +| `api.enablePermissiveCORSPolicy` | Whether to enable a permissive CORS (Cross Origin Resource Sharing) policy. This is sometimes advantageous during local development, but otherwise, should generally be left disabled. | `false` | +| `api.ingress.enabled` | Whether to enable ingress. By default, this is disabled. Enabling ingress is advanced usage. | `false` | +| `api.ingress.annotations` | Annotations specified by your ingress controller to customize the behavior of the ingress resource. | `nil` | +| `api.ingress.ingressClassName` | From Kubernetes 1.18+, this field is supported if implemented by your ingress controller. When set, you do not need to add the ingress class as annotation. | `nil` | +| `api.ingress.tls.enabled` | Whether to enable TLS for the ingress. All other settings in this section will be ignored when this is set to `false`. | `true` | +| `api.ingress.tls.selfSignedCert` | Whether to generate a self-signed certificate for use with the API server's Ingress resource. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-ingress-cert` **must** be provided in the same namespace as Kargo. | `true` | +| `api.service.type` | If you're not going to use an ingress controller, you may want to change this value to `LoadBalancer` for production deployments. If running locally, you may want to change it to `NodePort` OR leave it as `ClusterIP` and use `kubectl port-forward` to map a port on the local network interface to the service. | `ClusterIP` | +| `api.service.nodePort` | Host port the `Service` will be mapped to when `type` is either `NodePort` or `LoadBalancer`. If not specified, Kubernetes chooses. | `undefined` | +| `api.adminAccount.enabled` | Whether to enable the admin account. | `true` | +| `api.adminAccount.passwordHash` | Bcrypt password hash for the admin account. If specified, will ignore `password`. A value **must** be provided for either this field or `password`. | `""` | +| `api.adminAccount.password` | A password for the admin account. Ignored if `passwordHash` is set. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut -c1-25`. A value **must** be provided for either this field or `passwordHash`. | `""` | +| `api.adminAccount.tokenSigningKey` | Key used to sign ID tokens (JWTs) for the admin account. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut`. A value **must** be provided for this field. | `""` | +| `api.adminAccount.tokenTTL` | Specifies how long ID tokens for the admin account are valid. (i.e. The expiry will be the time of issue plus this duration.) | `24h` | +| `api.oidc.enabled` | Whether to enable authentication using Open ID Connect. | `false` | +| `api.oidc.issuerURL` | The issuer URL for the identity provider. If Dex is enabled, this value will be ignored and the issuer URL will be automatically configured. If Dex is not enabled, this should be set to the issuer URL provided to you by your identity provider. | `nil` | +| `api.oidc.clientID` | The client ID for the OIDC client. If Dex is enabled, this value will be ignored and the client ID will be automatically configured. If Dex is not enabled, this should be set to the client ID provided to you by your identity provider. | `nil` | +| `api.oidc.cliClientID` | The client ID for the OIDC client used by CLI (optional). Needed by some OIDC providers (such as Dex) that require a separate Client ID for web app login vs. CLI login (`http://localhost`). If Dex is enabled, this value will be ignored and cli client ID will be automatically configured. If Dex is not enabled, and a different client app is configured for localhost CLI login, this should be the client ID configured in the IdP. | `nil` | +| `api.oidc.globalServiceAccounts.namespaces` | List of namespaces to look for shared service accounts. | `[]` | +| `api.oidc.dex.enabled` | Whether to enable Dex as the identity provider. When set to true, the Kargo installation will include a Dex server and the Kargo API server will be configured to make the /dex endpoint a reverse proxy for the Dex server. | `false` | +| `api.oidc.dex.image.repository` | Image repository of Dex | `ghcr.io/dexidp/dex` | +| `api.oidc.dex.image.tag` | Image tag for Dex. | `v2.37.0` | +| `api.oidc.dex.image.pullPolicy` | Image pull policy for Dex. | `IfNotPresent` | +| `api.oidc.dex.probes.enabled` | Whether liveness and readiness probes should be included in the Dex server deployment. It is sometimes advantageous to disable these during local development. | `true` | +| `api.oidc.dex.tls.selfSignedCert` | Whether to generate a self-signed certificate for use with Dex. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-dex-server-cert` **must** be provided in the same namespace as Kargo. There is no provision for running Dex without TLS. | `true` | +| `api.oidc.dex.skipApprovalScreen` | Whether to skip Dex's own approval screen. Since upstream identity providers will already request user consent, this second approval screen from Dex can be both superfluous and confusing. | `true` | +| `api.oidc.dex.connectors` | Configure [Dex connectors](https://dexidp.io/docs/connectors/) to one or more upstream identity providers. | `[]` | +| `api.oidc.dex.resources` | Resources limits and requests for the Dex server containers. | `{}` | +| `api.oidc.dex.nodeSelector` | Node selector for Dex server pods. | `{}` | +| `api.oidc.dex.tolerations` | Tolerations for Dex server pods. | `[]` | +| `api.oidc.dex.affinity` | Specifies pod affinity for the Dex server pods. | `{}` | +| `api.argocd.urls` | Mapping of Argo CD shards names to URLs to support deep links to Argo CD URLs. If sharding is not used, map the empty string to the single Argo CD URL. | `nil` | +| `api.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. When enabled, the API server will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the API server will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the API server. | `true` | ### Controller @@ -98,11 +98,11 @@ the Kargo controller is running. | `controller.enabled` | Whether the controller is enabled. | `true` | | `controller.globalCredentials.namespaces` | List of namespaces to look for shared credentials. | `[]` | | `controller.shardName` | Set a shard name only if you are running multiple controllers backed by a single underlying control plane. Setting a shard name will cause this controller to operate **only** on resources with a matching shard name. Leaving the shard name undefined will designate this controller as the default controller that is responsible exclusively for resources that are **not** assigned to a specific shard. Leaving this undefined is the correct choice when you are not using sharding at all. It is also the correct setting if you are using sharding and want to designate a controller as the default for handling resources not assigned to a specific shard. In most cases, this setting should simply be left alone. | `undefined` | -| `controller.argocd.integrationEnabled` | Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. | `true` | +| `controller.argocd.integrationEnabled` | Specifies whether Argo CD integration is enabled. When not enabled, the controller will not watch Argo CD Application resources or factor Application health and sync state into determinations of Stage health. Argo CD-based promotion mechanisms will also fail. When enabled, the controller will perform a sanity check at startup. If Argo CD CRDs are not found, the controller will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the controller. | `true` | | `controller.argocd.namespace` | The namespace into which Argo CD is installed. | `argocd` | | `controller.argocd.watchArgocdNamespaceOnly` | Specifies whether the reconciler that watches Argo CD Applications for the sake of forcing related Stages to reconcile should only watch Argo CD Application resources residing in Argo CD's own namespace. Note: Older versions of Argo CD only supported Argo CD Application resources in Argo CD's own namespace, but newer versions support Argo CD Application resources in any namespace. This should usually be left as `false`. | `false` | | `controller.argocd.enableCredentialBorrowing` | Specifies whether Kargo may borrow repository credentials (specially formatted and specially annotated Secrets) from Argo CD. | `true` | -| `controller.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. | `true` | +| `controller.rollouts.integrationEnabled` | Specifies whether Argo Rollouts integration is enabled. When not enabled, the controller will not reconcile Argo Rollouts AnalysisRun resources and attempts to verify Stages via Analysis will fail. When enabled, the controller will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the controller will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the controller. | `true` | | `controller.logLevel` | The log level for the controller. | `INFO` | | `controller.resources` | Resources limits and requests for the controller containers. | `{}` | | `controller.nodeSelector` | Node selector for controller pods. | `{}` |