Authenticate with a seeded operator key; fold escalation and switches into TerdutTeam
Credentials: the TerdutServer controller generates <name>-operator-key in the server's own namespace (owned by it) and hands it to the pods as TERDUT_OPERATOR_KEY; the server creates its instance-scoped account from it at every start. A replaced Secret rolls the pods. The bootstrap handshake, the checkpoint Secret, per-team service accounts and credentials Secrets, BootstrapStateLost and credentials.deletionPolicy are gone. CRDs: TerdutServer, TerdutTeam and TerdutAlertSource. TerdutEscalationRule and TerdutDeadmanSwitch become spec.escalation and spec.deadmanSwitches[] on the team (matched by name, extras removed); team invites are removed. A team is created under the identity <namespace>/<name> (external_id), so a retry, a lost status or a deleted team heal by repeating the same call, and a display name owned by another team is TeamNameTaken instead of an adoption. The server resolves escalation usernames (UnknownUser condition). OIDC claim names and trustEmail are spec fields. Fixes: query values are URL-escaped; every delete treats 404 as success; deleting a team no longer depends on allowedTeams consent; a switch or integration deleted on the server is recreated; unnamed switches take the CR's name. Cleanup: scaffold e2e test, AGENTS.md, devcontainer, unused config/ pieces and Client.Version() removed; DESIGN.md, README, ROADMAP and the demo (run-demo.sh, manifests) rewritten for the new design. Secret RBAC stays cluster-wide, now stated in DESIGN.md section 9. Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// TerdutDeadmanSwitchSpec defines the desired state of TerdutDeadmanSwitch.
|
||||
//
|
||||
// One per switch (DESIGN.md §4.4). Reconciled with real update-in-place
|
||||
// (terdut-server v0.33.0 added PUT specifically for this, §5) -- but with no
|
||||
// unique-name constraint server-side, idempotent-create here means
|
||||
// GET-list-and-match-by-name, not adopt-on-409.
|
||||
type TerdutDeadmanSwitchSpec struct {
|
||||
// +required
|
||||
TeamRef TerdutTeamRef `json:"teamRef"`
|
||||
|
||||
// name is optional, same as the API: left empty, terdut-server derives
|
||||
// it from matcher's own canonical form, and that's what the
|
||||
// idempotent-create lookup matches against too.
|
||||
// +optional
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// matcher names the alerts this switch watches, e.g.
|
||||
// "alertname=Watchdog,cluster=prod". One matcher per switch -- add
|
||||
// another TerdutDeadmanSwitch instead of separating with ";"
|
||||
// (terdut-server's own restriction, mirrored here so a bad spec is
|
||||
// rejected at apply time).
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:XValidation:rule="!self.contains(';')",message="one matcher per switch: add another TerdutDeadmanSwitch instead of separating with ;"
|
||||
Matcher string `json:"matcher"`
|
||||
|
||||
// timeout is a Go duration string, e.g. "15m".
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Timeout string `json:"timeout"`
|
||||
|
||||
// +kubebuilder:validation:Enum=critical;error;warning;info
|
||||
// +kubebuilder:default=critical
|
||||
// +optional
|
||||
Severity string `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
// TerdutDeadmanSwitchStatus defines the observed state of TerdutDeadmanSwitch.
|
||||
type TerdutDeadmanSwitchStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
|
||||
// switchID is the server-side id.
|
||||
// +optional
|
||||
SwitchID int64 `json:"switchID,omitempty"`
|
||||
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Team",type=string,JSONPath=`.spec.teamRef.name`
|
||||
// +kubebuilder:printcolumn:name="SwitchID",type=integer,JSONPath=`.status.switchID`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
|
||||
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
|
||||
|
||||
// TerdutDeadmanSwitch is the Schema for the terdutdeadmanswitches API
|
||||
type TerdutDeadmanSwitch struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// metadata is a standard object metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
|
||||
// spec defines the desired state of TerdutDeadmanSwitch
|
||||
// +required
|
||||
Spec TerdutDeadmanSwitchSpec `json:"spec"`
|
||||
|
||||
// status defines the observed state of TerdutDeadmanSwitch
|
||||
// +optional
|
||||
Status TerdutDeadmanSwitchStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TerdutDeadmanSwitchList contains a list of TerdutDeadmanSwitch
|
||||
type TerdutDeadmanSwitchList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
Items []TerdutDeadmanSwitch `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||
s.AddKnownTypes(SchemeGroupVersion, &TerdutDeadmanSwitch{}, &TerdutDeadmanSwitchList{})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// TerdutTeamRef names the TerdutTeam this resource belongs to. Always
|
||||
// same-namespace as the CR itself (DESIGN.md §1: only TerdutTeam.spec.serverRef
|
||||
// crosses namespaces in v1) -- no namespace field, unlike TerdutServerRef.
|
||||
type TerdutTeamRef struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// EscalationTargetKind is who one rung of the ladder pages.
|
||||
// +kubebuilder:validation:Enum=oncall;user
|
||||
type EscalationTargetKind string
|
||||
|
||||
const (
|
||||
EscalationTargetOncall EscalationTargetKind = "oncall"
|
||||
EscalationTargetUser EscalationTargetKind = "user"
|
||||
)
|
||||
|
||||
// EscalationTarget is one page within a level. username is required iff
|
||||
// kind is "user" (terdut-server's own validation, internal/api/escalation.go's
|
||||
// handleSetEscalation -- mirrored here as a CEL rule so a bad spec is
|
||||
// rejected at apply time, not discovered on the next failed PUT).
|
||||
// +kubebuilder:validation:XValidation:rule="self.kind != 'user' || has(self.username)",message="username is required when kind is user"
|
||||
// +kubebuilder:validation:XValidation:rule="self.kind != 'oncall' || !has(self.username)",message="username must not be set when kind is oncall"
|
||||
type EscalationTarget struct {
|
||||
// +required
|
||||
Kind EscalationTargetKind `json:"kind"`
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
// EscalationLevel is one rung of the ladder: how long to wait, and who to
|
||||
// page if nobody's acknowledged by then.
|
||||
type EscalationLevel struct {
|
||||
// timeout is a Go duration string, e.g. "5m".
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Timeout string `json:"timeout"`
|
||||
|
||||
// +required
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Targets []EscalationTarget `json:"targets"`
|
||||
}
|
||||
|
||||
// TerdutEscalationRuleSpec defines the desired state of TerdutEscalationRule.
|
||||
//
|
||||
// One per team (DESIGN.md §4.3) -- terdut-server models a policy as one row
|
||||
// with an owned list of levels, reconciled with a single whole-policy PUT.
|
||||
// Not enforced at admission if two CRs name the same team (no webhooks in
|
||||
// v1, §1); they would simply clobber each other every reconcile.
|
||||
type TerdutEscalationRuleSpec struct {
|
||||
// +required
|
||||
TeamRef TerdutTeamRef `json:"teamRef"`
|
||||
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +kubebuilder:validation:Maximum=10
|
||||
// +optional
|
||||
RepeatCount int64 `json:"repeatCount,omitempty"`
|
||||
|
||||
// +optional
|
||||
FallbackTopic string `json:"fallbackTopic,omitempty"`
|
||||
|
||||
// +required
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Levels []EscalationLevel `json:"levels"`
|
||||
}
|
||||
|
||||
// Condition reasons shared by TerdutEscalationRule and TerdutDeadmanSwitch
|
||||
// (both resolve a teamRef the same way, DESIGN.md §5).
|
||||
const (
|
||||
// ReasonTeamRefNotFound: spec.teamRef names no TerdutTeam (yet).
|
||||
ReasonTeamRefNotFound = "TeamRefNotFound"
|
||||
// ReasonWaitingForTeam: the referenced TerdutTeam exists but isn't
|
||||
// Ready yet (no status.credentialsSecretRef to read).
|
||||
ReasonWaitingForTeam = "WaitingForTeam"
|
||||
// ReasonUnknownUser: an escalation target's username doesn't resolve to
|
||||
// any user server-side (TerdutEscalationRule only).
|
||||
ReasonUnknownUser = "UnknownUser"
|
||||
// ReasonChildAdopted: the happy path, shared by both child kinds.
|
||||
ReasonChildAdopted = "Adopted"
|
||||
)
|
||||
|
||||
// TerdutEscalationRuleStatus defines the observed state of TerdutEscalationRule.
|
||||
type TerdutEscalationRuleStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Team",type=string,JSONPath=`.spec.teamRef.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`
|
||||
|
||||
// TerdutEscalationRule is the Schema for the terdutescalationrules API
|
||||
type TerdutEscalationRule struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// metadata is a standard object metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
|
||||
// spec defines the desired state of TerdutEscalationRule
|
||||
// +required
|
||||
Spec TerdutEscalationRuleSpec `json:"spec"`
|
||||
|
||||
// status defines the observed state of TerdutEscalationRule
|
||||
// +optional
|
||||
Status TerdutEscalationRuleStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TerdutEscalationRuleList contains a list of TerdutEscalationRule
|
||||
type TerdutEscalationRuleList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
Items []TerdutEscalationRule `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||
s.AddKnownTypes(SchemeGroupVersion, &TerdutEscalationRule{}, &TerdutEscalationRuleList{})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -7,12 +7,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// SecretKeyRef names one data key inside a Secret. Every use of this type in
|
||||
// TerdutServerSpec resolves in the TerdutServer's own namespace (it's wired
|
||||
// straight into the Deployment's pod spec as a secretKeyRef env source,
|
||||
// which Kubernetes itself only allows same-namespace) -- unlike the
|
||||
// generated credentials Secret (DESIGN.md §6), which always lives in the
|
||||
// operator's own namespace and is never referenced through this type.
|
||||
// SecretKeyRef names one data key inside a Secret in the TerdutServer's own
|
||||
// namespace. Every use of this type is wired into the Deployment's pod spec as
|
||||
// a secretKeyRef env source, which Kubernetes only allows same-namespace --
|
||||
// including status.credentialsSecretRef, the operator key the controller
|
||||
// generates there.
|
||||
type SecretKeyRef struct {
|
||||
// name is the Secret's name.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
@@ -23,37 +22,6 @@ type SecretKeyRef struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CredentialsDeletionPolicy is what deleting a TerdutServer does to the
|
||||
// instance credential Secret the operator generated for it.
|
||||
// +kubebuilder:validation:Enum=Retain;Delete
|
||||
type CredentialsDeletionPolicy string
|
||||
|
||||
const (
|
||||
// CredentialsRetain keeps the Secret when the TerdutServer is deleted, so
|
||||
// a TerdutServer recreated with the same name and namespace against the
|
||||
// same database adopts it again instead of finding a server it cannot
|
||||
// log in to. Deleting a TerdutServer never touches its database, so the
|
||||
// operator's service account is still there to be reused. The default.
|
||||
CredentialsRetain CredentialsDeletionPolicy = "Retain"
|
||||
// CredentialsDelete removes the Secret with the TerdutServer. Choose it
|
||||
// when the database goes too, or when the credential must not outlive the
|
||||
// object.
|
||||
CredentialsDelete CredentialsDeletionPolicy = "Delete"
|
||||
)
|
||||
|
||||
// CredentialsSpec configures the lifecycle of the generated instance
|
||||
// credential.
|
||||
type CredentialsSpec struct {
|
||||
// deletionPolicy: whether the instance credential Secret is kept
|
||||
// (Retain, the default) or removed (Delete) when this TerdutServer is
|
||||
// deleted. A kept Secret is only ever adopted after the server accepts
|
||||
// its token, so one left over from a database that has since been reset
|
||||
// is ignored and replaced.
|
||||
// +kubebuilder:default=Retain
|
||||
// +optional
|
||||
DeletionPolicy CredentialsDeletionPolicy `json:"deletionPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// ImageSpec is the terdut-server image to run.
|
||||
type ImageSpec struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
@@ -135,18 +103,6 @@ type SweeperSpec struct {
|
||||
ArchiveAfter string `json:"archiveAfter,omitempty"`
|
||||
}
|
||||
|
||||
// DeadmanSpec controls dead man's switch alerts. Matchers/Timeout/Severity
|
||||
// map straight to TERDUT_DEADMAN_MATCHERS/TERDUT_DEADMAN_TIMEOUT/
|
||||
// TERDUT_DEADMAN_SEVERITY.
|
||||
type DeadmanSpec struct {
|
||||
// +optional
|
||||
Matchers string `json:"matchers,omitempty"`
|
||||
// +optional
|
||||
Timeout string `json:"timeout,omitempty"`
|
||||
// +optional
|
||||
Severity string `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
// NotifySpec controls push notifications via ntfy. Empty ntfyURL disables
|
||||
// notifications entirely (matches the chart's own default).
|
||||
type NotifySpec struct {
|
||||
@@ -162,11 +118,7 @@ type NotifySpec struct {
|
||||
TokenSecretRef *SecretKeyRef `json:"tokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCSpec controls single sign-on. Fields the chart also exposes but
|
||||
// DESIGN.md's spec doesn't (usernameClaim, emailClaim, groupsClaim,
|
||||
// trustEmail) use terdut-server's own defaults
|
||||
// (preferred_username/email/groups/false) rather than being added here
|
||||
// speculatively.
|
||||
// OIDCSpec controls single sign-on.
|
||||
type OIDCSpec struct {
|
||||
// +optional
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
@@ -189,6 +141,21 @@ type OIDCSpec struct {
|
||||
// +kubebuilder:default="12h"
|
||||
// +optional
|
||||
SessionMaxAge string `json:"sessionMaxAge,omitempty"`
|
||||
// usernameClaim, emailClaim and groupsClaim name the ID token claims read.
|
||||
// +kubebuilder:default="preferred_username"
|
||||
// +optional
|
||||
UsernameClaim string `json:"usernameClaim,omitempty"`
|
||||
// +kubebuilder:default="email"
|
||||
// +optional
|
||||
EmailClaim string `json:"emailClaim,omitempty"`
|
||||
// +kubebuilder:default="groups"
|
||||
// +optional
|
||||
GroupsClaim string `json:"groupsClaim,omitempty"`
|
||||
// trustEmail links a sign-in to an existing local user by email even when
|
||||
// the provider does not vouch the address is verified (Authentik reports
|
||||
// email_verified false unless told otherwise).
|
||||
// +optional
|
||||
TrustEmail bool `json:"trustEmail,omitempty"`
|
||||
}
|
||||
|
||||
// AllowedTeamsNamespaces gates which namespaces a TerdutTeam may resolve a
|
||||
@@ -352,14 +319,6 @@ type TerdutServerSpec struct {
|
||||
// +optional
|
||||
Sweeper SweeperSpec `json:"sweeper,omitempty"`
|
||||
|
||||
// +optional
|
||||
Deadman DeadmanSpec `json:"deadman,omitempty"`
|
||||
|
||||
// credentials: what happens to the instance credential this operator
|
||||
// generates for the server.
|
||||
// +optional
|
||||
Credentials CredentialsSpec `json:"credentials,omitempty"`
|
||||
|
||||
// +optional
|
||||
Notify NotifySpec `json:"notify,omitempty"`
|
||||
|
||||
@@ -389,15 +348,6 @@ const (
|
||||
// ConditionReady is the standard top-level condition every CRD carries
|
||||
// (DESIGN.md §7).
|
||||
ConditionReady = "Ready"
|
||||
// ConditionDatabaseReady reflects whether the configured database is
|
||||
// usable -- for postgresClusterRef, whether the Zalando CR and its
|
||||
// generated credentials Secret both resolved; for a plain dsn, always
|
||||
// true once set (DESIGN.md §8: "no connectivity check beyond what the
|
||||
// Deployment's own readiness probe already gives").
|
||||
ConditionDatabaseReady = "DatabaseReady"
|
||||
// ConditionBootstrapped reflects whether a working credential has been
|
||||
// acquired via self-registration (DESIGN.md §6).
|
||||
ConditionBootstrapped = "Bootstrapped"
|
||||
)
|
||||
|
||||
// Condition reasons this controller sets.
|
||||
@@ -415,16 +365,6 @@ const (
|
||||
// instead -- but a TerdutServer that explicitly asks for it still needs
|
||||
// to say clearly that it can't be satisfied).
|
||||
ReasonPostgresOperatorCRDNotInstalled = "PostgresOperatorCRDNotInstalled"
|
||||
// ReasonBootstrapStateLost: a checkpointed admin credential
|
||||
// (DESIGN.md §6) was lost after being used but before the lasting
|
||||
// credential it was for could be persisted -- the one genuinely
|
||||
// pathological case in the self-registration flow -- or the server's
|
||||
// database is already bootstrapped and no credential for it survives
|
||||
// (spec.credentials.deletionPolicy: Delete, or the Secret removed by
|
||||
// hand). Fail-closed: the operator cannot mint a credential, and
|
||||
// deleting and recreating the TerdutServer does not clear the database.
|
||||
// Restore the Secret, or reset the server's database.
|
||||
ReasonBootstrapStateLost = "BootstrapStateLost"
|
||||
// ReasonAdopted: the happy path. A working credential is in hand, the
|
||||
// Deployment has a ready replica, and the database (if postgresClusterRef)
|
||||
// resolved.
|
||||
@@ -445,16 +385,9 @@ type TerdutServerStatus struct {
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// serviceName is the Service this controller created for the
|
||||
// Deployment, so other objects can reference it without recomputing the
|
||||
// naming convention.
|
||||
// +optional
|
||||
ServiceName string `json:"serviceName,omitempty"`
|
||||
|
||||
// credentialsSecretRef is the generated instance-scoped credential
|
||||
// (DESIGN.md §6) -- pure output, always in the operator's own
|
||||
// namespace, under a fixed data key ("token"). Set only once
|
||||
// Bootstrapped is True.
|
||||
// credentialsSecretRef is the operator key this controller generated for
|
||||
// the server (TERDUT_OPERATOR_KEY): pure output, in the TerdutServer's own
|
||||
// namespace and owned by it, under the data key "token".
|
||||
// +optional
|
||||
CredentialsSecretRef *SecretKeyRef `json:"credentialsSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
@@ -27,52 +27,15 @@ type TerdutTeamOIDC struct {
|
||||
OwnerGroup string `json:"ownerGroup,omitempty"`
|
||||
}
|
||||
|
||||
// TerdutTeamInvite requests a standing invite link into this team, minted
|
||||
// with the team's own team-scoped credential — requireTeamOwner already
|
||||
// treats that credential as owner-equivalent for every /invites route
|
||||
// (ratified, not a gap, as of terdut-server's SERVICE-ACCOUNTS.md). This is
|
||||
// the real answer to "how does a human ever get a first login on a
|
||||
// password-only, operator-managed install" (terdut-server#23): no signup_mode
|
||||
// flip, no admin token, just a link redeemed the same way anyone else's
|
||||
// invite would be.
|
||||
type TerdutTeamInvite struct {
|
||||
// enabled mints (and keeps refreshed ahead of terdut-server's own fixed
|
||||
// 7-day TTL) an invite link while true. Flipping it back to false
|
||||
// revokes the current one server-side rather than leaving it to expire
|
||||
// on its own.
|
||||
// +optional
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
|
||||
// role is what the invite grants: member or owner. Defaults to member —
|
||||
// owner by default would make every invite link a standing
|
||||
// administrative credential for the team, a much bigger blast radius
|
||||
// than "let a human see the queue".
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=member;owner
|
||||
// +kubebuilder:default=member
|
||||
Role string `json:"role,omitempty"`
|
||||
|
||||
// maxUses bounds how many times this link may be redeemed before it
|
||||
// stops working, mirroring terdut-server's own 1-100 range
|
||||
// (POST /api/teams/{teamID}/invites). Defaults to 1: a link meant for
|
||||
// one specific person, not a standing door.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:validation:Maximum=100
|
||||
// +kubebuilder:default=1
|
||||
MaxUses int64 `json:"maxUses,omitempty"`
|
||||
}
|
||||
|
||||
// TerdutTeamSpec defines the desired state of TerdutTeam.
|
||||
type TerdutTeamSpec struct {
|
||||
// serverRef names the TerdutServer this team belongs to.
|
||||
// +required
|
||||
ServerRef TerdutServerRef `json:"serverRef"`
|
||||
|
||||
// displayName is this team's name, both in terdut-server's own data
|
||||
// (POST /api/teams {"name": ...}) and as the identity POST /api/teams
|
||||
// and GET /api/teams?name= correlate on (DESIGN.md §5's idempotent-
|
||||
// create rule, via TEAM-LOOKUP.md).
|
||||
// displayName is this team's name on the server. It can be changed freely:
|
||||
// the team is found by the CR's own identity (<namespace>/<name>, sent as
|
||||
// external_id), not by this name.
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
DisplayName string `json:"displayName"`
|
||||
@@ -80,8 +43,107 @@ type TerdutTeamSpec struct {
|
||||
// +optional
|
||||
OIDC TerdutTeamOIDC `json:"oidc,omitempty"`
|
||||
|
||||
// escalation is this team's escalation ladder. Omitted, the team has none
|
||||
// (the server's plain reminder behaviour applies).
|
||||
// +optional
|
||||
Invite TerdutTeamInvite `json:"invite,omitempty"`
|
||||
Escalation *EscalationSpec `json:"escalation,omitempty"`
|
||||
|
||||
// deadmanSwitches are this team's dead man's switches, by name. Switches on
|
||||
// the server that are not listed here are removed: in operator mode this
|
||||
// list is the whole truth.
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +kubebuilder:validation:MaxItems=50
|
||||
// +optional
|
||||
DeadmanSwitches []DeadmanSwitchSpec `json:"deadmanSwitches,omitempty"`
|
||||
}
|
||||
|
||||
// EscalationTargetKind is who one rung of the ladder pages.
|
||||
// +kubebuilder:validation:Enum=oncall;user
|
||||
type EscalationTargetKind string
|
||||
|
||||
const (
|
||||
EscalationTargetOncall EscalationTargetKind = "oncall"
|
||||
EscalationTargetUser EscalationTargetKind = "user"
|
||||
)
|
||||
|
||||
// EscalationTarget is one page within a level. username is required iff kind is
|
||||
// "user".
|
||||
// +kubebuilder:validation:XValidation:rule="self.kind != 'user' || has(self.username)",message="username is required when kind is user"
|
||||
// +kubebuilder:validation:XValidation:rule="self.kind != 'oncall' || !has(self.username)",message="username must not be set when kind is oncall"
|
||||
type EscalationTarget struct {
|
||||
// +required
|
||||
Kind EscalationTargetKind `json:"kind"`
|
||||
// +kubebuilder:validation:MaxLength=255
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
// EscalationLevel is one rung of the ladder: how long to wait, and who to page
|
||||
// if nobody has acknowledged by then.
|
||||
type EscalationLevel struct {
|
||||
// timeout is a Go duration string, e.g. "5m".
|
||||
// +required
|
||||
// +kubebuilder:validation:MaxLength=32
|
||||
// +kubebuilder:validation:Pattern=`^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`
|
||||
Timeout string `json:"timeout"`
|
||||
|
||||
// +required
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
// +kubebuilder:validation:MaxItems=20
|
||||
Targets []EscalationTarget `json:"targets"`
|
||||
}
|
||||
|
||||
// EscalationSpec is a team's escalation ladder.
|
||||
type EscalationSpec struct {
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +kubebuilder:validation:Maximum=10
|
||||
// +optional
|
||||
RepeatCount int64 `json:"repeatCount,omitempty"`
|
||||
|
||||
// +optional
|
||||
FallbackTopic string `json:"fallbackTopic,omitempty"`
|
||||
|
||||
// +required
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
// +kubebuilder:validation:MaxItems=10
|
||||
Levels []EscalationLevel `json:"levels"`
|
||||
}
|
||||
|
||||
// DeadmanSwitchSpec is one dead man's switch: the absence of an alert matching
|
||||
// matcher for longer than timeout opens an incident.
|
||||
type DeadmanSwitchSpec struct {
|
||||
// name identifies the switch within the team.
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=100
|
||||
Name string `json:"name"`
|
||||
|
||||
// matcher names the alerts this switch watches, e.g.
|
||||
// "alertname=Watchdog,cluster=prod". One matcher per switch.
|
||||
// +required
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=512
|
||||
// +kubebuilder:validation:XValidation:rule="!self.contains(';')",message="one matcher per switch: add another entry instead of separating with ;"
|
||||
Matcher string `json:"matcher"`
|
||||
|
||||
// timeout is a Go duration string, e.g. "15m".
|
||||
// +required
|
||||
// +kubebuilder:validation:MaxLength=32
|
||||
// +kubebuilder:validation:Pattern=`^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`
|
||||
Timeout string `json:"timeout"`
|
||||
|
||||
// +kubebuilder:validation:Enum=critical;error;warning;info
|
||||
// +kubebuilder:default=critical
|
||||
// +optional
|
||||
Severity string `json:"severity,omitempty"`
|
||||
}
|
||||
|
||||
// TerdutTeamRef names the TerdutTeam a TerdutAlertSource belongs to. Always
|
||||
// same-namespace as the CR itself.
|
||||
type TerdutTeamRef struct {
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Condition reasons this controller sets.
|
||||
@@ -97,21 +159,28 @@ const (
|
||||
// DESIGN.md §5's "every child requeues with backoff, no cross-
|
||||
// controller RPC" rule.
|
||||
ReasonWaitingForServer = "WaitingForServer"
|
||||
// ReasonTeamNameTaken: the server already has a team with spec.displayName
|
||||
// that belongs to a different TerdutTeam (or to a person). The name is global
|
||||
// to the server, so the operator waits for one of them to change.
|
||||
ReasonTeamNameTaken = "TeamNameTaken"
|
||||
// ReasonUnknownUser: an escalation target's username matches no user on the
|
||||
// server (yet).
|
||||
ReasonUnknownUser = "UnknownUser"
|
||||
// ReasonInvalidSpec: a duration in the spec does not parse.
|
||||
ReasonInvalidSpec = "InvalidSpec"
|
||||
// ReasonTeamAdopted: the happy path.
|
||||
ReasonTeamAdopted = "Adopted"
|
||||
)
|
||||
|
||||
// Condition reasons for spec.invite reconciliation (TerdutTeamInvite). Not
|
||||
// surfaced on the Ready condition itself — an invite is a convenience, not
|
||||
// a dependency anything else in this team's own readiness waits on — but
|
||||
// recorded as Events and readable via `kubectl describe`.
|
||||
// Condition reasons shared by the resources that hang off a TerdutTeam
|
||||
// (currently TerdutAlertSource).
|
||||
const (
|
||||
// ReasonInviteMinted: spec.invite.enabled is true and status.inviteSecretRef
|
||||
// is populated and live.
|
||||
ReasonInviteMinted = "InviteMinted"
|
||||
// ReasonInviteRevoked: spec.invite.enabled flipped back to false and the
|
||||
// server-side invite was revoked (or there was nothing to revoke).
|
||||
ReasonInviteRevoked = "InviteRevoked"
|
||||
// ReasonTeamRefNotFound: spec.teamRef names no TerdutTeam (yet).
|
||||
ReasonTeamRefNotFound = "TeamRefNotFound"
|
||||
// ReasonWaitingForTeam: the referenced TerdutTeam exists but is not Ready.
|
||||
ReasonWaitingForTeam = "WaitingForTeam"
|
||||
// ReasonChildAdopted: the happy path.
|
||||
ReasonChildAdopted = "Adopted"
|
||||
)
|
||||
|
||||
// TerdutTeamStatus defines the observed state of TerdutTeam.
|
||||
@@ -126,30 +195,6 @@ type TerdutTeamStatus struct {
|
||||
// +optional
|
||||
TeamID int64 `json:"teamID,omitempty"`
|
||||
|
||||
// credentialsSecretRef is this team's own scoped credential
|
||||
// (DESIGN.md §6 point 3) -- pure output, always in the operator's own
|
||||
// namespace, under a fixed data key ("token").
|
||||
// +optional
|
||||
CredentialsSecretRef *SecretKeyRef `json:"credentialsSecretRef,omitempty"`
|
||||
|
||||
// serverEndpoint is the resolved TerdutServer's base URL, resolved once
|
||||
// here so no child controller (TerdutEscalationRule, TerdutDeadmanSwitch,
|
||||
// TerdutAlertSource) ever needs its own RBAC on terdutservers just to
|
||||
// find out where to send a request (DESIGN.md §5).
|
||||
// +optional
|
||||
ServerEndpoint string `json:"serverEndpoint,omitempty"`
|
||||
|
||||
// inviteSecretRef is this team's current invite link, if spec.invite.enabled.
|
||||
// Unlike credentialsSecretRef, this lives in the TerdutTeam's OWN
|
||||
// namespace, not the operator's: an invite is bounded, limited-use, and
|
||||
// meant for this namespace's own human operators to read and hand out,
|
||||
// not a durable high-privilege credential — same shape as
|
||||
// TerdutAlertSource's status.webhookURLSecretRef, not TerdutServer's
|
||||
// cross-namespace credentialsSecretRef. Nil whenever spec.invite.enabled
|
||||
// is false or unset.
|
||||
// +optional
|
||||
InviteSecretRef *LocalSecretRef `json:"inviteSecretRef,omitempty"`
|
||||
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
}
|
||||
|
||||
@@ -47,21 +47,6 @@ func (in *AllowedTeamsNamespaces) DeepCopy() *AllowedTeamsNamespaces {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialsSpec) DeepCopyInto(out *CredentialsSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialsSpec.
|
||||
func (in *CredentialsSpec) DeepCopy() *CredentialsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CredentialsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DatabaseSpec) DeepCopyInto(out *DatabaseSpec) {
|
||||
*out = *in
|
||||
@@ -88,16 +73,16 @@ func (in *DatabaseSpec) DeepCopy() *DatabaseSpec {
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeadmanSpec) DeepCopyInto(out *DeadmanSpec) {
|
||||
func (in *DeadmanSwitchSpec) DeepCopyInto(out *DeadmanSwitchSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeadmanSpec.
|
||||
func (in *DeadmanSpec) DeepCopy() *DeadmanSpec {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeadmanSwitchSpec.
|
||||
func (in *DeadmanSwitchSpec) DeepCopy() *DeadmanSwitchSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeadmanSpec)
|
||||
out := new(DeadmanSwitchSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -122,6 +107,28 @@ func (in *EscalationLevel) DeepCopy() *EscalationLevel {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EscalationSpec) DeepCopyInto(out *EscalationSpec) {
|
||||
*out = *in
|
||||
if in.Levels != nil {
|
||||
in, out := &in.Levels, &out.Levels
|
||||
*out = make([]EscalationLevel, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EscalationSpec.
|
||||
func (in *EscalationSpec) DeepCopy() *EscalationSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EscalationSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EscalationTarget) DeepCopyInto(out *EscalationTarget) {
|
||||
*out = *in
|
||||
@@ -496,207 +503,6 @@ func (in *TerdutAlertSourceStatus) DeepCopy() *TerdutAlertSourceStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutDeadmanSwitch) DeepCopyInto(out *TerdutDeadmanSwitch) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutDeadmanSwitch.
|
||||
func (in *TerdutDeadmanSwitch) DeepCopy() *TerdutDeadmanSwitch {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutDeadmanSwitch)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TerdutDeadmanSwitch) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutDeadmanSwitchList) DeepCopyInto(out *TerdutDeadmanSwitchList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]TerdutDeadmanSwitch, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutDeadmanSwitchList.
|
||||
func (in *TerdutDeadmanSwitchList) DeepCopy() *TerdutDeadmanSwitchList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutDeadmanSwitchList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TerdutDeadmanSwitchList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutDeadmanSwitchSpec) DeepCopyInto(out *TerdutDeadmanSwitchSpec) {
|
||||
*out = *in
|
||||
out.TeamRef = in.TeamRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutDeadmanSwitchSpec.
|
||||
func (in *TerdutDeadmanSwitchSpec) DeepCopy() *TerdutDeadmanSwitchSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutDeadmanSwitchSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutDeadmanSwitchStatus) DeepCopyInto(out *TerdutDeadmanSwitchStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutDeadmanSwitchStatus.
|
||||
func (in *TerdutDeadmanSwitchStatus) DeepCopy() *TerdutDeadmanSwitchStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutDeadmanSwitchStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutEscalationRule) DeepCopyInto(out *TerdutEscalationRule) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutEscalationRule.
|
||||
func (in *TerdutEscalationRule) DeepCopy() *TerdutEscalationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutEscalationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TerdutEscalationRule) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutEscalationRuleList) DeepCopyInto(out *TerdutEscalationRuleList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]TerdutEscalationRule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutEscalationRuleList.
|
||||
func (in *TerdutEscalationRuleList) DeepCopy() *TerdutEscalationRuleList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutEscalationRuleList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TerdutEscalationRuleList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutEscalationRuleSpec) DeepCopyInto(out *TerdutEscalationRuleSpec) {
|
||||
*out = *in
|
||||
out.TeamRef = in.TeamRef
|
||||
if in.Levels != nil {
|
||||
in, out := &in.Levels, &out.Levels
|
||||
*out = make([]EscalationLevel, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutEscalationRuleSpec.
|
||||
func (in *TerdutEscalationRuleSpec) DeepCopy() *TerdutEscalationRuleSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutEscalationRuleSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutEscalationRuleStatus) DeepCopyInto(out *TerdutEscalationRuleStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutEscalationRuleStatus.
|
||||
func (in *TerdutEscalationRuleStatus) DeepCopy() *TerdutEscalationRuleStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutEscalationRuleStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutServer) DeepCopyInto(out *TerdutServer) {
|
||||
*out = *in
|
||||
@@ -778,8 +584,6 @@ func (in *TerdutServerSpec) DeepCopyInto(out *TerdutServerSpec) {
|
||||
out.Networking = in.Networking
|
||||
in.Database.DeepCopyInto(&out.Database)
|
||||
out.Sweeper = in.Sweeper
|
||||
out.Deadman = in.Deadman
|
||||
out.Credentials = in.Credentials
|
||||
in.Notify.DeepCopyInto(&out.Notify)
|
||||
in.OIDC.DeepCopyInto(&out.OIDC)
|
||||
in.AllowedTeams.DeepCopyInto(&out.AllowedTeams)
|
||||
@@ -828,7 +632,7 @@ func (in *TerdutTeam) DeepCopyInto(out *TerdutTeam) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
@@ -850,21 +654,6 @@ func (in *TerdutTeam) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutTeamInvite) DeepCopyInto(out *TerdutTeamInvite) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutTeamInvite.
|
||||
func (in *TerdutTeamInvite) DeepCopy() *TerdutTeamInvite {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TerdutTeamInvite)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TerdutTeamList) DeepCopyInto(out *TerdutTeamList) {
|
||||
*out = *in
|
||||
@@ -932,7 +721,16 @@ func (in *TerdutTeamSpec) DeepCopyInto(out *TerdutTeamSpec) {
|
||||
*out = *in
|
||||
out.ServerRef = in.ServerRef
|
||||
out.OIDC = in.OIDC
|
||||
out.Invite = in.Invite
|
||||
if in.Escalation != nil {
|
||||
in, out := &in.Escalation, &out.Escalation
|
||||
*out = new(EscalationSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.DeadmanSwitches != nil {
|
||||
in, out := &in.DeadmanSwitches, &out.DeadmanSwitches
|
||||
*out = make([]DeadmanSwitchSpec, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutTeamSpec.
|
||||
@@ -955,16 +753,6 @@ func (in *TerdutTeamStatus) DeepCopyInto(out *TerdutTeamStatus) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.CredentialsSecretRef != nil {
|
||||
in, out := &in.CredentialsSecretRef, &out.CredentialsSecretRef
|
||||
*out = new(SecretKeyRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.InviteSecretRef != nil {
|
||||
in, out := &in.InviteSecretRef, &out.InviteSecretRef
|
||||
*out = new(LocalSecretRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerdutTeamStatus.
|
||||
|
||||
Reference in New Issue
Block a user