Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ build: generate fmt vet ## Build manager binary.

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
ARGOCD_OPERATOR_NAMESPACE=openshift-gitops-operator CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES=argo-rollouts,test-rom-ns-1,rom-ns-1,openshift-gitops ARGOCD_CLUSTER_CONFIG_NAMESPACES="openshift-gitops, argocd-e2e-cluster-config, argocd-test-impersonation-1-046, argocd-agent-principal-1-051, argocd-agent-agent-1-052, argocd-spiffe-1-136, appset-argocd, appset-old-ns, appset-new-ns, ns-hosting-principal, ns-hosting-managed-agent, ns-hosting-autonomous-agent, appset-argocd-clusterrole, gitops-promoter-1-134" REDIS_CONFIG_PATH="build/redis" go run ./cmd/main.go
ARGOCD_OPERATOR_NAMESPACE=openshift-gitops-operator CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES=argo-rollouts,test-rom-ns-1,rom-ns-1,openshift-gitops ARGOCD_CLUSTER_CONFIG_NAMESPACES="openshift-gitops, argocd-e2e-cluster-config, argocd-test-impersonation-1-046, argocd-agent-principal-1-051, argocd-agent-agent-1-052, argocd-spiffe-1-136, appset-argocd, appset-old-ns, appset-new-ns, ns-hosting-principal, ns-hosting-managed-agent, ns-hosting-autonomous-agent, appset-argocd-clusterrole, gitops-promoter-1-134, argocd-agent-principal-ha-1-137" REDIS_CONFIG_PATH="build/redis" go run ./cmd/main.go

.PHONY: docker-build
docker-build: test ## Build container image with the manager.
Expand Down
36 changes: 36 additions & 0 deletions argocd-operator/api/v1alpha1/argocd_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ func ConvertAlphaToBetaPrincipal(src *PrincipalSpec) *v1beta1.PrincipalSpec {
SPIFFE: ConvertAlphaToBetaSPIFFE(src.SPIFFE),
LogFullDetail: src.LogFullDetail,
PProf: ConvertAlphaToBetaAgentPProf(src.PProf),
HA: ConvertAlphaToBetaPrincipalHA(src.HA),
}
}
return dst
Expand Down Expand Up @@ -873,6 +874,7 @@ func ConvertBetaToAlphaPrincipal(src *v1beta1.PrincipalSpec) *PrincipalSpec {
SPIFFE: ConvertBetaToAlphaSPIFFE(src.SPIFFE),
LogFullDetail: src.LogFullDetail,
PProf: ConvertBetaToAlphaAgentPProf(src.PProf),
HA: ConvertBetaToAlphaPrincipalHA(src.HA),
}
}
return dst
Expand Down Expand Up @@ -1291,3 +1293,37 @@ func ConvertBetaToAlphaPrincipalSelfRegistration(src *v1beta1.PrincipalSelfRegis
}
return dst
}

func ConvertAlphaToBetaPrincipalHA(src *PrincipalHASpec) *v1beta1.PrincipalHASpec {
var dst *v1beta1.PrincipalHASpec
if src != nil {
dst = &v1beta1.PrincipalHASpec{
Enabled: src.Enabled,
PreferredRole: src.PreferredRole,
PeerAddress: src.PeerAddress,
PeerTLSSecretName: src.PeerTLSSecretName,
AllowedReplicationClients: src.AllowedReplicationClients,
AdminPort: src.AdminPort,
AdminAuth: src.AdminAuth,
ReplicationInitialAckTimeout: src.ReplicationInitialAckTimeout,
}
}
return dst
}

func ConvertBetaToAlphaPrincipalHA(src *v1beta1.PrincipalHASpec) *PrincipalHASpec {
var dst *PrincipalHASpec
if src != nil {
dst = &PrincipalHASpec{
Enabled: src.Enabled,
PreferredRole: src.PreferredRole,
PeerAddress: src.PeerAddress,
PeerTLSSecretName: src.PeerTLSSecretName,
AllowedReplicationClients: src.AllowedReplicationClients,
AdminPort: src.AdminPort,
AdminAuth: src.AdminAuth,
ReplicationInitialAckTimeout: src.ReplicationInitialAckTimeout,
}
}
return dst
}
86 changes: 86 additions & 0 deletions argocd-operator/api/v1alpha1/argocd_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,49 @@ func TestAlphaToBetaConversion(t *testing.T) {
}
}),
},
{
name: "ArgoCD Example - Agent Principal with HA enabled",
input: makeTestArgoCDAlpha(func(cr *ArgoCD) {
enabled := true
haEnabled := true
adminPort := int32(8405)
cr.Spec.ArgoCDAgent = &ArgoCDAgentSpec{
Principal: &PrincipalSpec{
Enabled: &enabled,
HA: &PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "primary",
PeerAddress: "argocd-agent-principal-replica:8443",
PeerTLSSecretName: "custom-ha-peer-tls",
AllowedReplicationClients: []string{"argocd-agent-principal-peer"},
AdminPort: &adminPort,
AdminAuth: "mtls:subject:CN=ha-admin",
ReplicationInitialAckTimeout: "5m",
},
},
}
}),
expectedOutput: makeTestArgoCDBeta(func(cr *v1beta1.ArgoCD) {
enabled := true
haEnabled := true
adminPort := int32(8405)
cr.Spec.ArgoCDAgent = &v1beta1.ArgoCDAgentSpec{
Principal: &v1beta1.PrincipalSpec{
Enabled: &enabled,
HA: &v1beta1.PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "primary",
PeerAddress: "argocd-agent-principal-replica:8443",
PeerTLSSecretName: "custom-ha-peer-tls",
AllowedReplicationClients: []string{"argocd-agent-principal-peer"},
AdminPort: &adminPort,
AdminAuth: "mtls:subject:CN=ha-admin",
ReplicationInitialAckTimeout: "5m",
},
},
}
}),
},
}

for _, test := range tests {
Expand Down Expand Up @@ -1477,6 +1520,49 @@ func TestBetaToAlphaConversion(t *testing.T) {
}
}),
},
{
name: "ArgoCD Example - Agent Principal with HA enabled",
input: makeTestArgoCDBeta(func(cr *v1beta1.ArgoCD) {
enabled := true
haEnabled := true
adminPort := int32(8405)
cr.Spec.ArgoCDAgent = &v1beta1.ArgoCDAgentSpec{
Principal: &v1beta1.PrincipalSpec{
Enabled: &enabled,
HA: &v1beta1.PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "primary",
PeerAddress: "argocd-agent-principal-replica:8443",
PeerTLSSecretName: "custom-ha-peer-tls",
AllowedReplicationClients: []string{"argocd-agent-principal-peer"},
AdminPort: &adminPort,
AdminAuth: "mtls:subject:CN=ha-admin",
ReplicationInitialAckTimeout: "5m",
},
},
}
}),
expectedOutput: makeTestArgoCDAlpha(func(cr *ArgoCD) {
enabled := true
haEnabled := true
adminPort := int32(8405)
cr.Spec.ArgoCDAgent = &ArgoCDAgentSpec{
Principal: &PrincipalSpec{
Enabled: &enabled,
HA: &PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "primary",
PeerAddress: "argocd-agent-principal-replica:8443",
PeerTLSSecretName: "custom-ha-peer-tls",
AllowedReplicationClients: []string{"argocd-agent-principal-peer"},
AdminPort: &adminPort,
AdminAuth: "mtls:subject:CN=ha-admin",
ReplicationInitialAckTimeout: "5m",
},
},
}
}),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
45 changes: 45 additions & 0 deletions argocd-operator/api/v1alpha1/argocd_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,9 @@ type PrincipalSpec struct {

// PProf defines the profiling options for the Principal component.
PProf *ArgoCDAgentPProfSpec `json:"pprof,omitempty"`

// HA defines the active/passive High Availability configuration for the Principal component.
HA *PrincipalHASpec `json:"ha,omitempty"`
}

// ArgoCDAgentPProfSpec defines the profiling options of an Argo CD Agent component.
Expand All @@ -1315,6 +1318,48 @@ type ArgoCDAgentPProfSpec struct {
Enabled *bool `json:"enabled,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled && has(self.preferredRole) && self.preferredRole == 'replica') || (has(self.peerAddress) && size(self.peerAddress) > 0)",message="peerAddress must be set when preferredRole is replica"
// PrincipalHASpec defines the active/passive High Availability configuration
// for the Principal component of ArgoCD Agent.
type PrincipalHASpec struct {
// Enabled enables active/passive High Availability for the principal. (default: false)
Enabled *bool `json:"enabled,omitempty"`

// PreferredRole is the startup role of this principal instance.
// "primary" starts in ACTIVE state.
// "replica" starts in SYNCING → REPLICATING state.
// +kubebuilder:validation:Enum=primary;replica
PreferredRole string `json:"preferredRole,omitempty"`

// PeerAddress is the gRPC address (host:port) of the peer principal.
// Required when PreferredRole is "replica".
PeerAddress string `json:"peerAddress,omitempty"`

// PeerTLSSecretName is the name of the TLS secret containing the dedicated
// replication client certificate (tls.crt, tls.key).
// (default: "argocd-agent-ha-peer-tls")
PeerTLSSecretName string `json:"peerTLSSecretName,omitempty"`

// AllowedReplicationClients is the list of mTLS identities permitted to connect for replication.
// When empty, any authenticated peer is allowed.
// The dedicated peer client certificate uses CN=argocd-agent-principal-peer.
AllowedReplicationClients []string `json:"allowedReplicationClients,omitempty"`

// AdminPort is the port for the HAAdmin gRPC server used by
// argocd-agentctl ha status/promote/demote. Access via kubectl port-forward. (default: 8405)
AdminPort *int32 `json:"adminPort,omitempty"`
Comment thread
jparsai marked this conversation as resolved.
Resolved

// AdminAuth is the authorization policy for the HA admin endpoint.
// When TLS is active and this is unset, all admin calls are denied.
// Format: "mtls:subject:<regex>" or "mtls:uri:<regex>".
// Example: "mtls:subject:CN=ha-admin"
AdminAuth string `json:"adminAuth,omitempty"`

// ReplicationInitialAckTimeout is the duration the primary waits for the replica's
// initial acknowledgement after delivering the full snapshot. (default: 5m)
ReplicationInitialAckTimeout string `json:"replicationInitialAckTimeout,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="!has(self.enabled) || self.enabled == false || (has(self.clientCertSecretName) && self.clientCertSecretName != \"\")",message="clientCertSecretName must be set when self-registration is enabled"
type PrincipalSelfRegistrationSpec struct {
// Enabled is the flag to enable self-registration of agents.
Expand Down
41 changes: 41 additions & 0 deletions argocd-operator/api/v1alpha1/argocd_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,44 @@ func Test_ArgoCDNotifications_SourceNamespaces(t *testing.T) {
emptyNotifications := ArgoCDNotifications{}
assert.Nil(t, emptyNotifications.SourceNamespaces)
}

func Test_PrincipalHASpec_Fields(t *testing.T) {
haEnabled := true
adminPort := int32(8405)

ha := PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "primary",
PeerAddress: "argocd-agent-principal-replica:8443",
PeerTLSSecretName: "argocd-agent-ha-peer-tls",
AllowedReplicationClients: []string{"argocd-agent-principal-peer"},
AdminPort: &adminPort,
AdminAuth: "mtls:subject:CN=ha-admin",
ReplicationInitialAckTimeout: "5m",
}

assert.True(t, *ha.Enabled)
assert.Equal(t, "primary", ha.PreferredRole)
assert.Equal(t, "argocd-agent-principal-replica:8443", ha.PeerAddress)
assert.Equal(t, "argocd-agent-ha-peer-tls", ha.PeerTLSSecretName)
assert.Equal(t, []string{"argocd-agent-principal-peer"}, ha.AllowedReplicationClients)
assert.Equal(t, int32(8405), *ha.AdminPort)
assert.Equal(t, "mtls:subject:CN=ha-admin", ha.AdminAuth)
assert.Equal(t, "5m", ha.ReplicationInitialAckTimeout)
}

func Test_PrincipalSpec_HA_OmitEmpty(t *testing.T) {
// When HA is not set, the field should be nil
principal := PrincipalSpec{}
assert.Nil(t, principal.HA)

// When HA is set, it should be accessible
haEnabled := false
principal.HA = &PrincipalHASpec{
Enabled: &haEnabled,
PreferredRole: "replica",
}
assert.NotNil(t, principal.HA)
assert.False(t, *principal.HA.Enabled)
assert.Equal(t, "replica", principal.HA.PreferredRole)
}
35 changes: 35 additions & 0 deletions argocd-operator/api/v1alpha1/zz_generated.deepcopy.go

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

45 changes: 45 additions & 0 deletions argocd-operator/api/v1beta1/argocd_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,9 @@ type PrincipalSpec struct {

// PProf defines the profiling options for the Principal component.
PProf *ArgoCDAgentPProfSpec `json:"pprof,omitempty"`

// HA defines the active/passive High Availability configuration for the Principal component.
HA *PrincipalHASpec `json:"ha,omitempty"`
}

// ArgoCDAgentPProfSpec defines the profiling options of an Argo CD Agent component.
Expand Down Expand Up @@ -1561,6 +1564,48 @@ type SPIFFESpec struct {
CSIDriverName string `json:"csiDriverName,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled && has(self.preferredRole) && self.preferredRole == 'replica') || (has(self.peerAddress) && size(self.peerAddress) > 0)",message="peerAddress must be set when preferredRole is replica"
// PrincipalHASpec defines the active/passive High Availability configuration
// for the Principal component of ArgoCD Agent.
type PrincipalHASpec struct {
// Enabled enables active/passive High Availability for the principal. (default: false)
Enabled *bool `json:"enabled,omitempty"`

// PreferredRole is the startup role of this principal instance.
// "primary" starts in ACTIVE state.
// "replica" starts in SYNCING → REPLICATING state.
// +kubebuilder:validation:Enum=primary;replica
PreferredRole string `json:"preferredRole,omitempty"`

// PeerAddress is the gRPC address (host:port) of the peer principal.
// Required when PreferredRole is "replica".
PeerAddress string `json:"peerAddress,omitempty"`

// PeerTLSSecretName is the name of the TLS secret containing the dedicated
// replication client certificate (tls.crt, tls.key).
// (default: "argocd-agent-ha-peer-tls")
PeerTLSSecretName string `json:"peerTLSSecretName,omitempty"`

// AllowedReplicationClients is the list of mTLS identities permitted to connect for replication.
// When empty, any authenticated peer is allowed.
// The dedicated peer client certificate uses CN=argocd-agent-principal-peer.
AllowedReplicationClients []string `json:"allowedReplicationClients,omitempty"`

// AdminPort is the port for the HAAdmin gRPC server used by
// argocd-agentctl ha status/promote/demote. Access via kubectl port-forward. (default: 8405)
AdminPort *int32 `json:"adminPort,omitempty"`
Comment thread
jparsai marked this conversation as resolved.
Resolved

// AdminAuth is the authorization policy for the HA admin endpoint.
// When TLS is active and this is unset, all admin calls are denied.
// Format: "mtls:subject:<regex>" or "mtls:uri:<regex>".
// Example: "mtls:subject:CN=ha-admin"
AdminAuth string `json:"adminAuth,omitempty"`

// ReplicationInitialAckTimeout is the duration the primary waits for the replica's
// initial acknowledgement after delivering the full snapshot. (default: 5m)
ReplicationInitialAckTimeout string `json:"replicationInitialAckTimeout,omitempty"`
}

type PrincipalServerSpec struct {
// EnableWebSocket is the flag to enable the WebSocket on gRPC to stream events to the Agent.
EnableWebSocket *bool `json:"enableWebSocket,omitempty"`
Expand Down
Loading
Loading