kube: move cert-manager resources to kube.local.libsonnet
This way kubernetes consumers don't have to import anything from
cluster/, hopefully.
We also create a small abstraction for local additions for
kube.libsonnet without having to modify upstream.
Change-Id: I209095781f91c8867250a647fe944370cddd67d0
diff --git a/cluster/kube/cluster.jsonnet b/cluster/kube/cluster.jsonnet
index a0b1aed..e9e8932 100644
--- a/cluster/kube/cluster.jsonnet
+++ b/cluster/kube/cluster.jsonnet
@@ -176,7 +176,7 @@
// Main nginx Ingress Controller
nginx: nginx.Environment {},
certmanager: certmanager.Environment {},
- issuer: certmanager.ClusterIssuer("letsencrypt-prod") {
+ issuer: kube.ClusterIssuer("letsencrypt-prod") {
spec: {
acme: {
server: "https://acme-v02.api.letsencrypt.org/directory",
diff --git a/cluster/kube/lib/cert-manager.libsonnet b/cluster/kube/lib/cert-manager.libsonnet
index e9e4a4b..93b9357 100644
--- a/cluster/kube/lib/cert-manager.libsonnet
+++ b/cluster/kube/lib/cert-manager.libsonnet
@@ -501,13 +501,13 @@
},
issuers: {
- webhookSelfsign: cm.Issuer("cert-manager-webhook-selfsign") {
+ webhookSelfsign: kube.Issuer("cert-manager-webhook-selfsign") {
metadata+: env.metadata,
spec: {
selfSigned: {},
},
},
- webhookCA: cm.Issuer("cert-manager-webhook-ca") {
+ webhookCA: kube.Issuer("cert-manager-webhook-ca") {
metadata+: env.metadata,
spec: {
ca: {
@@ -517,7 +517,7 @@
},
},
certificates: {
- webhookCA: cm.Certificate("cert-manager-webhook-ca") {
+ webhookCA: kube.Certificate("cert-manager-webhook-ca") {
metadata+: env.metadata,
spec: {
secretName: "cert-manager-webhook-ca",
@@ -529,7 +529,7 @@
isCA: true,
},
},
- webhookTLS: cm.Certificate("cert-manager-webhook-webhook-tls") {
+ webhookTLS: kube.Certificate("cert-manager-webhook-webhook-tls") {
metadata+: env.metadata,
spec: {
secretName: "cert-manager-webhook-webhook-tls",
@@ -696,16 +696,4 @@
],
},
},
-
- Issuer(name): kube._Object("certmanager.k8s.io/v1alpha1", "Issuer", name) {
- spec: error "spec must be specified",
- },
-
- ClusterIssuer(name): kube._Object("certmanager.k8s.io/v1alpha1", "ClusterIssuer", name) {
- spec: error "spec must be specified",
- },
-
- Certificate(name): kube._Object("certmanager.k8s.io/v1alpha1", "Certificate", name) {
- spec: error "spec must be specified",
- },
}
diff --git a/cluster/kube/lib/cockroachdb.libsonnet b/cluster/kube/lib/cockroachdb.libsonnet
index 212104d..0b58180 100644
--- a/cluster/kube/lib/cockroachdb.libsonnet
+++ b/cluster/kube/lib/cockroachdb.libsonnet
@@ -35,7 +35,6 @@
local kube = import "../../../kube/kube.libsonnet";
-local cm = import "cert-manager.libsonnet";
local policies = import "../../../kube/policies.libsonnet";
{
@@ -76,14 +75,14 @@
name(suffix):: if cluster.cfg.ownNamespace then suffix else name + "-" + suffix,
pki: {
- selfSignedIssuer: cm.Issuer(cluster.name("selfsigned")) {
+ selfSignedIssuer: kube.Issuer(cluster.name("selfsigned")) {
metadata+: cluster.metadata,
spec: {
selfSigned: {},
},
},
- selfSignedKeypair: cm.Certificate(cluster.name("cluster-ca")) {
+ selfSignedKeypair: kube.Certificate(cluster.name("cluster-ca")) {
metadata+: cluster.metadata,
spec: {
secretName: cluster.name("cluster-ca"),
@@ -96,7 +95,7 @@
},
},
- clusterIssuer: cm.Issuer(cluster.name("cluster-ca")) {
+ clusterIssuer: kube.Issuer(cluster.name("cluster-ca")) {
metadata+: cluster.metadata,
spec: {
ca: {
@@ -105,7 +104,7 @@
},
},
- nodeCertificate: cm.Certificate(cluster.name("node")) {
+ nodeCertificate: kube.Certificate(cluster.name("node")) {
metadata+: cluster.metadata,
spec: {
secretName: "cockroachdb-node-cert",
@@ -127,7 +126,7 @@
},
},
- clientCertificate: cm.Certificate(cluster.name("client")) {
+ clientCertificate: kube.Certificate(cluster.name("client")) {
metadata+: cluster.metadata,
spec: {
secretName: cluster.name("client-certificate"),
@@ -371,7 +370,7 @@
},
Client(name):: {
- certificate: cm.Certificate(cluster.name("client-%s" % name)) {
+ certificate: kube.Certificate(cluster.name("client-%s" % name)) {
metadata+: cluster.metadata,
spec: {
secretName: cluster.name("client-%s-certificate" % name),
diff --git a/cluster/kube/lib/registry.libsonnet b/cluster/kube/lib/registry.libsonnet
index 5272b2d..d457830 100644
--- a/cluster/kube/lib/registry.libsonnet
+++ b/cluster/kube/lib/registry.libsonnet
@@ -5,7 +5,6 @@
# kubectl get secrets rook-ceph-object-user-<ceph-pool>-object-registry -n <ceph-namespace> -o yaml --export | kubectl replace -f - -n registry
local kube = import "../../../kube/kube.libsonnet";
-local cm = import "cert-manager.libsonnet";
{
Environment: {
@@ -29,13 +28,13 @@
namespace: kube.Namespace(cfg.namespace),
- registryIssuer: cm.Issuer("registry-issuer") {
+ registryIssuer: kube.Issuer("registry-issuer") {
metadata+: env.metadata("registry-issuer"),
spec: {
selfSigned: {},
},
},
- authCertificate: cm.Certificate("auth") {
+ authCertificate: kube.Certificate("auth") {
metadata+: env.metadata("auth"),
spec: {
secretName: "auth-internal",
@@ -46,7 +45,7 @@
commonName: "auth.registry",
},
},
- registryCertificate: cm.Certificate("registry") {
+ registryCertificate: kube.Certificate("registry") {
metadata+: env.metadata("registry"),
spec: {
secretName: "registry-internal",
diff --git a/kube/kube.libsonnet b/kube/kube.libsonnet
index e20d872..c12e3db 100644
--- a/kube/kube.libsonnet
+++ b/kube/kube.libsonnet
@@ -1,702 +1,15 @@
-// Generic library of Kubernetes objects (https://github.com/bitnami-labs/kube-libsonnet)
-//
-// Objects in this file follow the regular Kubernetes API object
-// schema with two exceptions:
-//
-// ## Optional helpers
-//
-// A few objects have defaults or additional "helper" hidden
-// (double-colon) fields that will help with common situations. For
-// example, `Service.target_pod` generates suitable `selector` and
-// `ports` blocks for the common case of a single-pod/single-port
-// service. If for some reason you don't want the helper, just
-// provide explicit values for the regular Kubernetes fields that the
-// helper *would* have generated, and the helper logic will be
-// ignored.
-//
-// ## The Underscore Convention:
-//
-// Various constructs in the Kubernetes API use JSON arrays to
-// represent unordered sets or named key/value maps. This is
-// particularly annoying with jsonnet since we want to use jsonnet's
-// powerful object merge operation with these constructs.
-//
-// To combat this, this library attempts to provide more "jsonnet
-// native" variants of these arrays in alternative hidden fields that
-// end with an underscore. For example, the `env_` block in
-// `Container`:
-// ```
-// kube.Container("foo") {
-// env_: { FOO: "bar" },
-// }
-// ```
-// ... produces the expected `container.env` JSON array:
-// ```
-// {
-// "env": [
-// { "name": "FOO", "value": "bar" }
-// ]
-// }
-// ```
-//
-// If you are confused by the underscore versions, or don't want them
-// in your situation then just ignore them and set the regular
-// non-underscore field as usual.
-//
-//
-// ## TODO
-//
-// TODO: Expand this to include all API objects.
-//
-// Should probably fill out all the defaults here too, so jsonnet can
-// reference them. In addition, jsonnet validation is more useful
-// (client-side, and gives better line information).
+// Local extensions to kube.upstream.libsonnet.
-{
- // Returns array of values from given object. Does not include hidden fields.
- objectValues(o):: [o[field] for field in std.objectFields(o)],
+local kube = import "kube.upstream.libsonnet";
- // Returns array of [key, value] pairs from given object. Does not include hidden fields.
- objectItems(o):: [[k, o[k]] for k in std.objectFields(o)],
-
- // Replace all occurrences of `_` with `-`.
- hyphenate(s):: std.join("-", std.split(s, "_")),
-
- // Convert an octal (as a string) to number,
- parseOctal(s):: (
- local len = std.length(s);
- local leading = std.substr(s, 0, len - 1);
- local last = std.parseInt(std.substr(s, len - 1, 1));
- assert last < 8 : "found '%s' digit >= 8" % [last];
- last + (if len > 1 then 8 * $.parseOctal(leading) else 0)
- ),
-
- // Convert {foo: {a: b}} to [{name: foo, a: b}]
- mapToNamedList(o):: [{ name: $.hyphenate(n) } + o[n] for n in std.objectFields(o)],
-
- // Return object containing only these fields elements
- filterMapByFields(o, fields): { [field]: o[field] for field in std.setInter(std.objectFields(o), fields) },
-
- // Convert from SI unit suffixes to regular number
- siToNum(n):: (
- local convert =
- if std.endsWith(n, "m") then [1, 0.001]
- else if std.endsWith(n, "K") then [1, 1e3]
- else if std.endsWith(n, "M") then [1, 1e6]
- else if std.endsWith(n, "G") then [1, 1e9]
- else if std.endsWith(n, "T") then [1, 1e12]
- else if std.endsWith(n, "P") then [1, 1e15]
- else if std.endsWith(n, "E") then [1, 1e18]
- else if std.endsWith(n, "Ki") then [2, std.pow(2, 10)]
- else if std.endsWith(n, "Mi") then [2, std.pow(2, 20)]
- else if std.endsWith(n, "Gi") then [2, std.pow(2, 30)]
- else if std.endsWith(n, "Ti") then [2, std.pow(2, 40)]
- else if std.endsWith(n, "Pi") then [2, std.pow(2, 50)]
- else if std.endsWith(n, "Ei") then [2, std.pow(2, 60)]
- else error "Unknown numerical suffix in " + n;
- local n_len = std.length(n);
- std.parseInt(std.substr(n, 0, n_len - convert[0])) * convert[1]
- ),
-
- local remap(v, start, end, newstart) =
- if v >= start && v <= end then v - start + newstart else v,
- local remapChar(c, start, end, newstart) =
- std.char(remap(
- std.codepoint(c), std.codepoint(start), std.codepoint(end), std.codepoint(newstart)
- )),
- toLower(s):: (
- std.join("", [remapChar(c, "A", "Z", "a") for c in std.stringChars(s)])
- ),
- toUpper(s):: (
- std.join("", [remapChar(c, "a", "z", "A") for c in std.stringChars(s)])
- ),
-
- _Object(apiVersion, kind, name):: {
- local this = self,
- apiVersion: apiVersion,
- kind: kind,
- metadata: {
- name: name,
- labels: { name: std.join("-", std.split(this.metadata.name, ":")) },
- annotations: {},
+kube {
+ ClusterIssuer(name): kube._Object("certmanager.k8s.io/v1alpha1", "ClusterIssuer", name) {
+ spec: error "spec must be defined",
},
- },
-
- List(): {
- apiVersion: "v1",
- kind: "List",
- items_:: {},
- items: $.objectValues(self.items_),
- },
-
- Namespace(name): $._Object("v1", "Namespace", name) {
- },
-
- Endpoints(name): $._Object("v1", "Endpoints", name) {
- Ip(addr):: { ip: addr },
- Port(p):: { port: p },
-
- subsets: [],
- },
-
- Service(name): $._Object("v1", "Service", name) {
- local service = self,
-
- target_pod:: error "service target_pod required",
- port:: self.target_pod.spec.containers[0].ports[0].containerPort,
-
- // Helpers that format host:port in various ways
- host:: "%s.%s.svc" % [self.metadata.name, self.metadata.namespace],
- host_colon_port:: "%s:%s" % [self.host, self.spec.ports[0].port],
- http_url:: "http://%s/" % self.host_colon_port,
- proxy_urlpath:: "/api/v1/proxy/namespaces/%s/services/%s/" % [
- self.metadata.namespace,
- self.metadata.name,
- ],
- // Useful in Ingress rules
- name_port:: {
- serviceName: service.metadata.name,
- servicePort: service.spec.ports[0].port,
+ Issuer(name): kube._Object("certmanager.k8s.io/v1alpha1", "Issuer", name) {
+ spec: error "spec must be defined",
},
-
- spec: {
- selector: service.target_pod.metadata.labels,
- ports: [
- {
- port: service.port,
- targetPort: service.target_pod.spec.containers[0].ports[0].containerPort,
- },
- ],
- type: "ClusterIP",
+ Certificate(name): kube._Object("certmanager.k8s.io/v1alpha1", "Certificate", name) {
+ spec: error "spec must be defined",
},
- },
-
- PersistentVolume(name): $._Object("v1", "PersistentVolume", name) {
- spec: {},
- },
-
- // TODO: This is a terrible name
- PersistentVolumeClaimVolume(pvc): {
- persistentVolumeClaim: { claimName: pvc.metadata.name },
- },
-
- StorageClass(name): $._Object("storage.k8s.io/v1beta1", "StorageClass", name) {
- provisioner: error "provisioner required",
- },
-
- PersistentVolumeClaim(name): $._Object("v1", "PersistentVolumeClaim", name) {
- local pvc = self,
-
- storageClass:: null,
- storage:: error "storage required",
-
- metadata+: if pvc.storageClass != null then {
- annotations+: {
- "volume.beta.kubernetes.io/storage-class": pvc.storageClass,
- },
- } else {},
-
- spec: {
- resources: {
- requests: {
- storage: pvc.storage,
- },
- },
- accessModes: ["ReadWriteOnce"],
- },
- },
-
- Container(name): {
- name: name,
- image: error "container image value required",
- imagePullPolicy: if std.endsWith(self.image, ":latest") then "Always" else "IfNotPresent",
-
- envList(map):: [
- if std.type(map[x]) == "object" then { name: x, valueFrom: map[x] } else { name: x, value: map[x] }
- for x in std.objectFields(map)
- ],
-
- env_:: {},
- env: self.envList(self.env_),
-
- args_:: {},
- args: ["--%s=%s" % kv for kv in $.objectItems(self.args_)],
-
- ports_:: {},
- ports: $.mapToNamedList(self.ports_),
-
- volumeMounts_:: {},
- volumeMounts: $.mapToNamedList(self.volumeMounts_),
-
- stdin: false,
- tty: false,
- assert !self.tty || self.stdin : "tty=true requires stdin=true",
- },
-
- PodDisruptionBudget(name): $._Object("policy/v1beta1", "PodDisruptionBudget", name) {
- local this = self,
- target_pod:: error "target_pod required",
- spec: {
- minAvailable: 1,
- selector: {
- matchLabels: this.target_pod.metadata.labels,
- },
- },
- },
-
- Pod(name): $._Object("v1", "Pod", name) {
- spec: $.PodSpec,
- },
-
- PodSpec: {
- // The 'first' container is used in various defaults in k8s.
- local container_names = std.objectFields(self.containers_),
- default_container:: if std.length(container_names) > 1 then "default" else container_names[0],
- containers_:: {},
-
- local container_names_ordered = [self.default_container] + [n for n in container_names if n != self.default_container],
- containers: [{ name: $.hyphenate(name) } + self.containers_[name] for name in container_names_ordered if self.containers_[name] != null],
-
- // Note initContainers are inherently ordered, and using this
- // named object will lose that ordering. If order matters, then
- // manipulate `initContainers` directly (perhaps
- // appending/prepending to `super.initContainers` to mix+match
- // both approaches)
- initContainers_:: {},
- initContainers: [{ name: $.hyphenate(name) } + self.initContainers_[name] for name in std.objectFields(self.initContainers_) if self.initContainers_[name] != null],
-
- volumes_:: {},
- volumes: $.mapToNamedList(self.volumes_),
-
- imagePullSecrets: [],
-
- terminationGracePeriodSeconds: 30,
-
- assert std.length(self.containers) > 0 : "must have at least one container",
-
- // Return an array of pod's ports numbers
- ports(proto):: [
- p.containerPort
- for p in std.flattenArrays([
- c.ports
- for c in self.containers
- ])
- if (
- (!(std.objectHas(p, "protocol")) && proto == "TCP")
- ||
- ((std.objectHas(p, "protocol")) && p.protocol == proto)
- )
- ],
-
- },
-
- EmptyDirVolume(): {
- emptyDir: {},
- },
-
- HostPathVolume(path, type=""): {
- hostPath: { path: path, type: type },
- },
-
- GitRepoVolume(repository, revision): {
- gitRepo: {
- repository: repository,
-
- // "master" is possible, but should be avoided for production
- revision: revision,
- },
- },
-
- SecretVolume(secret): {
- secret: { secretName: secret.metadata.name },
- },
-
- ConfigMapVolume(configmap): {
- configMap: { name: configmap.metadata.name },
- },
-
- ConfigMap(name): $._Object("v1", "ConfigMap", name) {
- data: {},
-
- // I keep thinking data values can be any JSON type. This check
- // will remind me that they must be strings :(
- local nonstrings = [
- k
- for k in std.objectFields(self.data)
- if std.type(self.data[k]) != "string"
- ],
- assert std.length(nonstrings) == 0 : "data contains non-string values: %s" % [nonstrings],
- },
-
- // subtype of EnvVarSource
- ConfigMapRef(configmap, key): {
- assert std.objectHas(configmap.data, key) : "%s not in configmap.data" % [key],
- configMapKeyRef: {
- name: configmap.metadata.name,
- key: key,
- },
- },
-
- Secret(name): $._Object("v1", "Secret", name) {
- local secret = self,
-
- type: "Opaque",
- data_:: {},
- data: { [k]: std.base64(secret.data_[k]) for k in std.objectFields(secret.data_) },
- },
-
- // subtype of EnvVarSource
- SecretKeyRef(secret, key): {
- assert std.objectHas(secret.data, key) : "%s not in secret.data" % [key],
- secretKeyRef: {
- name: secret.metadata.name,
- key: key,
- },
- },
-
- // subtype of EnvVarSource
- FieldRef(key): {
- fieldRef: {
- apiVersion: "v1",
- fieldPath: key,
- },
- },
-
- // subtype of EnvVarSource
- ResourceFieldRef(key, divisor="1"): {
- resourceFieldRef: {
- resource: key,
- divisor: std.toString(divisor),
- },
- },
-
- Deployment(name): $._Object("apps/v1beta2", "Deployment", name) {
- local deployment = self,
-
- spec: {
- template: {
- spec: $.PodSpec,
- metadata: {
- labels: deployment.metadata.labels,
- annotations: {},
- },
- },
-
- selector: {
- matchLabels: deployment.spec.template.metadata.labels,
- },
-
- strategy: {
- type: "RollingUpdate",
-
- local pvcs = [
- v
- for v in deployment.spec.template.spec.volumes
- if std.objectHas(v, "persistentVolumeClaim")
- ],
- local is_stateless = std.length(pvcs) == 0,
-
- // Apps trying to maintain a majority quorum or similar will
- // want to tune these carefully.
- // NB: Upstream default is surge=1 unavail=1
- rollingUpdate: if is_stateless then {
- maxSurge: "25%", // rounds up
- maxUnavailable: "25%", // rounds down
- } else {
- // Poor-man's StatelessSet. Useful mostly with replicas=1.
- maxSurge: 0,
- maxUnavailable: 1,
- },
- },
-
- // NB: Upstream default is 0
- minReadySeconds: 30,
-
- replicas: 1,
- assert self.replicas >= 0,
- },
- },
-
- CrossVersionObjectReference(target): {
- apiVersion: target.apiVersion,
- kind: target.kind,
- name: target.metadata.name,
- },
-
- HorizontalPodAutoscaler(name): $._Object("autoscaling/v1", "HorizontalPodAutoscaler", name) {
- local hpa = self,
-
- target:: error "target required",
-
- spec: {
- scaleTargetRef: $.CrossVersionObjectReference(hpa.target),
-
- minReplicas: hpa.target.spec.replicas,
- maxReplicas: error "maxReplicas required",
-
- assert self.maxReplicas >= self.minReplicas,
- },
- },
-
- StatefulSet(name): $._Object("apps/v1beta2", "StatefulSet", name) {
- local sset = self,
-
- spec: {
- serviceName: name,
-
- updateStrategy: {
- type: "RollingUpdate",
- rollingUpdate: {
- partition: 0,
- },
- },
-
- template: {
- spec: $.PodSpec,
- metadata: {
- labels: sset.metadata.labels,
- annotations: {},
- },
- },
-
- selector: {
- matchLabels: sset.spec.template.metadata.labels,
- },
-
- volumeClaimTemplates_:: {},
- volumeClaimTemplates: [
- // StatefulSet is overly fussy about "changes" (even when
- // they're no-ops).
- // In particular annotations={} is apparently a "change",
- // since the comparison is ignorant of defaults.
- std.prune($.PersistentVolumeClaim($.hyphenate(kv[0])) + { apiVersion:: null, kind:: null } + kv[1])
- for kv in $.objectItems(self.volumeClaimTemplates_)
- ],
-
- replicas: 1,
- assert self.replicas >= 1,
- },
- },
-
- Job(name): $._Object("batch/v1", "Job", name) {
- local job = self,
-
- spec: $.JobSpec {
- template+: {
- metadata+: {
- labels: job.metadata.labels,
- },
- },
- },
- },
-
- // NB: kubernetes >= 1.8.x has batch/v1beta1 (olders were batch/v2alpha1)
- CronJob(name): $._Object("batch/v1beta1", "CronJob", name) {
- local cronjob = self,
-
- spec: {
- jobTemplate: {
- spec: $.JobSpec {
- template+: {
- metadata+: {
- labels: cronjob.metadata.labels,
- },
- },
- },
- },
-
- schedule: error "Need to provide spec.schedule",
- successfulJobsHistoryLimit: 10,
- failedJobsHistoryLimit: 20,
- // NB: upstream concurrencyPolicy default is "Allow"
- concurrencyPolicy: "Forbid",
- },
- },
-
- JobSpec: {
- local this = self,
-
- template: {
- spec: $.PodSpec {
- restartPolicy: "OnFailure",
- },
- },
-
- selector: {
- matchLabels: this.template.metadata.labels,
- },
-
- completions: 1,
- parallelism: 1,
- },
-
- DaemonSet(name): $._Object("apps/v1beta2", "DaemonSet", name) {
- local ds = self,
- spec: {
- updateStrategy: {
- type: "RollingUpdate",
- rollingUpdate: {
- maxUnavailable: 1,
- },
- },
- template: {
- metadata: {
- labels: ds.metadata.labels,
- annotations: {},
- },
- spec: $.PodSpec,
- },
-
- selector: {
- matchLabels: ds.spec.template.metadata.labels,
- },
- },
- },
-
- Ingress(name): $._Object("extensions/v1beta1", "Ingress", name) {
- spec: {},
-
- local rel_paths = [
- p.path
- for r in self.spec.rules
- for p in r.http.paths
- if !std.startsWith(p.path, "/")
- ],
- assert std.length(rel_paths) == 0 : "paths must be absolute: " + rel_paths,
- },
-
- ThirdPartyResource(name): $._Object("extensions/v1beta1", "ThirdPartyResource", name) {
- versions_:: [],
- versions: [{ name: n } for n in self.versions_],
- },
-
- CustomResourceDefinition(group, version, kind): {
- local this = self,
- apiVersion: "apiextensions.k8s.io/v1beta1",
- kind: "CustomResourceDefinition",
- metadata+: {
- name: this.spec.names.plural + "." + this.spec.group,
- },
- spec: {
- scope: "Namespaced",
- group: group,
- version: version,
- names: {
- kind: kind,
- singular: $.toLower(self.kind),
- plural: self.singular + "s",
- listKind: self.kind + "List",
- },
- },
- },
-
- ServiceAccount(name): $._Object("v1", "ServiceAccount", name) {
- },
-
- Role(name): $._Object("rbac.authorization.k8s.io/v1beta1", "Role", name) {
- rules: [],
- },
-
- ClusterRole(name): $.Role(name) {
- kind: "ClusterRole",
- },
-
- Group(name): {
- kind: "Group",
- name: name,
- apiGroup: "rbac.authorization.k8s.io",
- },
-
- User(name): {
- kind: "User",
- name: name,
- apiGroup: "rbac.authorization.k8s.io",
- },
-
- RoleBinding(name): $._Object("rbac.authorization.k8s.io/v1beta1", "RoleBinding", name) {
- local rb = self,
-
- subjects_:: [],
- subjects: [{
- kind: o.kind,
- namespace: o.metadata.namespace,
- name: o.metadata.name,
- } for o in self.subjects_],
-
- roleRef_:: error "roleRef is required",
- roleRef: {
- apiGroup: "rbac.authorization.k8s.io",
- kind: rb.roleRef_.kind,
- name: rb.roleRef_.metadata.name,
- },
- },
-
- ClusterRoleBinding(name): $.RoleBinding(name) {
- kind: "ClusterRoleBinding",
- },
-
- // NB: datalines_ can be used to reduce boilerplate importstr as:
- // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.data > mysec-ssdata.txt
- // datalines_: importstr "mysec-ssddata.txt"
- SealedSecret(name): $._Object("bitnami.com/v1alpha1", "SealedSecret", name) {
- spec: {
- data:
- if self.datalines_ != ""
- then std.join("", std.split(self.datalines_, "\n"))
- else error "data or datalines_ required (output from: kubeseal | jq -r .spec.data)",
- datalines_:: "",
- },
- assert std.base64Decode(self.spec.data) != "",
- },
-
- // NB: helper method to access several Kubernetes objects podRef,
- // used below to extract its labels
- podRef(obj):: ({
- Pod: obj,
- Deployment: obj.spec.template,
- StatefulSet: obj.spec.template,
- DaemonSet: obj.spec.template,
- Job: obj.spec.template,
- CronJob: obj.spec.jobTemplate.spec.template,
- }[obj.kind]),
-
- // NB: return a { podSelector: ... } ready to use for e.g. NSPs (see below)
- // pod labels can be optionally filtered by their label name 2nd array arg
- podLabelsSelector(obj, filter=null):: {
- podSelector: std.prune({
- matchLabels:
- if filter != null then $.filterMapByFields($.podRef(obj).metadata.labels, filter)
- else $.podRef(obj).metadata.labels,
- }),
- },
-
- // NB: Returns an array as [{ port: num, protocol: "PROTO" }, {...}, ... ]
- // Need to split TCP, UDP logic to be able to dedup each set of protocol ports
- podsPorts(obj_list):: std.flattenArrays([
- [
- { port: port, protocol: protocol }
- for port in std.set(
- std.flattenArrays([$.podRef(obj).spec.ports(protocol) for obj in obj_list])
- )
- ]
- for protocol in ["TCP", "UDP"]
- ]),
-
- // NB: most of the "helper" stuff comes from above (podLabelsSelector, podsPorts),
- // NetworkPolicy returned object will have "Ingress", "Egress" policyTypes auto-set
- // based on populated spec.ingress or spec.egress
- // See tests/test-simple-validate.jsonnet for example(s).
- NetworkPolicy(name): $._Object("networking.k8s.io/v1", "NetworkPolicy", name) {
- local networkpolicy = self,
- spec: {
- policyTypes: std.prune([
- if networkpolicy.spec.ingress != [] then "Ingress" else null,
- if networkpolicy.spec.egress != [] then "Egress" else null,
- ]),
- ingress: $.objectValues(self.ingress_),
- ingress_:: {},
- egress: $.objectValues(self.egress_),
- egress_:: {},
- },
- },
}
diff --git a/kube/kube.upstream.libsonnet b/kube/kube.upstream.libsonnet
new file mode 100644
index 0000000..e20d872
--- /dev/null
+++ b/kube/kube.upstream.libsonnet
@@ -0,0 +1,702 @@
+// Generic library of Kubernetes objects (https://github.com/bitnami-labs/kube-libsonnet)
+//
+// Objects in this file follow the regular Kubernetes API object
+// schema with two exceptions:
+//
+// ## Optional helpers
+//
+// A few objects have defaults or additional "helper" hidden
+// (double-colon) fields that will help with common situations. For
+// example, `Service.target_pod` generates suitable `selector` and
+// `ports` blocks for the common case of a single-pod/single-port
+// service. If for some reason you don't want the helper, just
+// provide explicit values for the regular Kubernetes fields that the
+// helper *would* have generated, and the helper logic will be
+// ignored.
+//
+// ## The Underscore Convention:
+//
+// Various constructs in the Kubernetes API use JSON arrays to
+// represent unordered sets or named key/value maps. This is
+// particularly annoying with jsonnet since we want to use jsonnet's
+// powerful object merge operation with these constructs.
+//
+// To combat this, this library attempts to provide more "jsonnet
+// native" variants of these arrays in alternative hidden fields that
+// end with an underscore. For example, the `env_` block in
+// `Container`:
+// ```
+// kube.Container("foo") {
+// env_: { FOO: "bar" },
+// }
+// ```
+// ... produces the expected `container.env` JSON array:
+// ```
+// {
+// "env": [
+// { "name": "FOO", "value": "bar" }
+// ]
+// }
+// ```
+//
+// If you are confused by the underscore versions, or don't want them
+// in your situation then just ignore them and set the regular
+// non-underscore field as usual.
+//
+//
+// ## TODO
+//
+// TODO: Expand this to include all API objects.
+//
+// Should probably fill out all the defaults here too, so jsonnet can
+// reference them. In addition, jsonnet validation is more useful
+// (client-side, and gives better line information).
+
+{
+ // Returns array of values from given object. Does not include hidden fields.
+ objectValues(o):: [o[field] for field in std.objectFields(o)],
+
+ // Returns array of [key, value] pairs from given object. Does not include hidden fields.
+ objectItems(o):: [[k, o[k]] for k in std.objectFields(o)],
+
+ // Replace all occurrences of `_` with `-`.
+ hyphenate(s):: std.join("-", std.split(s, "_")),
+
+ // Convert an octal (as a string) to number,
+ parseOctal(s):: (
+ local len = std.length(s);
+ local leading = std.substr(s, 0, len - 1);
+ local last = std.parseInt(std.substr(s, len - 1, 1));
+ assert last < 8 : "found '%s' digit >= 8" % [last];
+ last + (if len > 1 then 8 * $.parseOctal(leading) else 0)
+ ),
+
+ // Convert {foo: {a: b}} to [{name: foo, a: b}]
+ mapToNamedList(o):: [{ name: $.hyphenate(n) } + o[n] for n in std.objectFields(o)],
+
+ // Return object containing only these fields elements
+ filterMapByFields(o, fields): { [field]: o[field] for field in std.setInter(std.objectFields(o), fields) },
+
+ // Convert from SI unit suffixes to regular number
+ siToNum(n):: (
+ local convert =
+ if std.endsWith(n, "m") then [1, 0.001]
+ else if std.endsWith(n, "K") then [1, 1e3]
+ else if std.endsWith(n, "M") then [1, 1e6]
+ else if std.endsWith(n, "G") then [1, 1e9]
+ else if std.endsWith(n, "T") then [1, 1e12]
+ else if std.endsWith(n, "P") then [1, 1e15]
+ else if std.endsWith(n, "E") then [1, 1e18]
+ else if std.endsWith(n, "Ki") then [2, std.pow(2, 10)]
+ else if std.endsWith(n, "Mi") then [2, std.pow(2, 20)]
+ else if std.endsWith(n, "Gi") then [2, std.pow(2, 30)]
+ else if std.endsWith(n, "Ti") then [2, std.pow(2, 40)]
+ else if std.endsWith(n, "Pi") then [2, std.pow(2, 50)]
+ else if std.endsWith(n, "Ei") then [2, std.pow(2, 60)]
+ else error "Unknown numerical suffix in " + n;
+ local n_len = std.length(n);
+ std.parseInt(std.substr(n, 0, n_len - convert[0])) * convert[1]
+ ),
+
+ local remap(v, start, end, newstart) =
+ if v >= start && v <= end then v - start + newstart else v,
+ local remapChar(c, start, end, newstart) =
+ std.char(remap(
+ std.codepoint(c), std.codepoint(start), std.codepoint(end), std.codepoint(newstart)
+ )),
+ toLower(s):: (
+ std.join("", [remapChar(c, "A", "Z", "a") for c in std.stringChars(s)])
+ ),
+ toUpper(s):: (
+ std.join("", [remapChar(c, "a", "z", "A") for c in std.stringChars(s)])
+ ),
+
+ _Object(apiVersion, kind, name):: {
+ local this = self,
+ apiVersion: apiVersion,
+ kind: kind,
+ metadata: {
+ name: name,
+ labels: { name: std.join("-", std.split(this.metadata.name, ":")) },
+ annotations: {},
+ },
+ },
+
+ List(): {
+ apiVersion: "v1",
+ kind: "List",
+ items_:: {},
+ items: $.objectValues(self.items_),
+ },
+
+ Namespace(name): $._Object("v1", "Namespace", name) {
+ },
+
+ Endpoints(name): $._Object("v1", "Endpoints", name) {
+ Ip(addr):: { ip: addr },
+ Port(p):: { port: p },
+
+ subsets: [],
+ },
+
+ Service(name): $._Object("v1", "Service", name) {
+ local service = self,
+
+ target_pod:: error "service target_pod required",
+ port:: self.target_pod.spec.containers[0].ports[0].containerPort,
+
+ // Helpers that format host:port in various ways
+ host:: "%s.%s.svc" % [self.metadata.name, self.metadata.namespace],
+ host_colon_port:: "%s:%s" % [self.host, self.spec.ports[0].port],
+ http_url:: "http://%s/" % self.host_colon_port,
+ proxy_urlpath:: "/api/v1/proxy/namespaces/%s/services/%s/" % [
+ self.metadata.namespace,
+ self.metadata.name,
+ ],
+ // Useful in Ingress rules
+ name_port:: {
+ serviceName: service.metadata.name,
+ servicePort: service.spec.ports[0].port,
+ },
+
+ spec: {
+ selector: service.target_pod.metadata.labels,
+ ports: [
+ {
+ port: service.port,
+ targetPort: service.target_pod.spec.containers[0].ports[0].containerPort,
+ },
+ ],
+ type: "ClusterIP",
+ },
+ },
+
+ PersistentVolume(name): $._Object("v1", "PersistentVolume", name) {
+ spec: {},
+ },
+
+ // TODO: This is a terrible name
+ PersistentVolumeClaimVolume(pvc): {
+ persistentVolumeClaim: { claimName: pvc.metadata.name },
+ },
+
+ StorageClass(name): $._Object("storage.k8s.io/v1beta1", "StorageClass", name) {
+ provisioner: error "provisioner required",
+ },
+
+ PersistentVolumeClaim(name): $._Object("v1", "PersistentVolumeClaim", name) {
+ local pvc = self,
+
+ storageClass:: null,
+ storage:: error "storage required",
+
+ metadata+: if pvc.storageClass != null then {
+ annotations+: {
+ "volume.beta.kubernetes.io/storage-class": pvc.storageClass,
+ },
+ } else {},
+
+ spec: {
+ resources: {
+ requests: {
+ storage: pvc.storage,
+ },
+ },
+ accessModes: ["ReadWriteOnce"],
+ },
+ },
+
+ Container(name): {
+ name: name,
+ image: error "container image value required",
+ imagePullPolicy: if std.endsWith(self.image, ":latest") then "Always" else "IfNotPresent",
+
+ envList(map):: [
+ if std.type(map[x]) == "object" then { name: x, valueFrom: map[x] } else { name: x, value: map[x] }
+ for x in std.objectFields(map)
+ ],
+
+ env_:: {},
+ env: self.envList(self.env_),
+
+ args_:: {},
+ args: ["--%s=%s" % kv for kv in $.objectItems(self.args_)],
+
+ ports_:: {},
+ ports: $.mapToNamedList(self.ports_),
+
+ volumeMounts_:: {},
+ volumeMounts: $.mapToNamedList(self.volumeMounts_),
+
+ stdin: false,
+ tty: false,
+ assert !self.tty || self.stdin : "tty=true requires stdin=true",
+ },
+
+ PodDisruptionBudget(name): $._Object("policy/v1beta1", "PodDisruptionBudget", name) {
+ local this = self,
+ target_pod:: error "target_pod required",
+ spec: {
+ minAvailable: 1,
+ selector: {
+ matchLabels: this.target_pod.metadata.labels,
+ },
+ },
+ },
+
+ Pod(name): $._Object("v1", "Pod", name) {
+ spec: $.PodSpec,
+ },
+
+ PodSpec: {
+ // The 'first' container is used in various defaults in k8s.
+ local container_names = std.objectFields(self.containers_),
+ default_container:: if std.length(container_names) > 1 then "default" else container_names[0],
+ containers_:: {},
+
+ local container_names_ordered = [self.default_container] + [n for n in container_names if n != self.default_container],
+ containers: [{ name: $.hyphenate(name) } + self.containers_[name] for name in container_names_ordered if self.containers_[name] != null],
+
+ // Note initContainers are inherently ordered, and using this
+ // named object will lose that ordering. If order matters, then
+ // manipulate `initContainers` directly (perhaps
+ // appending/prepending to `super.initContainers` to mix+match
+ // both approaches)
+ initContainers_:: {},
+ initContainers: [{ name: $.hyphenate(name) } + self.initContainers_[name] for name in std.objectFields(self.initContainers_) if self.initContainers_[name] != null],
+
+ volumes_:: {},
+ volumes: $.mapToNamedList(self.volumes_),
+
+ imagePullSecrets: [],
+
+ terminationGracePeriodSeconds: 30,
+
+ assert std.length(self.containers) > 0 : "must have at least one container",
+
+ // Return an array of pod's ports numbers
+ ports(proto):: [
+ p.containerPort
+ for p in std.flattenArrays([
+ c.ports
+ for c in self.containers
+ ])
+ if (
+ (!(std.objectHas(p, "protocol")) && proto == "TCP")
+ ||
+ ((std.objectHas(p, "protocol")) && p.protocol == proto)
+ )
+ ],
+
+ },
+
+ EmptyDirVolume(): {
+ emptyDir: {},
+ },
+
+ HostPathVolume(path, type=""): {
+ hostPath: { path: path, type: type },
+ },
+
+ GitRepoVolume(repository, revision): {
+ gitRepo: {
+ repository: repository,
+
+ // "master" is possible, but should be avoided for production
+ revision: revision,
+ },
+ },
+
+ SecretVolume(secret): {
+ secret: { secretName: secret.metadata.name },
+ },
+
+ ConfigMapVolume(configmap): {
+ configMap: { name: configmap.metadata.name },
+ },
+
+ ConfigMap(name): $._Object("v1", "ConfigMap", name) {
+ data: {},
+
+ // I keep thinking data values can be any JSON type. This check
+ // will remind me that they must be strings :(
+ local nonstrings = [
+ k
+ for k in std.objectFields(self.data)
+ if std.type(self.data[k]) != "string"
+ ],
+ assert std.length(nonstrings) == 0 : "data contains non-string values: %s" % [nonstrings],
+ },
+
+ // subtype of EnvVarSource
+ ConfigMapRef(configmap, key): {
+ assert std.objectHas(configmap.data, key) : "%s not in configmap.data" % [key],
+ configMapKeyRef: {
+ name: configmap.metadata.name,
+ key: key,
+ },
+ },
+
+ Secret(name): $._Object("v1", "Secret", name) {
+ local secret = self,
+
+ type: "Opaque",
+ data_:: {},
+ data: { [k]: std.base64(secret.data_[k]) for k in std.objectFields(secret.data_) },
+ },
+
+ // subtype of EnvVarSource
+ SecretKeyRef(secret, key): {
+ assert std.objectHas(secret.data, key) : "%s not in secret.data" % [key],
+ secretKeyRef: {
+ name: secret.metadata.name,
+ key: key,
+ },
+ },
+
+ // subtype of EnvVarSource
+ FieldRef(key): {
+ fieldRef: {
+ apiVersion: "v1",
+ fieldPath: key,
+ },
+ },
+
+ // subtype of EnvVarSource
+ ResourceFieldRef(key, divisor="1"): {
+ resourceFieldRef: {
+ resource: key,
+ divisor: std.toString(divisor),
+ },
+ },
+
+ Deployment(name): $._Object("apps/v1beta2", "Deployment", name) {
+ local deployment = self,
+
+ spec: {
+ template: {
+ spec: $.PodSpec,
+ metadata: {
+ labels: deployment.metadata.labels,
+ annotations: {},
+ },
+ },
+
+ selector: {
+ matchLabels: deployment.spec.template.metadata.labels,
+ },
+
+ strategy: {
+ type: "RollingUpdate",
+
+ local pvcs = [
+ v
+ for v in deployment.spec.template.spec.volumes
+ if std.objectHas(v, "persistentVolumeClaim")
+ ],
+ local is_stateless = std.length(pvcs) == 0,
+
+ // Apps trying to maintain a majority quorum or similar will
+ // want to tune these carefully.
+ // NB: Upstream default is surge=1 unavail=1
+ rollingUpdate: if is_stateless then {
+ maxSurge: "25%", // rounds up
+ maxUnavailable: "25%", // rounds down
+ } else {
+ // Poor-man's StatelessSet. Useful mostly with replicas=1.
+ maxSurge: 0,
+ maxUnavailable: 1,
+ },
+ },
+
+ // NB: Upstream default is 0
+ minReadySeconds: 30,
+
+ replicas: 1,
+ assert self.replicas >= 0,
+ },
+ },
+
+ CrossVersionObjectReference(target): {
+ apiVersion: target.apiVersion,
+ kind: target.kind,
+ name: target.metadata.name,
+ },
+
+ HorizontalPodAutoscaler(name): $._Object("autoscaling/v1", "HorizontalPodAutoscaler", name) {
+ local hpa = self,
+
+ target:: error "target required",
+
+ spec: {
+ scaleTargetRef: $.CrossVersionObjectReference(hpa.target),
+
+ minReplicas: hpa.target.spec.replicas,
+ maxReplicas: error "maxReplicas required",
+
+ assert self.maxReplicas >= self.minReplicas,
+ },
+ },
+
+ StatefulSet(name): $._Object("apps/v1beta2", "StatefulSet", name) {
+ local sset = self,
+
+ spec: {
+ serviceName: name,
+
+ updateStrategy: {
+ type: "RollingUpdate",
+ rollingUpdate: {
+ partition: 0,
+ },
+ },
+
+ template: {
+ spec: $.PodSpec,
+ metadata: {
+ labels: sset.metadata.labels,
+ annotations: {},
+ },
+ },
+
+ selector: {
+ matchLabels: sset.spec.template.metadata.labels,
+ },
+
+ volumeClaimTemplates_:: {},
+ volumeClaimTemplates: [
+ // StatefulSet is overly fussy about "changes" (even when
+ // they're no-ops).
+ // In particular annotations={} is apparently a "change",
+ // since the comparison is ignorant of defaults.
+ std.prune($.PersistentVolumeClaim($.hyphenate(kv[0])) + { apiVersion:: null, kind:: null } + kv[1])
+ for kv in $.objectItems(self.volumeClaimTemplates_)
+ ],
+
+ replicas: 1,
+ assert self.replicas >= 1,
+ },
+ },
+
+ Job(name): $._Object("batch/v1", "Job", name) {
+ local job = self,
+
+ spec: $.JobSpec {
+ template+: {
+ metadata+: {
+ labels: job.metadata.labels,
+ },
+ },
+ },
+ },
+
+ // NB: kubernetes >= 1.8.x has batch/v1beta1 (olders were batch/v2alpha1)
+ CronJob(name): $._Object("batch/v1beta1", "CronJob", name) {
+ local cronjob = self,
+
+ spec: {
+ jobTemplate: {
+ spec: $.JobSpec {
+ template+: {
+ metadata+: {
+ labels: cronjob.metadata.labels,
+ },
+ },
+ },
+ },
+
+ schedule: error "Need to provide spec.schedule",
+ successfulJobsHistoryLimit: 10,
+ failedJobsHistoryLimit: 20,
+ // NB: upstream concurrencyPolicy default is "Allow"
+ concurrencyPolicy: "Forbid",
+ },
+ },
+
+ JobSpec: {
+ local this = self,
+
+ template: {
+ spec: $.PodSpec {
+ restartPolicy: "OnFailure",
+ },
+ },
+
+ selector: {
+ matchLabels: this.template.metadata.labels,
+ },
+
+ completions: 1,
+ parallelism: 1,
+ },
+
+ DaemonSet(name): $._Object("apps/v1beta2", "DaemonSet", name) {
+ local ds = self,
+ spec: {
+ updateStrategy: {
+ type: "RollingUpdate",
+ rollingUpdate: {
+ maxUnavailable: 1,
+ },
+ },
+ template: {
+ metadata: {
+ labels: ds.metadata.labels,
+ annotations: {},
+ },
+ spec: $.PodSpec,
+ },
+
+ selector: {
+ matchLabels: ds.spec.template.metadata.labels,
+ },
+ },
+ },
+
+ Ingress(name): $._Object("extensions/v1beta1", "Ingress", name) {
+ spec: {},
+
+ local rel_paths = [
+ p.path
+ for r in self.spec.rules
+ for p in r.http.paths
+ if !std.startsWith(p.path, "/")
+ ],
+ assert std.length(rel_paths) == 0 : "paths must be absolute: " + rel_paths,
+ },
+
+ ThirdPartyResource(name): $._Object("extensions/v1beta1", "ThirdPartyResource", name) {
+ versions_:: [],
+ versions: [{ name: n } for n in self.versions_],
+ },
+
+ CustomResourceDefinition(group, version, kind): {
+ local this = self,
+ apiVersion: "apiextensions.k8s.io/v1beta1",
+ kind: "CustomResourceDefinition",
+ metadata+: {
+ name: this.spec.names.plural + "." + this.spec.group,
+ },
+ spec: {
+ scope: "Namespaced",
+ group: group,
+ version: version,
+ names: {
+ kind: kind,
+ singular: $.toLower(self.kind),
+ plural: self.singular + "s",
+ listKind: self.kind + "List",
+ },
+ },
+ },
+
+ ServiceAccount(name): $._Object("v1", "ServiceAccount", name) {
+ },
+
+ Role(name): $._Object("rbac.authorization.k8s.io/v1beta1", "Role", name) {
+ rules: [],
+ },
+
+ ClusterRole(name): $.Role(name) {
+ kind: "ClusterRole",
+ },
+
+ Group(name): {
+ kind: "Group",
+ name: name,
+ apiGroup: "rbac.authorization.k8s.io",
+ },
+
+ User(name): {
+ kind: "User",
+ name: name,
+ apiGroup: "rbac.authorization.k8s.io",
+ },
+
+ RoleBinding(name): $._Object("rbac.authorization.k8s.io/v1beta1", "RoleBinding", name) {
+ local rb = self,
+
+ subjects_:: [],
+ subjects: [{
+ kind: o.kind,
+ namespace: o.metadata.namespace,
+ name: o.metadata.name,
+ } for o in self.subjects_],
+
+ roleRef_:: error "roleRef is required",
+ roleRef: {
+ apiGroup: "rbac.authorization.k8s.io",
+ kind: rb.roleRef_.kind,
+ name: rb.roleRef_.metadata.name,
+ },
+ },
+
+ ClusterRoleBinding(name): $.RoleBinding(name) {
+ kind: "ClusterRoleBinding",
+ },
+
+ // NB: datalines_ can be used to reduce boilerplate importstr as:
+ // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.data > mysec-ssdata.txt
+ // datalines_: importstr "mysec-ssddata.txt"
+ SealedSecret(name): $._Object("bitnami.com/v1alpha1", "SealedSecret", name) {
+ spec: {
+ data:
+ if self.datalines_ != ""
+ then std.join("", std.split(self.datalines_, "\n"))
+ else error "data or datalines_ required (output from: kubeseal | jq -r .spec.data)",
+ datalines_:: "",
+ },
+ assert std.base64Decode(self.spec.data) != "",
+ },
+
+ // NB: helper method to access several Kubernetes objects podRef,
+ // used below to extract its labels
+ podRef(obj):: ({
+ Pod: obj,
+ Deployment: obj.spec.template,
+ StatefulSet: obj.spec.template,
+ DaemonSet: obj.spec.template,
+ Job: obj.spec.template,
+ CronJob: obj.spec.jobTemplate.spec.template,
+ }[obj.kind]),
+
+ // NB: return a { podSelector: ... } ready to use for e.g. NSPs (see below)
+ // pod labels can be optionally filtered by their label name 2nd array arg
+ podLabelsSelector(obj, filter=null):: {
+ podSelector: std.prune({
+ matchLabels:
+ if filter != null then $.filterMapByFields($.podRef(obj).metadata.labels, filter)
+ else $.podRef(obj).metadata.labels,
+ }),
+ },
+
+ // NB: Returns an array as [{ port: num, protocol: "PROTO" }, {...}, ... ]
+ // Need to split TCP, UDP logic to be able to dedup each set of protocol ports
+ podsPorts(obj_list):: std.flattenArrays([
+ [
+ { port: port, protocol: protocol }
+ for port in std.set(
+ std.flattenArrays([$.podRef(obj).spec.ports(protocol) for obj in obj_list])
+ )
+ ]
+ for protocol in ["TCP", "UDP"]
+ ]),
+
+ // NB: most of the "helper" stuff comes from above (podLabelsSelector, podsPorts),
+ // NetworkPolicy returned object will have "Ingress", "Egress" policyTypes auto-set
+ // based on populated spec.ingress or spec.egress
+ // See tests/test-simple-validate.jsonnet for example(s).
+ NetworkPolicy(name): $._Object("networking.k8s.io/v1", "NetworkPolicy", name) {
+ local networkpolicy = self,
+ spec: {
+ policyTypes: std.prune([
+ if networkpolicy.spec.ingress != [] then "Ingress" else null,
+ if networkpolicy.spec.egress != [] then "Egress" else null,
+ ]),
+ ingress: $.objectValues(self.ingress_),
+ ingress_:: {},
+ egress: $.objectValues(self.egress_),
+ egress_:: {},
+ },
+ },
+}