Skip to content
Merged
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 .coverage-baseline
Original file line number Diff line number Diff line change
@@ -1 +1 @@
77.1
77.2
24 changes: 0 additions & 24 deletions api/v1alpha3/clusterprovider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,6 @@ import (
// every other provider's, and it is never created by the operator.
const DefaultClusterProviderName = "default"

// ClusterProviderReference references the cluster-scoped ClusterProvider a GitTarget sources
// FROM. It is the read-side peer of GitProviderReference (which names the WRITE destination):
// a GitTarget names one ClusterProvider by name and its author-attribution facts, kube client,
// and namespace authorization all follow from that single reference. Group and Kind are typed
// (with defaults) for consistency with the project's other typed references.
type ClusterProviderReference struct {
// API Group of the referent.
// +kubebuilder:default=configbutler.ai
// +kubebuilder:validation:Enum=configbutler.ai
Group string `json:"group,omitempty"`

// Kind of the referent.
// Optional because this reference currently only supports a single kind (ClusterProvider).
// +optional
// +kubebuilder:validation:Enum=ClusterProvider
// +kubebuilder:default=ClusterProvider
Kind string `json:"kind,omitempty"`

// Name of the referent.
// +required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}

// ClusterProviderSpec defines the desired state of ClusterProvider.
//
// kubeConfig is IMMUTABLE and OPTIONAL: which physical cluster a provider name means must not
Expand Down
80 changes: 12 additions & 68 deletions api/v1alpha3/clusterwatchrule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
package v1alpha3

import (
meta "github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ResourceScope names a Kubernetes resource's scope. It is an INTERNAL matching vocabulary: the
// resolver uses both constants to align a rule's selector with the discovered scope of each type
// (a WatchRule always resolves Namespaced records, a ClusterWatchRule always Cluster ones). The
// only field that still exposes it — ClusterResourceRule.scope — is narrowed to Cluster alone, so
// "Namespaced" is no longer a public choice anywhere in the API.
// ResourceScope names a Kubernetes resource's scope. It is a purely INTERNAL matching vocabulary
// with no field in any CRD: the resolver uses both constants to align a rule's selector with the
// discovered scope of each type. A WatchRule always resolves Namespaced records, a ClusterWatchRule
// always Cluster ones, and which kind you write is the whole of how scope is chosen.
type ResourceScope string

const (
Expand All @@ -21,37 +21,14 @@ const (
ResourceScopeNamespaced ResourceScope = "Namespaced"
)

type NamespacedTargetReference struct {
// API Group of the referent.
// +kubebuilder:validation:Enum=configbutler.ai
// +kubebuilder:default=configbutler.ai
Group string `json:"group,omitempty"`

// Kind of the referent.
// Optional because this reference currently only supports a single kind (GitTarget).
// Keeping it optional allows users to omit it while still benefiting from CRD defaulting.
// +optional
// +kubebuilder:validation:Enum=GitTarget
// +kubebuilder:default=GitTarget
Kind string `json:"kind,omitempty"`

// Name of the referent.
// +required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`

// Required because ClusterWatchRule has no namespace.
// +required
// +kubebuilder:validation:MinLength=1
Namespace string `json:"namespace"`
}

// ClusterWatchRuleSpec defines the desired state of ClusterWatchRule.
type ClusterWatchRuleSpec struct {
// TargetRef references the GitTarget to use.
// Must specify namespace.
// GitTargetRef names the GitTarget this rule feeds. A ClusterWatchRule has no namespace of its
// own, so the namespace is required here rather than defaulted.
// +required
TargetRef NamespacedTargetReference `json:"targetRef"`
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.gitTargetRef.name must not be empty"
// +kubebuilder:validation:XValidation:rule="has(self.namespace) && self.namespace != ''",message="spec.gitTargetRef.namespace is required: a ClusterWatchRule has no namespace to default to"
GitTargetRef meta.NamespacedObjectReference `json:"gitTargetRef"`

// Rules define which CLUSTER-SCOPED resources to watch.
// Multiple rules create a logical OR - a resource matching ANY rule is watched.
Expand Down Expand Up @@ -110,39 +87,6 @@ type ClusterResourceRule struct {
// +kubebuilder:validation:items:MinLength=1
// +kubebuilder:validation:items:Pattern=`^[^/]*$`
Resources []string `json:"resources"`

// Retained in the schema purely so re-applying a manifest that still says "Namespaced" FAILS.
// Deleting it would be silent twice over: pruning drops the value without an error, and a
// stored pre-release object would keep its value with no Go field left to refuse it.

// Scope is REMOVED as a choice: a ClusterWatchRule is cluster-scoped only, so "Cluster" is the
// only accepted value and also the default, making the field omittable. To watch NAMESPACED
// resources, use a WatchRule in the tenant namespace and set spec.rules[].sourceNamespace.
//
// Deprecated: ClusterWatchRule is cluster-scope-only; use WatchRule with
// spec.rules[].sourceNamespace for namespaced resources. Removed one release from now, or at
// v1beta1.
// +optional
// +kubebuilder:default=Cluster
// +kubebuilder:validation:Enum=Cluster
Scope ResourceScope `json:"scope,omitempty"`
}

// DeclaresNamespacedScope reports whether a STORED ClusterWatchRule still selects namespaced
// resources through the removed scope choice. Admission rejects the value, but an object written
// before this release keeps it in etcd, so the compile path must refuse it rather than let the
// rule resolve as if it had asked for cluster scope.
//
// It keys on the STORED value, not on what the selector happens to resolve: `resources: ["*"]`
// legitimately resolves cluster-scoped records, so inferring the refusal from the resolution would
// be ambiguous exactly where it matters.
func (s *ClusterWatchRuleSpec) DeclaresNamespacedScope() bool {
for i := range s.Rules {
if s.Rules[i].Scope != "" && s.Rules[i].Scope != ResourceScopeCluster {
return true
}
}
return false
}

// ClusterWatchRuleStatus defines the observed state of ClusterWatchRule.
Expand Down Expand Up @@ -171,7 +115,7 @@ type ClusterWatchRuleStatus struct {
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Target",type=string,JSONPath=`.spec.targetRef.name`
// +kubebuilder:printcolumn:name="Target",type=string,JSONPath=`.spec.gitTargetRef.name`
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
// +kubebuilder:printcolumn:name="Streams",type=string,JSONPath=`.status.streams.summary`
Expand All @@ -183,7 +127,7 @@ type ClusterWatchRuleStatus struct {
// from — Nodes, PersistentVolumes, StorageClasses, ClusterRoles, CRDs, and the like. Scope is
// carried by the rule KIND, so it has no per-rule scope choice and no source-namespace selection.
//
// It is cluster-scoped and requires cluster-admin permissions. Its targetRef names a GitTarget
// It is cluster-scoped and requires cluster-admin permissions. Its gitTargetRef names a GitTarget
// (namespace required), whose namespace must be admitted by that target's ClusterProvider. To
// mirror NAMESPACED resources use a WatchRule in the tenant namespace and set
// spec.rules[].sourceNamespace, whose "*" reaches every namespace the source credential can read.
Expand Down
8 changes: 5 additions & 3 deletions api/v1alpha3/commitrequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package v1alpha3

import (
meta "github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -13,10 +14,11 @@ import (
//
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="CommitRequest spec is immutable after creation"
type CommitRequestSpec struct {
// TargetRef names the GitTarget whose open commit window to finalize.
// GitTargetRef names the GitTarget whose open commit window to finalize.
// The GitTarget must be in the same namespace as this CommitRequest.
// +required
TargetRef LocalTargetReference `json:"targetRef"`
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.gitTargetRef.name must not be empty"
GitTargetRef meta.LocalObjectReference `json:"gitTargetRef"`

// Message is an optional commit message for the finalized commit. When
// omitted, the generated grouped-commit message is used.
Expand Down Expand Up @@ -87,7 +89,7 @@ type CommitRequestStatus struct {

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="GitTarget",type=string,JSONPath=`.spec.targetRef.name`
// +kubebuilder:printcolumn:name="GitTarget",type=string,JSONPath=`.spec.gitTargetRef.name`
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
// +kubebuilder:printcolumn:name="SHA",type=string,JSONPath=`.status.sha`
Expand Down
34 changes: 13 additions & 21 deletions api/v1alpha3/gitprovider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package v1alpha3

import (
meta "github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -25,7 +26,14 @@ type GitProviderSpec struct {
URL string `json:"url"`

// SecretRef for authentication credentials (may be nil for public repos)
SecretRef *LocalSecretReference `json:"secretRef,omitempty"`
//
// An OMITTED secretRef means anonymous access. An empty name is refused rather than treated as
// one: a typo there would otherwise downgrade a private repository to anonymous silently, and
// the failure would surface as an authentication error against the remote rather than as a
// mistake in this object.
// +optional
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.secretRef.name must not be empty; omit secretRef entirely for anonymous access"
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

// KnownHostsRef optionally points at a namespace-local ConfigMap or Secret holding SSH
// known_hosts, so host trust can be centralized across GitProviders on the same host instead
Expand All @@ -48,24 +56,6 @@ type GitProviderSpec struct {
Commit *CommitSpec `json:"commit,omitempty"`
}

// LocalSecretReference is a typed reference to a Secret in the same namespace.
type LocalSecretReference struct {
// Group of the referent.
// +kubebuilder:default=""
// +optional
Group string `json:"group,omitempty"`

// Kind of the referent.
// +kubebuilder:validation:Enum=Secret
// +kubebuilder:default=Secret
// +optional
Kind string `json:"kind,omitempty"`

// Name of the Secret.
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}

// KnownHostsReference points at a namespace-local ConfigMap or Secret that holds SSH known_hosts
// host-trust material. The data is read from the "known_hosts" key, falling back to
// "ssh_known_hosts" (the key Argo CD's argocd-ssh-known-hosts-cm ConfigMap uses, for host keys
Expand All @@ -91,8 +81,9 @@ type EncryptionSpec struct {
Provider string `json:"provider"`

// SecretRef references namespace-local Secret data used by the encryption provider.
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="encryption.secretRef.name must not be empty"
// +optional
SecretRef LocalSecretReference `json:"secretRef,omitempty"`
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`

// Age configures age-specific encryption behavior for SOPS.
// +optional
Expand Down Expand Up @@ -213,8 +204,9 @@ type CommitMessageSpec struct {
// CommitSigningSpec configures commit signing.
type CommitSigningSpec struct {
// SecretRef references the Secret containing the signing key material.
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="commit.signing.secretRef.name must not be empty"
// Expected keys will be defined by the signing implementation.
SecretRef LocalSecretReference `json:"secretRef"`
SecretRef meta.LocalObjectReference `json:"secretRef"`

// GenerateWhenMissing causes the operator to generate signing key material
// in the referenced Secret when it is missing.
Expand Down
50 changes: 18 additions & 32 deletions api/v1alpha3/gittarget_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,13 @@
package v1alpha3

import (
meta "github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GitProviderReference references the GitProvider that backs a GitTarget. Many GitTargets may
// reference the same GitProvider; the reference is always to a GitProvider in the GitTarget's own
// namespace. Group and Kind are typed (with defaults) for consistency with the project's other
// local references and so the schema is explicit about what it accepts — currently only
// configbutler.ai/GitProvider.
type GitProviderReference struct {
// API Group of the referent.
// +kubebuilder:default=configbutler.ai
// +kubebuilder:validation:Enum=configbutler.ai
Group string `json:"group,omitempty"`

// Kind of the referent.
// Optional because this reference currently only supports a single kind (GitProvider).
// Keeping it optional allows users to omit it while still benefiting from CRD defaulting.
// +optional
// +kubebuilder:validation:Enum=GitProvider
// +kubebuilder:default=GitProvider
Kind string `json:"kind,omitempty"`

// Name of the referent.
// +required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}

// GitTargetSpec defines the desired state of GitTarget.
//
// The destination fields — providerRef, branch, and path — are immutable. A
// The destination fields — gitProviderRef, branch, and path — are immutable. A
// GitTarget materializes the watched resources at exactly one (provider, branch,
// folder); changing where it writes would orphan the old materialization and require
// migrating manifests between repositories/branches/folders. Instead of reconciling
Expand All @@ -42,20 +18,29 @@ type GitProviderReference struct {
// simple — a successful snapshot can never be silently invalidated by a destination
// change.
//
// +kubebuilder:validation:XValidation:rule="self.providerRef == oldSelf.providerRef",message="spec.providerRef is immutable; delete and recreate the GitTarget to change its destination"
// The gitProviderRef rule is guarded on oldSelf rather than written as a plain equality, and the
// guard is a one-way migration door rather than a loosening. A GitTarget stored before this field
// was named gitProviderRef serves NO value for it (a field outside the structural schema is not
// served, measured in TestRenamedRequiredField_StoredObjectCanAdoptIt), so a plain equality would
// reject the very apply that migrates it and force a delete-and-recreate. Because the field is
// REQUIRED, it can never be absent on an object created from this release on, so the door is shut
// for everything except the objects it exists for.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.gitProviderRef) || self.gitProviderRef == oldSelf.gitProviderRef",message="spec.gitProviderRef is immutable; delete and recreate the GitTarget to change its destination"
// +kubebuilder:validation:XValidation:rule="self.branch == oldSelf.branch",message="spec.branch is immutable; delete and recreate the GitTarget to change its destination"
// +kubebuilder:validation:XValidation:rule="self.path == oldSelf.path",message="spec.path is immutable; delete and recreate the GitTarget to change its destination"
//
// spec.clusterProviderRef names the SOURCE cluster a GitTarget mirrors FROM (see its field doc). It
// is immutable — a folder's source cluster is part of what the folder means, like
// providerRef/branch/path above — and defaults to a ClusterProvider named "default", so it is
// gitProviderRef/branch/path above — and defaults to a ClusterProvider named "default", so it is
// always populated (never nil) and always jumpable.
// +kubebuilder:validation:XValidation:rule="self.clusterProviderRef == oldSelf.clusterProviderRef",message="spec.clusterProviderRef is immutable; delete and recreate the GitTarget to change the cluster it mirrors"
type GitTargetSpec struct {
// ProviderRef references the GitProvider that backs this target.
// GitProviderRef names the GitProvider that backs this target, in this GitTarget's own
// namespace. Many GitTargets may name the same GitProvider.
// Immutable: delete and recreate the GitTarget to change its destination.
// +required
ProviderRef GitProviderReference `json:"providerRef"`
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.gitProviderRef.name must not be empty"
GitProviderRef meta.LocalObjectReference `json:"gitProviderRef"`

// Branch to use for this target.
// Must be one of the allowed branches in the provider.
Expand Down Expand Up @@ -128,7 +113,8 @@ type GitTargetSpec struct {
// Immutable: a folder's source cluster is part of what the folder means; delete and recreate.
// +kubebuilder:default={name: "default"}
// +optional
ClusterProviderRef *ClusterProviderReference `json:"clusterProviderRef,omitempty"`
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="spec.clusterProviderRef.name must not be empty"
ClusterProviderRef *meta.LocalObjectReference `json:"clusterProviderRef,omitempty"`

// Mutable, unlike the destination fields above: recovering from a scope mistake must not
// require recreating the GitTarget, which is the one operation that loses the folder's history.
Expand Down Expand Up @@ -447,7 +433,7 @@ type GitTargetRetentionStatus struct {
// +kubebuilder:printcolumn:name="ClusterProviderReady",type=string,JSONPath=`.status.conditions[?(@.type=="ClusterProviderReady")].status`,priority=1
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].message`,priority=1
// +kubebuilder:printcolumn:name="Encryption",type=string,JSONPath=`.spec.encryption.provider`,priority=1
// +kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.providerRef.name`,priority=1
// +kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.gitProviderRef.name`,priority=1
// +kubebuilder:printcolumn:name="Branch",type=string,JSONPath=`.spec.branch`,priority=1
// +kubebuilder:printcolumn:name="Path",type=string,JSONPath=`.spec.path`,priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
Expand Down
Loading
Loading