···11+/*
22+Copyright 2014 The Kubernetes Authors.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+// Package k8s provides the tiny slice of the Kubernetes API tack's Tekton
1818+// provider needs.
1919+//
2020+// The package is based on the shape and behavior of Kubernetes client-go's
2121+// in-cluster REST client and watch plumbing, but reimplemented here with a much
2222+// smaller API surface so tack doesn't need to import the full client-go module
2323+// graph just to create resources, watch them, and stream pod logs.
2424+//
2525+// The Apache 2.0 notice above records that upstream client-go provenance in one
2626+// place for the package.
2727+package k8s
···11+package k8s
22+33+// Package k8s exposes the tiny slice of the Kubernetes API tack's Tekton
44+// provider actually needs. The goal is to keep Tekton support in-process
55+// without dragging the full client-go stack into the module graph.
66+77+import (
88+ "context"
99+ "errors"
1010+ "io"
1111+ "time"
1212+)
1313+1414+// ErrNotFound mirrors the API server's 404 in a way callers can branch on with
1515+// errors.Is instead of inspecting status codes or response bodies.
1616+var ErrNotFound = errors.New("k8s: not found")
1717+1818+// ErrAlreadyExists mirrors the API server's 409 for create calls whose name is
1919+// already present.
2020+var ErrAlreadyExists = errors.New("k8s: already exists")
2121+2222+// Client is the minimal Kubernetes surface tack needs today: CRUD-ish access to
2323+// arbitrary JSON objects, plus pod listing and log streaming.
2424+type Client interface {
2525+ CreateObject(
2626+ ctx context.Context,
2727+ gvr GVR,
2828+ namespace string,
2929+ obj Object,
3030+ ) (Object, error)
3131+3232+ GetObject(
3333+ ctx context.Context,
3434+ gvr GVR,
3535+ namespace string,
3636+ name string,
3737+ ) (Object, error)
3838+3939+ ListObjects(
4040+ ctx context.Context,
4141+ gvr GVR,
4242+ namespace string,
4343+ opts ListOptions,
4444+ ) ([]Object, error)
4545+4646+ WatchObjects(
4747+ ctx context.Context,
4848+ gvr GVR,
4949+ namespace string,
5050+ opts ListOptions,
5151+ ) (WatchInterface, error)
5252+5353+ ListPods(
5454+ ctx context.Context,
5555+ namespace string,
5656+ labelSelector string,
5757+ ) ([]Pod, error)
5858+5959+ StreamPodLogs(
6060+ ctx context.Context,
6161+ namespace string,
6262+ podName string,
6363+ container string,
6464+ ) (io.ReadCloser, error)
6565+}
6666+6767+// GVR identifies a Kubernetes resource by the path segments the API server
6868+// routes on.
6969+type GVR struct {
7070+ Group string
7171+ Version string
7272+ Resource string
7373+}
7474+7575+// ListOptions is the subset of query options tack currently uses.
7676+type ListOptions struct {
7777+ LabelSelector string
7878+ FieldSelector string
7979+}
8080+8181+// WatchInterface matches the shape provider code already expects from a watch:
8282+// a receive-only event channel and an explicit stop hook.
8383+type WatchInterface interface {
8484+ ResultChan() <-chan WatchEvent
8585+ Stop()
8686+}
8787+8888+// WatchEvent mirrors the API server's streaming watch envelope.
8989+type WatchEvent struct {
9090+ Type string
9191+ Object Object
9292+}
9393+9494+// Object is an arbitrary Kubernetes resource decoded from JSON.
9595+type Object map[string]any
9696+9797+// DeepCopy returns a recursive copy so callers can safely mutate the returned
9898+// object without aliasing shared test fixtures or cached state.
9999+func (o Object) DeepCopy() Object {
100100+ if o == nil {
101101+ return nil
102102+ }
103103+ return Object(deepCopyMap(map[string]any(o)))
104104+}
105105+106106+// GetName returns metadata.name, or the empty string when absent.
107107+func (o Object) GetName() string {
108108+ v, _ := NestedString(map[string]any(o), "metadata", "name")
109109+ return v
110110+}
111111+112112+// GetNamespace returns metadata.namespace, or the empty string when absent.
113113+func (o Object) GetNamespace() string {
114114+ v, _ := NestedString(map[string]any(o), "metadata", "namespace")
115115+ return v
116116+}
117117+118118+// GetAPIVersion returns apiVersion, or the empty string when absent.
119119+func (o Object) GetAPIVersion() string {
120120+ v, _ := NestedString(map[string]any(o), "apiVersion")
121121+ return v
122122+}
123123+124124+// GetKind returns kind, or the empty string when absent.
125125+func (o Object) GetKind() string {
126126+ v, _ := NestedString(map[string]any(o), "kind")
127127+ return v
128128+}
129129+130130+// GetUID returns metadata.uid, or the empty string when absent.
131131+func (o Object) GetUID() string {
132132+ v, _ := NestedString(map[string]any(o), "metadata", "uid")
133133+ return v
134134+}
135135+136136+// SetUID writes metadata.uid, creating metadata as needed.
137137+func (o Object) SetUID(uid string) {
138138+ meta := ensureNestedMap(o, "metadata")
139139+ meta["uid"] = uid
140140+}
141141+142142+// SetCreationTimestamp writes metadata.creationTimestamp in UTC RFC3339 form.
143143+func (o Object) SetCreationTimestamp(ts time.Time) {
144144+ meta := ensureNestedMap(o, "metadata")
145145+ meta["creationTimestamp"] = ts.UTC().Format(time.RFC3339Nano)
146146+}
147147+148148+// GetCreationTimestamp returns metadata.creationTimestamp, or the zero time when
149149+// the field is missing or malformed.
150150+func (o Object) GetCreationTimestamp() time.Time {
151151+ raw, ok := NestedString(map[string]any(o), "metadata", "creationTimestamp")
152152+ if !ok || raw == "" {
153153+ return time.Time{}
154154+ }
155155+ for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
156156+ if ts, err := time.Parse(layout, raw); err == nil {
157157+ return ts
158158+ }
159159+ }
160160+ return time.Time{}
161161+}
162162+163163+// GetLabels returns metadata.labels as a string map. Non-string label values are
164164+// ignored because Kubernetes labels are strings on the wire.
165165+func (o Object) GetLabels() map[string]string {
166166+ raw, ok := NestedMap(map[string]any(o), "metadata", "labels")
167167+ if !ok {
168168+ return nil
169169+ }
170170+ out := make(map[string]string, len(raw))
171171+ for key, value := range raw {
172172+ if s, ok := value.(string); ok {
173173+ out[key] = s
174174+ }
175175+ }
176176+ return out
177177+}
178178+179179+// GetAnnotations returns metadata.annotations as a string map.
180180+func (o Object) GetAnnotations() map[string]string {
181181+ raw, ok := NestedMap(map[string]any(o), "metadata", "annotations")
182182+ if !ok {
183183+ return nil
184184+ }
185185+ out := make(map[string]string, len(raw))
186186+ for key, value := range raw {
187187+ if s, ok := value.(string); ok {
188188+ out[key] = s
189189+ }
190190+ }
191191+ return out
192192+}
193193+194194+// NestedString returns a nested string field from the object.
195195+func (o Object) NestedString(fields ...string) (string, bool) {
196196+ return NestedString(map[string]any(o), fields...)
197197+}
198198+199199+// NestedSlice returns a nested slice field from the object.
200200+func (o Object) NestedSlice(fields ...string) ([]any, bool) {
201201+ return NestedSlice(map[string]any(o), fields...)
202202+}
203203+204204+// NestedMap returns a nested map field from the object.
205205+func (o Object) NestedMap(fields ...string) (map[string]any, bool) {
206206+ return NestedMap(map[string]any(o), fields...)
207207+}
208208+209209+// NestedString returns a nested string field from obj.
210210+func NestedString(obj map[string]any, fields ...string) (string, bool) {
211211+ value, ok := nestedValue(obj, fields...)
212212+ if !ok {
213213+ return "", false
214214+ }
215215+ s, ok := value.(string)
216216+ return s, ok
217217+}
218218+219219+// NestedSlice returns a nested slice field from obj.
220220+func NestedSlice(obj map[string]any, fields ...string) ([]any, bool) {
221221+ value, ok := nestedValue(obj, fields...)
222222+ if !ok {
223223+ return nil, false
224224+ }
225225+ slice, ok := value.([]any)
226226+ return slice, ok
227227+}
228228+229229+// NestedMap returns a nested map field from obj.
230230+func NestedMap(obj map[string]any, fields ...string) (map[string]any, bool) {
231231+ value, ok := nestedValue(obj, fields...)
232232+ if !ok {
233233+ return nil, false
234234+ }
235235+ m, ok := value.(map[string]any)
236236+ return m, ok
237237+}
238238+239239+// Container is the small slice of a pod container spec tack cares about for
240240+// log streaming.
241241+type Container struct {
242242+ Name string
243243+}
244244+245245+// Pod is the reduced pod shape tack uses to order containers and request logs.
246246+type Pod struct {
247247+ Name string
248248+ Namespace string
249249+ UID string
250250+ Labels map[string]string
251251+ CreationTimestamp time.Time
252252+ InitContainers []Container
253253+ Containers []Container
254254+}
255255+256256+func nestedValue(obj map[string]any, fields ...string) (any, bool) {
257257+ current := any(obj)
258258+ for _, field := range fields {
259259+ m, ok := current.(map[string]any)
260260+ if !ok {
261261+ return nil, false
262262+ }
263263+ current, ok = m[field]
264264+ if !ok {
265265+ return nil, false
266266+ }
267267+ }
268268+ return current, true
269269+}
270270+271271+func ensureNestedMap(obj map[string]any, fields ...string) map[string]any {
272272+ current := obj
273273+ for _, field := range fields {
274274+ next, ok := current[field].(map[string]any)
275275+ if !ok {
276276+ next = map[string]any{}
277277+ current[field] = next
278278+ }
279279+ current = next
280280+ }
281281+ return current
282282+}
283283+284284+func deepCopyMap(in map[string]any) map[string]any {
285285+ out := make(map[string]any, len(in))
286286+ for key, value := range in {
287287+ out[key] = deepCopyValue(value)
288288+ }
289289+ return out
290290+}
291291+292292+func deepCopySlice(in []any) []any {
293293+ out := make([]any, len(in))
294294+ for i, value := range in {
295295+ out[i] = deepCopyValue(value)
296296+ }
297297+ return out
298298+}
299299+300300+func deepCopyValue(value any) any {
301301+ switch v := value.(type) {
302302+ case map[string]any:
303303+ return deepCopyMap(v)
304304+ case []any:
305305+ return deepCopySlice(v)
306306+ default:
307307+ return v
308308+ }
309309+}