knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/v1/source_types.go (about) 1 /* 2 Copyright 2019 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1 18 19 import ( 20 "context" 21 "time" 22 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 27 "knative.dev/pkg/apis" 28 "knative.dev/pkg/apis/duck/ducktypes" 29 ) 30 31 // +genduck 32 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 33 34 // Source is the minimum resource shape to adhere to the Source Specification. 35 // This duck type is intended to allow implementors of Sources and 36 // Importers to verify their own resources meet the expectations. 37 // This is not a real resource. 38 // NOTE: The Source Specification is in progress and the shape and names could 39 // be modified until it has been accepted. 40 type Source struct { 41 metav1.TypeMeta `json:",inline"` 42 metav1.ObjectMeta `json:"metadata,omitempty"` 43 44 Spec SourceSpec `json:"spec"` 45 Status SourceStatus `json:"status"` 46 } 47 48 type SourceSpec struct { 49 // Sink is a reference to an object that will resolve to a uri to use as the sink. 50 Sink Destination `json:"sink,omitempty"` 51 52 // CloudEventOverrides defines overrides to control the output format and 53 // modifications of the event sent to the sink. 54 // +optional 55 CloudEventOverrides *CloudEventOverrides `json:"ceOverrides,omitempty"` 56 } 57 58 // CloudEventOverrides defines arguments for a Source that control the output 59 // format of the CloudEvents produced by the Source. 60 type CloudEventOverrides struct { 61 // Extensions specify what attribute are added or overridden on the 62 // outbound event. Each `Extensions` key-value pair are set on the event as 63 // an attribute extension independently. 64 // +optional 65 Extensions map[string]string `json:"extensions,omitempty"` 66 } 67 68 // SourceStatus shows how we expect folks to embed Addressable in 69 // their Status field. 70 type SourceStatus struct { 71 // inherits Status, which currently provides: 72 // * ObservedGeneration - the 'Generation' of the Service that was last 73 // processed by the controller. 74 // * Conditions - the latest available observations of a resource's current 75 // state. 76 Status `json:",inline"` 77 78 // SinkURI is the current active sink URI that has been configured for the 79 // Source. 80 // +optional 81 SinkURI *apis.URL `json:"sinkUri,omitempty"` 82 83 // CloudEventAttributes are the specific attributes that the Source uses 84 // as part of its CloudEvents. 85 // +optional 86 CloudEventAttributes []CloudEventAttributes `json:"ceAttributes,omitempty"` 87 88 // SinkCACerts are Certification Authority (CA) certificates in PEM format 89 // according to https://www.rfc-editor.org/rfc/rfc7468. 90 // +optional 91 SinkCACerts *string `json:"sinkCACerts,omitempty"` 92 93 // SinkAudience is the OIDC audience of the sink. 94 // +optional 95 SinkAudience *string `json:"sinkAudience,omitempty"` 96 97 // Auth defines the attributes that provide the generated service account 98 // name in the resource status. 99 // +optional 100 Auth *AuthStatus `json:"auth,omitempty"` 101 } 102 103 // CloudEventAttributes specifies the attributes that a Source 104 // uses as part of its CloudEvents. 105 type CloudEventAttributes struct { 106 // Type refers to the CloudEvent type attribute. 107 Type string `json:"type,omitempty"` 108 109 // Source is the CloudEvents source attribute. 110 Source string `json:"source,omitempty"` 111 } 112 113 // IsReady returns true if the resource is ready overall. 114 func (ss *SourceStatus) IsReady() bool { 115 for _, c := range ss.Conditions { 116 switch c.Type { 117 // Look for the "happy" condition, which is the only condition that 118 // we can reliably understand to be the overall state of the resource. 119 case apis.ConditionReady, apis.ConditionSucceeded: 120 return c.IsTrue() 121 } 122 } 123 return false 124 } 125 126 // Verify Source resources meet duck contracts. 127 var ( 128 _ apis.Listable = (*Source)(nil) 129 _ ducktypes.Implementable = (*Source)(nil) 130 _ ducktypes.Populatable = (*Source)(nil) 131 ) 132 133 const ( 134 // SourceConditionSinkProvided has status True when the Source 135 // has been configured with a sink target that is resolvable. 136 SourceConditionSinkProvided apis.ConditionType = "SinkProvided" 137 ) 138 139 // GetFullType implements duck.Implementable 140 func (*Source) GetFullType() ducktypes.Populatable { 141 return &Source{} 142 } 143 144 // Populate implements duck.Populatable 145 func (s *Source) Populate() { 146 s.Spec.Sink = Destination{ 147 URI: &apis.URL{ 148 Scheme: "https", 149 Host: "tableflip.dev", 150 RawQuery: "flip=mattmoor", 151 }, 152 } 153 s.Spec.CloudEventOverrides = &CloudEventOverrides{ 154 Extensions: map[string]string{"boosh": "kakow"}, 155 } 156 s.Status.ObservedGeneration = 42 157 s.Status.Conditions = Conditions{{ 158 // Populate ALL fields 159 Type: SourceConditionSinkProvided, 160 Status: corev1.ConditionTrue, 161 LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 2, 28, 18, 52, 0, 0, time.UTC))}, 162 }} 163 s.Status.SinkURI = &apis.URL{ 164 Scheme: "https", 165 Host: "tableflip.dev", 166 RawQuery: "flip=mattmoor", 167 } 168 s.Status.CloudEventAttributes = []CloudEventAttributes{{ 169 Type: "dev.knative.foo", 170 Source: "http://knative.dev/knative/eventing", 171 }} 172 } 173 174 // GetListType implements apis.Listable 175 func (*Source) GetListType() runtime.Object { 176 return &SourceList{} 177 } 178 179 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 180 181 // SourceList is a list of Source resources 182 type SourceList struct { 183 metav1.TypeMeta `json:",inline"` 184 metav1.ListMeta `json:"metadata"` 185 186 Items []Source `json:"items"` 187 } 188 189 func (s *Source) Validate(ctx context.Context) *apis.FieldError { 190 if s == nil { 191 return nil 192 } 193 return s.Spec.Validate(ctx).ViaField("spec") 194 } 195 196 func (s *SourceSpec) Validate(ctx context.Context) *apis.FieldError { 197 if s == nil { 198 return apis.ErrMissingField("spec") 199 } 200 return s.Sink.Validate(ctx).ViaField("sink"). 201 Also(s.CloudEventOverrides.Validate(ctx).ViaField("ceOverrides")) 202 } 203 204 func (ceOverrides *CloudEventOverrides) Validate(ctx context.Context) *apis.FieldError { 205 if ceOverrides == nil { 206 return nil 207 } 208 for key := range ceOverrides.Extensions { 209 if err := validateExtensionName(key); err != nil { 210 return err.ViaField("extensions") 211 } 212 } 213 return nil 214 } 215 216 func validateExtensionName(key string) *apis.FieldError { 217 if key == "" { 218 return apis.ErrInvalidKeyName( 219 key, 220 "", 221 "keys MUST NOT be empty", 222 ) 223 } 224 225 for _, c := range key { 226 //nolint:staticcheck 227 if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { 228 return apis.ErrInvalidKeyName( 229 key, 230 "", 231 "keys are expected to be alphanumeric", 232 ) 233 } 234 } 235 236 return nil 237 }