--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.22.0 name: terdutteams.terdut.ryuvia.com spec: group: terdut.ryuvia.com names: kind: TerdutTeam listKind: TerdutTeamList plural: terdutteams singular: terdutteam scope: Namespaced versions: - additionalPrinterColumns: - jsonPath: .spec.serverRef.name name: Server type: string - jsonPath: .status.teamID name: TeamID 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: TerdutTeam is the Schema for the terdutteams 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 TerdutTeam properties: 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). minLength: 1 type: string invite: 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. 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 type: string type: object oidc: description: |- TerdutTeamOIDC binds which identity-provider groups grant membership and ownership of this team (DESIGN.md §4.2). Both empty means no group grants either role here — matches terdut-server's own NULLIF-on-empty-string handling (internal/api/oidc_teams.go). properties: memberGroup: type: string ownerGroup: type: string type: object serverRef: description: serverRef names the TerdutServer this team belongs to. properties: name: minLength: 1 type: string namespace: type: string required: - name type: object required: - displayName - serverRef type: object status: description: status defines the observed state of TerdutTeam 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 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 controller (DESIGN.md §4.2). format: int64 type: integer type: object required: - spec type: object served: true storage: true subresources: status: {}