Stitch any CI into Tangled
2

Configure Feed

Select the types of activity you want to include in your feed.

1package k8s 2 3// Package k8s exposes the tiny slice of the Kubernetes API tack's Tekton 4// provider actually needs. The goal is to keep Tekton support in-process 5// without dragging the full client-go stack into the module graph. 6 7import ( 8 "context" 9 "errors" 10 "io" 11 "time" 12) 13 14// ErrNotFound mirrors the API server's 404 in a way callers can branch on with 15// errors.Is instead of inspecting status codes or response bodies. 16var ErrNotFound = errors.New("k8s: not found") 17 18// ErrAlreadyExists mirrors the API server's 409 for create calls whose name is 19// already present. 20var ErrAlreadyExists = errors.New("k8s: already exists") 21 22// Client is the minimal Kubernetes surface tack needs today: CRUD-ish access to 23// arbitrary JSON objects, plus pod listing and log streaming. 24type Client interface { 25 CreateObject( 26 ctx context.Context, 27 gvr GVR, 28 namespace string, 29 obj Object, 30 ) (Object, error) 31 32 GetObject( 33 ctx context.Context, 34 gvr GVR, 35 namespace string, 36 name string, 37 ) (Object, error) 38 39 ListObjects( 40 ctx context.Context, 41 gvr GVR, 42 namespace string, 43 opts ListOptions, 44 ) ([]Object, error) 45 46 WatchObjects( 47 ctx context.Context, 48 gvr GVR, 49 namespace string, 50 opts ListOptions, 51 ) (WatchInterface, error) 52 53 ListPods( 54 ctx context.Context, 55 namespace string, 56 labelSelector string, 57 ) ([]Pod, error) 58 59 StreamPodLogs( 60 ctx context.Context, 61 namespace string, 62 podName string, 63 container string, 64 opts LogOptions, 65 ) (io.ReadCloser, error) 66} 67 68// LogOptions controls how StreamPodLogs reads container logs. 69type LogOptions struct { 70 // Follow tells the API server to keep the connection open and stream 71 // new log bytes as the container writes them, only EOFing when the 72 // container terminates (or ctx is cancelled). Without Follow, the 73 // returned reader is a snapshot: it yields the bytes that exist at 74 // request time and then EOFs immediately, even if the container is 75 // still running. Live-streaming callers MUST set Follow=true; using 76 // the snapshot mode for a still-running container makes the caller 77 // look at a frozen view and treat the container as if it had finished. 78 Follow bool 79} 80 81// GVR identifies a Kubernetes resource by the path segments the API server 82// routes on. 83type GVR struct { 84 Group string 85 Version string 86 Resource string 87} 88 89// ListOptions is the subset of query options tack currently uses. 90type ListOptions struct { 91 LabelSelector string 92 FieldSelector string 93} 94 95// WatchInterface matches the shape provider code already expects from a watch: 96// a receive-only event channel and an explicit stop hook. 97type WatchInterface interface { 98 ResultChan() <-chan WatchEvent 99 Stop() 100} 101 102// WatchEvent mirrors the API server's streaming watch envelope. 103type WatchEvent struct { 104 Type string 105 Object Object 106} 107 108// Object is an arbitrary Kubernetes resource decoded from JSON. 109type Object map[string]any 110 111// DeepCopy returns a recursive copy so callers can safely mutate the returned 112// object without aliasing shared test fixtures or cached state. 113func (o Object) DeepCopy() Object { 114 if o == nil { 115 return nil 116 } 117 return Object(deepCopyMap(map[string]any(o))) 118} 119 120// GetName returns metadata.name, or the empty string when absent. 121func (o Object) GetName() string { 122 v, _ := NestedString(map[string]any(o), "metadata", "name") 123 return v 124} 125 126// GetNamespace returns metadata.namespace, or the empty string when absent. 127func (o Object) GetNamespace() string { 128 v, _ := NestedString(map[string]any(o), "metadata", "namespace") 129 return v 130} 131 132// GetAPIVersion returns apiVersion, or the empty string when absent. 133func (o Object) GetAPIVersion() string { 134 v, _ := NestedString(map[string]any(o), "apiVersion") 135 return v 136} 137 138// GetKind returns kind, or the empty string when absent. 139func (o Object) GetKind() string { 140 v, _ := NestedString(map[string]any(o), "kind") 141 return v 142} 143 144// GetUID returns metadata.uid, or the empty string when absent. 145func (o Object) GetUID() string { 146 v, _ := NestedString(map[string]any(o), "metadata", "uid") 147 return v 148} 149 150// SetUID writes metadata.uid, creating metadata as needed. 151func (o Object) SetUID(uid string) { 152 meta := ensureNestedMap(o, "metadata") 153 meta["uid"] = uid 154} 155 156// SetCreationTimestamp writes metadata.creationTimestamp in UTC RFC3339 form. 157func (o Object) SetCreationTimestamp(ts time.Time) { 158 meta := ensureNestedMap(o, "metadata") 159 meta["creationTimestamp"] = ts.UTC().Format(time.RFC3339Nano) 160} 161 162// GetCreationTimestamp returns metadata.creationTimestamp, or the zero time when 163// the field is missing or malformed. 164func (o Object) GetCreationTimestamp() time.Time { 165 raw, ok := NestedString(map[string]any(o), "metadata", "creationTimestamp") 166 if !ok || raw == "" { 167 return time.Time{} 168 } 169 for _, layout := range []string{time.RFC3339Nano, time.RFC3339} { 170 if ts, err := time.Parse(layout, raw); err == nil { 171 return ts 172 } 173 } 174 return time.Time{} 175} 176 177// GetLabels returns metadata.labels as a string map. Non-string label values are 178// ignored because Kubernetes labels are strings on the wire. 179func (o Object) GetLabels() map[string]string { 180 raw, ok := NestedMap(map[string]any(o), "metadata", "labels") 181 if !ok { 182 return nil 183 } 184 out := make(map[string]string, len(raw)) 185 for key, value := range raw { 186 if s, ok := value.(string); ok { 187 out[key] = s 188 } 189 } 190 return out 191} 192 193// GetAnnotations returns metadata.annotations as a string map. 194func (o Object) GetAnnotations() map[string]string { 195 raw, ok := NestedMap(map[string]any(o), "metadata", "annotations") 196 if !ok { 197 return nil 198 } 199 out := make(map[string]string, len(raw)) 200 for key, value := range raw { 201 if s, ok := value.(string); ok { 202 out[key] = s 203 } 204 } 205 return out 206} 207 208// NestedString returns a nested string field from the object. 209func (o Object) NestedString(fields ...string) (string, bool) { 210 return NestedString(map[string]any(o), fields...) 211} 212 213// NestedSlice returns a nested slice field from the object. 214func (o Object) NestedSlice(fields ...string) ([]any, bool) { 215 return NestedSlice(map[string]any(o), fields...) 216} 217 218// NestedMap returns a nested map field from the object. 219func (o Object) NestedMap(fields ...string) (map[string]any, bool) { 220 return NestedMap(map[string]any(o), fields...) 221} 222 223// NestedString returns a nested string field from obj. 224func NestedString(obj map[string]any, fields ...string) (string, bool) { 225 value, ok := nestedValue(obj, fields...) 226 if !ok { 227 return "", false 228 } 229 s, ok := value.(string) 230 return s, ok 231} 232 233// NestedSlice returns a nested slice field from obj. 234func NestedSlice(obj map[string]any, fields ...string) ([]any, bool) { 235 value, ok := nestedValue(obj, fields...) 236 if !ok { 237 return nil, false 238 } 239 slice, ok := value.([]any) 240 return slice, ok 241} 242 243// NestedMap returns a nested map field from obj. 244func NestedMap(obj map[string]any, fields ...string) (map[string]any, bool) { 245 value, ok := nestedValue(obj, fields...) 246 if !ok { 247 return nil, false 248 } 249 m, ok := value.(map[string]any) 250 return m, ok 251} 252 253// Container is the small slice of a pod container spec tack cares about for 254// log streaming. 255type Container struct { 256 Name string 257} 258 259// Pod is the reduced pod shape tack uses to order containers and request logs. 260type Pod struct { 261 Name string 262 Namespace string 263 UID string 264 Labels map[string]string 265 CreationTimestamp time.Time 266 InitContainers []Container 267 Containers []Container 268} 269 270func nestedValue(obj map[string]any, fields ...string) (any, bool) { 271 current := any(obj) 272 for _, field := range fields { 273 m, ok := current.(map[string]any) 274 if !ok { 275 return nil, false 276 } 277 current, ok = m[field] 278 if !ok { 279 return nil, false 280 } 281 } 282 return current, true 283} 284 285func ensureNestedMap(obj map[string]any, fields ...string) map[string]any { 286 current := obj 287 for _, field := range fields { 288 next, ok := current[field].(map[string]any) 289 if !ok { 290 next = map[string]any{} 291 current[field] = next 292 } 293 current = next 294 } 295 return current 296} 297 298func deepCopyMap(in map[string]any) map[string]any { 299 out := make(map[string]any, len(in)) 300 for key, value := range in { 301 out[key] = deepCopyValue(value) 302 } 303 return out 304} 305 306func deepCopySlice(in []any) []any { 307 out := make([]any, len(in)) 308 for i, value := range in { 309 out[i] = deepCopyValue(value) 310 } 311 return out 312} 313 314func deepCopyValue(value any) any { 315 switch v := value.(type) { 316 case map[string]any: 317 return deepCopyMap(v) 318 case []any: 319 return deepCopySlice(v) 320 default: 321 return v 322 } 323}