Skip to content

Commit

Permalink
feat: per-type adoption
Browse files Browse the repository at this point in the history
  • Loading branch information
mkmik committed Dec 7, 2023
1 parent f267439 commit b280d16
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 4 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
merge_group:
branches: ["main"]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: extractions/setup-just@v1
- run: just test
64 changes: 64 additions & 0 deletions examples/adopt.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
apiVersion: v1
data:
bar: baz
kind: ConfigMap
metadata:
name: foo
namespace: myns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: myns
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: nginx
labels:
app: nginx
spec:
containers:
- env:
- name: BAR
valueFrom:
configMapKeyRef:
key: bar
name: foo
- name: COMMON_ENV
value: example common env
image: nginx:1.14.2
name: nginx
ports:
- containerPort: 80
name: http
volumeMounts: []
- env:
- name: FOO
value: bar
image: my.dummy/sidecar
name: side
ports: []
volumeMounts: []
foo: 42
initContainers: []
volumes: []
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: myns
spec:
ports:
- name: http
port: 80
targetPort: http
selector:
app: nginx
18 changes: 18 additions & 0 deletions examples/adopt.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
ObjectMeta: {
namespace: 'myns',
},
v1+: {
PodSpec+: {
foo: 42,
containers_+: {
side+: {
image: 'my.dummy/sidecar',
env_+: {
FOO: { value: 'bar' },
},
},
},
},
Container+: {
env_+: {
COMMON_ENV+: { value: 'example common env' },
},
},
},
},

upstream: $.k8s.adopt(std.parseYaml(importstr 'adopt.yaml')),
Expand Down
17 changes: 17 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env just --justfile

export KUBECFG_ALPHA := "true"

test:
@ find . -name "*.jsonnet" -exec just assert {} \;
@ find . -name "*.golden" -exec just golden {} \;

golden FILE:
#!/bin/bash
golden={{FILE}}
jsonnet=${golden%%.golden}.jsonnet
diff "${golden}" <(kubecfg show "${jsonnet}")

assert FILE:
#!/bin/bash
kubecfg eval {{FILE}} >/dev/null
55 changes: 51 additions & 4 deletions k8s.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ local kubecfg = import 'kubecfg.libsonnet';
// generic helpers
// -------------------------

// workaround for https://github.com/google/go-jsonnet/issues/736
// local objectHas(o, f) = std.objectHas(o, f),
local objectHas(o, f) = std.setMember(f, std.objectFields(o)),

// applies overlay to each field of obj.
applyOverlayEach(obj, overlay):: $.mapObject(function(v) v + overlay, obj),

Expand All @@ -23,7 +27,7 @@ local kubecfg = import 'kubecfg.libsonnet';
// mapObject applies f to all the values of the o object. Unlike std.mapWithKey, the "hidden" property of the fields is preserved.
mapObject(f, o):: std.foldl(function(acc, i) acc + (
local v = f(o[i]);
if std.objectHas(o, i) then { [i]: v } else { [i]:: v }
if objectHas(o, i) then { [i]: v } else { [i]:: v }
), std.objectFieldsAll(o), {}),

// Like std.objectValues but adds a `[key`] field with the key of each element.
Expand Down Expand Up @@ -206,7 +210,33 @@ local kubecfg = import 'kubecfg.libsonnet';
acc {
[name + '_']+:: {},
[name]: $.asNamedArray($.deriveEach(type, self[name + '_'])),
}, arr, {}),
}, arr, { [adoptUpstreamKey]:: adopterFromTypedNamedArrays(arr) }),
local adoptUpstreamKey = '_adoptUpstream',

// This function returns the "adopter" function for a given partial type (object subtree).
// The adopter function logically performs the inverse function of typedNamedArrays.
local adopterFromPartialType(obj) = std.get(obj, adoptUpstreamKey, function(o) o),

// returns a function that will convert an upstream object into an object in the style of what users of this library
// would have crafted, according to the "typed named array" field definitions (containers -> containers_, env -> env_, ...).
// `arr` is an array of field definitions. A field definition is either string (the name of a field, e.g. 'containers')
// or in turn an tuple of [field_name, element_type]
local adopterFromTypedNamedArrays(arr) = function(o) std.foldl(
function(acc, d) adoptNamedArray(acc, if std.isArray(d) then d else [d, {}]),
arr,
o
),

local adoptNamedArray(o, fieldDef) =
local name = if std.isArray(fieldDef) then fieldDef[0] else fieldDef;
local type = if std.isArray(fieldDef) then fieldDef[1] else {};
o {
[name + '_']+:: {
[i.name]+: i
for i in std.get(o, name, [])
},
[name]: $.asNamedArray($.mapObject(function(v) adoptObject(v, type), self[name + '_'])),
},

// "Objectified" array
//
Expand All @@ -233,14 +263,29 @@ local kubecfg = import 'kubecfg.libsonnet';
local objectEntries(o) = [[k, o[k]] for k in std.objectFields(o)],

// adopt existing resources and make them extend the types defined by the k8s-libsonnet library.
adopt(objs):: kubecfg.deepMap(function(o) $.typeFor(o) + kubecfg.toOverlay(o), objs),
adopt(objs):: kubecfg.deepMap(function(o) adoptObject(kubecfg.toOverlay(o), $.typeFor(o)), objs),

local adoptObject(obj, typ) = deepMapObject(adoptObjectSubtree, typ + obj),
local adoptObjectSubtree(obj) = adopterFromPartialType(obj)(obj),

typeFor(o)::
local gv = std.splitLimitR('/' + o.apiVersion, '/', 1),
gvk = { group: gv[0], version: gv[1], kind: o.kind },
path = std.lstripChars('%(group)s.%(version)s.%(kind)s' % gvk, './');
kubecfg.getPath($, path, default=$.Object),

// deepMapObject(func, o): Apply the given function to each
// object in nested collection o, preserving the structure of o.
// It traverses arrays but it doesn't invoke the function on on arrays
local deepMapObject(func, o) = (
if std.isObject(o) then
func($.mapObject(function(o) deepMapObject(func, o), o))
else if std.isArray(o) then
[deepMapObject(func, elem) for elem in o]
else o
),
assert std.assertEqual('%s' % [deepMapObject(std.toString, { a: { b: [{ c: 1 }] } })], '{"a": "{\\"b\\": [\\"{\\\\\\"c\\\\\\": 1}\\"]}"}'),

// -------------------------
// types
// -------------------------
Expand Down Expand Up @@ -337,7 +382,9 @@ local kubecfg = import 'kubecfg.libsonnet';
} else {},
spec+: v.PodSpec,
},
PodSpec+: $.typedNamedArrays([['containers', v.Container], ['initContainers', v.Container], 'volumes']),
PodSpec+: $.typedNamedArrays([['containers', v.Container], ['initContainers', v.Container], 'volumes']) {
containers_+: {},
},
Container+: $.typedNamedArrays(['env', ['ports', v.ContainerPort], 'volumeMounts']),
ContainerPort+: {
containerPort: error 'must set containerPort',
Expand Down

0 comments on commit b280d16

Please sign in to comment.