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:
@@ -76,9 +76,8 @@ spec:
|
||||
type: string
|
||||
teamRef:
|
||||
description: |-
|
||||
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.
|
||||
TerdutTeamRef names the TerdutTeam a TerdutAlertSource belongs to. Always
|
||||
same-namespace as the CR itself.
|
||||
properties:
|
||||
name:
|
||||
minLength: 1
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.22.0
|
||||
name: terdutdeadmanswitches.terdut.ryuvia.com
|
||||
spec:
|
||||
group: terdut.ryuvia.com
|
||||
names:
|
||||
kind: TerdutDeadmanSwitch
|
||||
listKind: TerdutDeadmanSwitchList
|
||||
plural: terdutdeadmanswitches
|
||||
singular: terdutdeadmanswitch
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.teamRef.name
|
||||
name: Team
|
||||
type: string
|
||||
- jsonPath: .status.switchID
|
||||
name: SwitchID
|
||||
type: integer
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].status
|
||||
name: Ready
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
|
||||
name: Reason
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TerdutDeadmanSwitch is the Schema for the terdutdeadmanswitches
|
||||
API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: spec defines the desired state of TerdutDeadmanSwitch
|
||||
properties:
|
||||
matcher:
|
||||
description: |-
|
||||
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).
|
||||
minLength: 1
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: 'one matcher per switch: add another TerdutDeadmanSwitch
|
||||
instead of separating with ;'
|
||||
rule: '!self.contains('';'')'
|
||||
name:
|
||||
description: |-
|
||||
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.
|
||||
type: string
|
||||
severity:
|
||||
default: critical
|
||||
enum:
|
||||
- critical
|
||||
- error
|
||||
- warning
|
||||
- info
|
||||
type: string
|
||||
teamRef:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
name:
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
timeout:
|
||||
description: timeout is a Go duration string, e.g. "15m".
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- matcher
|
||||
- teamRef
|
||||
- timeout
|
||||
type: object
|
||||
status:
|
||||
description: status defines the observed state of TerdutDeadmanSwitch
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
switchID:
|
||||
description: switchID is the server-side id.
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,191 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.22.0
|
||||
name: terdutescalationrules.terdut.ryuvia.com
|
||||
spec:
|
||||
group: terdut.ryuvia.com
|
||||
names:
|
||||
kind: TerdutEscalationRule
|
||||
listKind: TerdutEscalationRuleList
|
||||
plural: terdutescalationrules
|
||||
singular: terdutescalationrule
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.teamRef.name
|
||||
name: Team
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].status
|
||||
name: Ready
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
|
||||
name: Reason
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TerdutEscalationRule is the Schema for the terdutescalationrules
|
||||
API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: spec defines the desired state of TerdutEscalationRule
|
||||
properties:
|
||||
fallbackTopic:
|
||||
type: string
|
||||
levels:
|
||||
items:
|
||||
description: |-
|
||||
EscalationLevel is one rung of the ladder: how long to wait, and who to
|
||||
page if nobody's acknowledged by then.
|
||||
properties:
|
||||
targets:
|
||||
items:
|
||||
description: |-
|
||||
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).
|
||||
properties:
|
||||
kind:
|
||||
description: EscalationTargetKind is who one rung of the
|
||||
ladder pages.
|
||||
enum:
|
||||
- oncall
|
||||
- user
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: username is required when kind is user
|
||||
rule: self.kind != 'user' || has(self.username)
|
||||
- message: username must not be set when kind is oncall
|
||||
rule: self.kind != 'oncall' || !has(self.username)
|
||||
minItems: 1
|
||||
type: array
|
||||
timeout:
|
||||
description: timeout is a Go duration string, e.g. "5m".
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- targets
|
||||
- timeout
|
||||
type: object
|
||||
minItems: 1
|
||||
type: array
|
||||
repeatCount:
|
||||
format: int64
|
||||
maximum: 10
|
||||
minimum: 0
|
||||
type: integer
|
||||
teamRef:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
name:
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- levels
|
||||
- teamRef
|
||||
type: object
|
||||
status:
|
||||
description: status defines the observed state of TerdutEscalationRule
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -125,24 +125,6 @@ spec:
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
type: object
|
||||
credentials:
|
||||
description: |-
|
||||
credentials: what happens to the instance credential this operator
|
||||
generates for the server.
|
||||
properties:
|
||||
deletionPolicy:
|
||||
default: Retain
|
||||
description: |-
|
||||
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.
|
||||
enum:
|
||||
- Retain
|
||||
- Delete
|
||||
type: string
|
||||
type: object
|
||||
database:
|
||||
description: |-
|
||||
DatabaseSpec is the Postgres connection this TerdutServer uses. Exactly
|
||||
@@ -193,19 +175,6 @@ spec:
|
||||
- message: exactly one of dsn or postgresClusterRef must be set
|
||||
rule: '(has(self.dsn) ? 1 : 0) + (has(self.postgresClusterRef) ?
|
||||
1 : 0) == 1'
|
||||
deadman:
|
||||
description: |-
|
||||
DeadmanSpec controls dead man's switch alerts. Matchers/Timeout/Severity
|
||||
map straight to TERDUT_DEADMAN_MATCHERS/TERDUT_DEADMAN_TIMEOUT/
|
||||
TERDUT_DEADMAN_SEVERITY.
|
||||
properties:
|
||||
matchers:
|
||||
type: string
|
||||
severity:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
type: object
|
||||
image:
|
||||
description: ImageSpec is the terdut-server image to run.
|
||||
properties:
|
||||
@@ -277,12 +246,7 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
oidc:
|
||||
description: |-
|
||||
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.
|
||||
description: OIDCSpec controls single sign-on.
|
||||
properties:
|
||||
adminGroup:
|
||||
type: string
|
||||
@@ -294,12 +258,11 @@ spec:
|
||||
type: string
|
||||
clientSecretRef:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
key:
|
||||
description: key is the data key inside the Secret holding
|
||||
@@ -314,8 +277,14 @@ spec:
|
||||
- key
|
||||
- name
|
||||
type: object
|
||||
emailClaim:
|
||||
default: email
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
groupsClaim:
|
||||
default: groups
|
||||
type: string
|
||||
issuer:
|
||||
type: string
|
||||
name:
|
||||
@@ -327,6 +296,17 @@ spec:
|
||||
sessionMaxAge:
|
||||
default: 12h
|
||||
type: string
|
||||
trustEmail:
|
||||
description: |-
|
||||
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).
|
||||
type: boolean
|
||||
usernameClaim:
|
||||
default: preferred_username
|
||||
description: usernameClaim, emailClaim and groupsClaim name the
|
||||
ID token claims read.
|
||||
type: string
|
||||
type: object
|
||||
passwordLogin:
|
||||
default: true
|
||||
@@ -4414,10 +4394,9 @@ spec:
|
||||
x-kubernetes-list-type: map
|
||||
credentialsSecretRef:
|
||||
description: |-
|
||||
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".
|
||||
properties:
|
||||
key:
|
||||
description: key is the data key inside the Secret holding the
|
||||
@@ -4439,12 +4418,6 @@ spec:
|
||||
tells "applied" from "seen" (DESIGN.md §7).
|
||||
format: int64
|
||||
type: integer
|
||||
serviceName:
|
||||
description: |-
|
||||
serviceName is the Service this controller created for the
|
||||
Deployment, so other objects can reference it without recomputing the
|
||||
naming convention.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
@@ -52,54 +52,121 @@ spec:
|
||||
spec:
|
||||
description: spec defines the desired state of TerdutTeam
|
||||
properties:
|
||||
deadmanSwitches:
|
||||
description: |-
|
||||
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.
|
||||
items:
|
||||
description: |-
|
||||
DeadmanSwitchSpec is one dead man's switch: the absence of an alert matching
|
||||
matcher for longer than timeout opens an incident.
|
||||
properties:
|
||||
matcher:
|
||||
description: |-
|
||||
matcher names the alerts this switch watches, e.g.
|
||||
"alertname=Watchdog,cluster=prod". One matcher per switch.
|
||||
maxLength: 512
|
||||
minLength: 1
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: 'one matcher per switch: add another entry instead
|
||||
of separating with ;'
|
||||
rule: '!self.contains('';'')'
|
||||
name:
|
||||
description: name identifies the switch within the team.
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
severity:
|
||||
default: critical
|
||||
enum:
|
||||
- critical
|
||||
- error
|
||||
- warning
|
||||
- info
|
||||
type: string
|
||||
timeout:
|
||||
description: timeout is a Go duration string, e.g. "15m".
|
||||
maxLength: 32
|
||||
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
|
||||
type: string
|
||||
required:
|
||||
- matcher
|
||||
- name
|
||||
- timeout
|
||||
type: object
|
||||
maxItems: 50
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
displayName:
|
||||
description: |-
|
||||
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.
|
||||
minLength: 1
|
||||
type: string
|
||||
invite:
|
||||
escalation:
|
||||
description: |-
|
||||
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.
|
||||
escalation is this team's escalation ladder. Omitted, the team has none
|
||||
(the server's plain reminder behaviour applies).
|
||||
properties:
|
||||
enabled:
|
||||
description: |-
|
||||
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.
|
||||
type: boolean
|
||||
maxUses:
|
||||
default: 1
|
||||
description: |-
|
||||
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.
|
||||
format: int64
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
type: integer
|
||||
role:
|
||||
default: member
|
||||
description: |-
|
||||
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".
|
||||
enum:
|
||||
- member
|
||||
- owner
|
||||
fallbackTopic:
|
||||
type: string
|
||||
levels:
|
||||
items:
|
||||
description: |-
|
||||
EscalationLevel is one rung of the ladder: how long to wait, and who to page
|
||||
if nobody has acknowledged by then.
|
||||
properties:
|
||||
targets:
|
||||
items:
|
||||
description: |-
|
||||
EscalationTarget is one page within a level. username is required iff kind is
|
||||
"user".
|
||||
properties:
|
||||
kind:
|
||||
description: EscalationTargetKind is who one rung
|
||||
of the ladder pages.
|
||||
enum:
|
||||
- oncall
|
||||
- user
|
||||
type: string
|
||||
username:
|
||||
maxLength: 255
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: username is required when kind is user
|
||||
rule: self.kind != 'user' || has(self.username)
|
||||
- message: username must not be set when kind is oncall
|
||||
rule: self.kind != 'oncall' || !has(self.username)
|
||||
maxItems: 20
|
||||
minItems: 1
|
||||
type: array
|
||||
timeout:
|
||||
description: timeout is a Go duration string, e.g. "5m".
|
||||
maxLength: 32
|
||||
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
|
||||
type: string
|
||||
required:
|
||||
- targets
|
||||
- timeout
|
||||
type: object
|
||||
maxItems: 10
|
||||
minItems: 1
|
||||
type: array
|
||||
repeatCount:
|
||||
format: int64
|
||||
maximum: 10
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- levels
|
||||
type: object
|
||||
oidc:
|
||||
description: |-
|
||||
@@ -190,53 +257,9 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
credentialsSecretRef:
|
||||
description: |-
|
||||
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").
|
||||
properties:
|
||||
key:
|
||||
description: key is the data key inside the Secret holding the
|
||||
raw value.
|
||||
minLength: 1
|
||||
type: string
|
||||
name:
|
||||
description: name is the Secret's name.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
type: object
|
||||
inviteSecretRef:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
name:
|
||||
description: name is the Secret's name.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
serverEndpoint:
|
||||
description: |-
|
||||
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).
|
||||
type: string
|
||||
teamID:
|
||||
description: |-
|
||||
teamID is the server-side id -- needed by every child object's
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
resources:
|
||||
- bases/terdut.ryuvia.com_terdutservers.yaml
|
||||
- bases/terdut.ryuvia.com_terdutteams.yaml
|
||||
- bases/terdut.ryuvia.com_terdutescalationrules.yaml
|
||||
- bases/terdut.ryuvia.com_terdutdeadmanswitches.yaml
|
||||
- bases/terdut.ryuvia.com_terdutalertsources.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
|
||||
Reference in New Issue
Block a user