Full Code of bitnami-labs/kube-libsonnet for AI

master 6c3dd4a19536 cached
46 files
136.5 KB
33.0k tokens
1 requests
Download .txt
Repository: bitnami-labs/kube-libsonnet
Branch: master
Commit: 6c3dd4a19536
Files: 46
Total size: 136.5 KB

Directory structure:
gitextract_n5_iu0de/

├── .gitattributes
├── .travis.yml
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── bitnami.libsonnet
├── examples/
│   ├── guestbook/
│   │   └── guestbook.jsonnet
│   └── wordpress/
│       ├── backend.jsonnet
│       ├── frontend.jsonnet
│       └── wordpress.jsonnet
├── kube-platforms.libsonnet
├── kube.libsonnet
├── tests/
│   ├── Dockerfile
│   ├── Makefile
│   ├── docker-compose.yaml
│   ├── golden/
│   │   ├── init-kube.json
│   │   ├── test-Ingress-2ndport.pass.json
│   │   ├── test-Ingress-port_num_only.pass.json
│   │   ├── test-SealedSecret.pass.json
│   │   ├── test-Service-container_index.pass.json
│   │   ├── test-gke-ManagedCertificate.pass.json
│   │   ├── test-simple-validate.pass.json
│   │   └── unittests.pass.json
│   ├── init-kube.jsonnet
│   ├── k3s-e2e-test.sh
│   ├── test-Ingress-2ndport.pass.jsonnet
│   ├── test-Ingress-name_port.fail.jsonnet
│   ├── test-Ingress-port_num_only.pass.jsonnet
│   ├── test-PDB-no-spec.fail.jsonnet
│   ├── test-PDB-wrong-spec.fail.jsonnet
│   ├── test-Pod-no_containers_array.fail.jsonnet
│   ├── test-Pod-no_containers_map.fail.jsonnet
│   ├── test-Pod-secretmount.fail.jsonnet
│   ├── test-SealedSecret.fail.jsonnet
│   ├── test-SealedSecret.pass.json
│   ├── test-SealedSecret.pass.jsonnet
│   ├── test-Service-container_index.fail.jsonnet
│   ├── test-Service-container_index.pass.jsonnet
│   ├── test-gke-ManagedCertificate.fail.jsonnet
│   ├── test-gke-ManagedCertificate.pass.jsonnet
│   ├── test-simple-validate.pass.jsonnet
│   └── unittests.pass.jsonnet
└── utils.libsonnet

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
tests/golden/* linguist-generated


================================================
FILE: .travis.yml
================================================
language: bash

os:
  - linux

services:
  - docker

before_install: # update to docker-ce
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) edge"
  - sudo apt-get update
  - sudo apt-get -y install docker-ce

script:
  - make tests


================================================
FILE: CODEOWNERS
================================================
*  @dbarranco @jbianquetti-nami


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Code of Conduct

As contributors and maintainers of the "Bitnami Charts" project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.

Communication through any of Bitnami's channels (GitHub, mailing lists, Twitter, and so on) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.

We promise to extend courtesy and respect to everyone involved in this project, regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the "Bitnami Charts" project to do the same.

If any member of the community violates this code of conduct, the maintainers of the "Bitnami Charts" project may take action, including removing issues, comments, and PRs or blocking accounts, as deemed appropriate.

If you are subjected to or witness unacceptable behavior, or have any other concerns, please communicate with us.

If you have suggestions to improve this Code of Conduct, please submit an issue or PR.

**Attribution**

This Code of Conduct is adapted from the Angular project available at this page: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: Makefile
================================================
# Originally taken from https://github.com/bitnami-labs/kube-manifests/,
# trimmed down to only run lib testing.
#
# Provides 'test' target.  Uses docker.
all:
	@echo make tests

tests:
	make -C tests

.PHONY: all tests


================================================
FILE: README.md
================================================
## WARNING: This repository is no longer actively maintained by Bitnami/VMware.
We have made the difficult decision to stop driving this project and therefore we will no longer actively respond to issues or pull requests.

There is a new initiative to continue maintaining this project outside of Bitnami/VMware at https://github.com/kube-libsonnet/kube-libsonnet

[![Build Status](https://travis-ci.org/bitnami-labs/kube-libsonnet.svg?branch=master)](https://travis-ci.org/bitnami-labs/kube-libsonnet)
# kube-libsonnet

This repo has been originally populated by the `lib/` folder contents
from `https://github.com/bitnami-labs/kube-manifests` as of Mar/2018,
aiming to provide a library of `jsonnet` manifests for common
Kubernetes objects (such as `Deployment`, `Service`, `Ingress`, etc).

Accordingly, above `kube-manifests` has been changed to use this repo as
a git submodule, i.e.:

    $ git submodule add https://github.com/bitnami-labs/kube-libsonnet
    $ cat .gitmodules
    [submodule "lib"]
    path = lib
    url = https://github.com/bitnami-labs/kube-libsonnet

## Testing

Unit and e2e-ish testing at tests/, needs usable `docker-compose`
setup at node, will run a `k3s` "dummy" container to serve Kube API,
"enough" to run `kubecfg validate` against it:

    make tests

If you don't want that full kube-api stack (will then use your "local"
kubernetes configured environment), you can run:

    make -C tests local-tests kube-validate


================================================
FILE: SECURITY.md
================================================
# Security Release Process

The community has adopted this security disclosure and response policy to ensure we responsibly handle critical issues.


## Supported Versions

For a list of support versions that this project will potentially create security fixes for, please refer to the Releases page on this project's GitHub and/or project related documentation on release cadence and support.


## Reporting a Vulnerability - Private Disclosure Process

Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to this project privately, to minimize attacks against current users  before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.

If you know of a publicly disclosed security vulnerability for this project, please **IMMEDIATELY** contact the maintainers of this project privately. The use of encrypted email is encouraged.


**IMPORTANT: Do not file public issues on GitHub for security vulnerabilities**

To report a vulnerability or a security-related issue, please contact the maintainers with enough details through one of the following channels: 
* Directly via their individual email addresses
* Open a [GitHub Security Advisory](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). This allows for anyone to report security vulnerabilities directly and privately to the maintainers via GitHub. Note that this option may not be present for every repository.

The report will be fielded by the maintainers who have committer and release permissions. Feedback will be sent within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime. 

Do not report non-security-impacting bugs through this channel. Use GitHub issues for all non-security-impacting bugs.


## Proposed Report Content

Provide a descriptive title and in the description of the report include the following information:

*   Basic identity information, such as your name and your affiliation or company.
*   Detailed steps to reproduce the vulnerability  (POC scripts, screenshots, and logs are all helpful to us).
*   Description of the effects of the vulnerability on this project and the related hardware and software configurations, so that the maintainers can reproduce it.
*   How the vulnerability affects this project's usage and an estimation of the attack surface, if there is one.
*   List other projects or dependencies that were used in conjunction with this project to produce the vulnerability.


## When to report a vulnerability

*   When you think this project has a potential security vulnerability.
*   When you suspect a potential vulnerability but you are unsure that it impacts this project.
*   When you know of or suspect a potential vulnerability on another project that is used by this project.


## Patch, Release, and Disclosure

The maintainers will respond to vulnerability reports as follows:

1. The maintainers will investigate the vulnerability and determine its effects and criticality.
2. If the issue is not deemed to be a vulnerability, the maintainers will follow up with a detailed reason for rejection.
3. The maintainers will initiate a conversation with the reporter within 3 business days.
4. If a vulnerability is acknowledged and the timeline for a fix is determined, the maintainers will work on a plan to communicate with the appropriate community, including identifying mitigating steps that affected users can take to protect themselves until the fix is rolled out.
5. The maintainers will also create a [Security Advisory](https://docs.github.com/en/code-security/repository-security-advisories/publishing-a-repository-security-advisory) using the [CVSS Calculator](https://www.first.org/cvss/calculator/3.0), if it is not created yet.  The maintainers make the final call on the calculated CVSS; it is better to move quickly than making the CVSS perfect. Issues may also be reported to [Mitre](https://cve.mitre.org/) using this [scoring calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The draft advisory will initially be set to private.
6. The maintainers will work on fixing the vulnerability and perform internal testing before preparing to roll out the fix.
7. Once the fix is confirmed, the maintainers will patch the vulnerability in the next patch or minor release, and backport a patch release into all earlier supported releases.


## Public Disclosure Process

The maintainers publish the public advisory to this project's community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog, and other channels will assist in educating the project's users and rolling out the patched release to affected users.

The maintainers will also publish any mitigating steps users can take until the fix can be applied to their instances. This project's distributors will handle creating and publishing their own security advisories.


## Confidentiality, integrity and availability

We consider vulnerabilities leading to the compromise of data confidentiality, elevation of privilege, or integrity to be our highest priority concerns. Availability, in particular in areas relating to DoS and resource exhaustion, is also a serious security concern. The maintainer team takes all vulnerabilities, potential vulnerabilities, and suspected vulnerabilities seriously and will investigate them in an urgent and expeditious manner.

Note that we do not currently consider the default settings for this project to be secure-by-default. It is necessary for operators to explicitly configure settings, role based access control, and other resource related features in this project to provide a hardened environment. We will not act on any security disclosure that relates to a lack of safe defaults. Over time, we will work towards improved safe-by-default configuration, taking into account backwards compatibility.


================================================
FILE: bitnami.libsonnet
================================================
// Generic stuff is in kube.libsonnet - this file contains
// bitnami-specific conventions.

local kube = import "kube.libsonnet";

local perCloudSvcAnnotations(cloud, internal, service) = (
  {
    aws: {
      "service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled": "true",
      "service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout": std.toString(service.target_pod.spec.terminationGracePeriodSeconds),
      // Use PROXY protocol (nginx supports this too)
      "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*",
      // Does LB do NAT or DSR? (OnlyLocal implies DSR)
      // https://kubernetes.io/docs/tutorials/services/source-ip/
      // NB: Don't enable this without modifying set-real-ip-from above!
      // Not supported on aws in k8s 1.5 - immediate close / serves 503s.
      //"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
    },
    gke: {},
  }[cloud] + if internal then {
    aws: {
      "service.beta.kubernetes.io/aws-load-balancer-internal": "0.0.0.0/0",
    },
    gke: {
      "cloud.google.com/load-balancer-type": "internal",
    },
  }[cloud] else {}
);

local perCloudSvcSpec(cloud) = (
  {
    aws: {},
    // Required to get real src IP address, which also allows proper
    // ingress.kubernetes.io/whitelist-source-range matching
    gke: { externalTrafficPolicy: "Local" },
  }[cloud]
);

{
  ElbService(name, cloud, internal): kube.Service(name) {
    local service = self,

    metadata+: {
      annotations+: perCloudSvcAnnotations(cloud, internal, service),
    },
    spec+: { type: "LoadBalancer" } + perCloudSvcSpec(cloud),
  },

  Ingress(name, class=null): kube.Ingress(name) {
    local ing = self,

    host:: error "host required",
    target_svc:: error "target_svc required",
    // Default to single-service - override if you want something else.
    paths:: [
      {
        path: "/",
        backend: ing.target_svc.name_port,
        pathType: "ImplementationSpecific",
      },
    ],
    secretName:: "%s-cert" % [ing.metadata.name],

    // cert_provider can either be:
    // - "cm-dns": cert-manager using route53 for ACME dns-01 challenge (default)
    // - "cm-http": cert-manager using ACME http, requires public ingress
    cert_provider:: $.CertManager.default_ingress_provider,

    metadata+: $.CertManager.IngressMeta[ing.cert_provider] {
      annotations+: {
        // Add ingress class iff specified
        [if class != null then "kubernetes.io/ingress.class" else null]: class,
      },
    },
    spec+: {
      tls: [
        {
          hosts: std.set([r.host for r in ing.spec.rules]),
          secretName: ing.secretName,
        },
      ],

      rules: [
        {
          host: ing.host,
          http: {
            paths: ing.paths,
          },
        },
      ],
    },
  },

  PromScrape(port): {
    local scrape = self,
    prom_path:: "/metrics",

    metadata+: {
      annotations+: {
        "prometheus.io/scrape": "true",
        "prometheus.io/port": std.toString(port),
        "prometheus.io/path": scrape.prom_path,
      },
    },
  },

  PodZoneAntiAffinityAnnotation(pod): {
    podAntiAffinity: {
      preferredDuringSchedulingIgnoredDuringExecution: [
        {
          weight: 50,
          podAffinityTerm: {
            labelSelector: { matchLabels: pod.metadata.labels },
            topologyKey: "failure-domain.beta.kubernetes.io/zone",
          },
        },
        {
          weight: 100,
          podAffinityTerm: {
            labelSelector: { matchLabels: pod.metadata.labels },
            topologyKey: "kubernetes.io/hostname",
          },
        },
      ],
    },
  },
  CertManager:: {
    // Deployed cluster issuers' names:
    cluster_issuers:: {
      acme_dns:: "letsencrypt-prod-dns",
      acme_http:: "letsencrypt-prod-http",
      in_cluster:: "in-cluster-issuer",
    },

    default_ingress_provider:: "cm-dns",
    IngressMeta:: {
      "cm-dns":: {
        annotations+: {
          "cert-manager.io/cluster-issuer": $.CertManager.cluster_issuers.acme_dns,
        },
      },
      "cm-http":: {
        annotations+: {
          "cert-manager.io/cluster-issuer": $.CertManager.cluster_issuers.acme_http,
        },
      },
    },


    // CertManager ClusterIssuer object
    ClusterIssuer(name):: kube._Object("cert-manager.io/v1alpha2", "ClusterIssuer", name),

    // CertManager Certificate object
    Certificate(name):: kube._Object("cert-manager.io/v1alpha2", "Certificate", name) {
      assert std.objectHas(self.metadata, "namespace") : "Certificate('%s') must set metadata.namespace" % self.metadata.name,
    },

    InCluster:: {
      // Broadest usage is ["any"], limit to mTLS usage:
      default_usages:: ["digital signature", "key encipherment"],
      // Ref to our in-cluster ClusterIssuer
      cluster_issuer:: $.CertManager.ClusterIssuer($.CertManager.cluster_issuers.in_cluster) {
        spec+: {
          selfSigned: {},
        },
      },
      // Use as:
      //   my_cert: kube.CertManager.InCluster.Certificate("my-tls-cert", "my-namespace")
      // to get a Kubernetes TLS secret named "my-tls-cert" in "my-namespace"
      Certificate(name, namespace):: $.CertManager.Certificate(name) {
        metadata+: { namespace: namespace },
        spec+: {
          secretName: name,
          issuerRef: kube.CrossVersionObjectReference($.CertManager.InCluster.cluster_issuer) {
            // issuerRef doesn't have the apiVersion field
            apiVersion:: null,
          },
          commonName: name,
          dnsNames: [
            name,
            "%s.%s" % [name, namespace],
            "%s.%s.svc" % [name, namespace],
          ],
          usages: $.CertManager.InCluster.default_usages,
        },
      },
    },
  },
}


================================================
FILE: examples/guestbook/guestbook.jsonnet
================================================
// Copyright 2017 The kubecfg authors
//
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

// Simple to demonstrate kubecfg using kube-libsonnet
// This should not necessarily be considered a model jsonnet example
// to build upon.

// This is a simple port to jsonnet of the standard guestbook example
// https://github.com/kubernetes/kubernetes/tree/master/examples/guestbook
//
// ```
// kubecfg update guestbook.jsonnet
//
// # poke at 
// - $(minikube service frontend), etc
// - kubectl proxy # then visit http://localhost:8001/api/v1/namespaces/default/services/frontend/proxy/ 
// kubecfg delete guestbook.jsonnet
// ```

local kube = import "../../kube.libsonnet";

{
  frontend_deployment: kube.Deployment("frontend") {
    spec+: {
      local my_spec = self,
      replicas: 3,
      template+: {
        spec+: {
          containers_+: {
            gb_fe: kube.Container("gb-frontend") {
              image: "gcr.io/google-samples/gb-frontend:v4",
              resources: { requests: { cpu: "100m", memory: "100Mi" } },
              env_+: {
                GET_HOSTS_FROM: "dns",
                NUMBER_REPLICAS: my_spec.replicas,
              },
              ports_+: { http: { containerPort: 80 } },
  }}}}}},

  frontend_service: kube.Service("frontend") {
    target_pod: $.frontend_deployment.spec.template,
    // spec+: { type: "LoadBalancer" },
  },

  redis_master_deployment: kube.Deployment("redis-master") {
    spec+: {
      template+: {
        spec+: {
          containers_+: {
            redis_master: kube.Container("redis-master") {
              image: "gcr.io/google_containers/redis:e2e",
              resources: { requests: { cpu: "100m", memory: "100Mi" } },
              ports_+: {
                redis: { containerPort: 6379 },
  }}}}}}},

  redis_master_service: kube.Service("redis-master") {
    target_pod: $.redis_master_deployment.spec.template,
  },

  redis_slave_deployment: kube.Deployment("redis-slave") {
    spec+: {
      replicas: 2,
      template+: {
        spec+: {
          containers_+: {
            redis_slave: kube.Container("redist-slave") {
              image: "gcr.io/google_samples/gb-redisslave:v1",
              resources: {
                requests: { cpu: "100m", memory: "100Mi" },
              },
              env_: {
                GET_HOSTS_FROM: "dns",
              },
              ports_+: {
                redis: { containerPort: 6379 },
  }}}}}}},

  redis_slave_service: kube.Service("redis-slave") {
    target_pod: $.redis_slave_deployment.spec.template,
  },
}


================================================
FILE: examples/wordpress/backend.jsonnet
================================================
local kube = import "../../kube.libsonnet";

local labels = {
  tier: "backend",
};

{
  backend: {
    secret: kube.Secret("mariadb") {
      metadata+: {
        labels+: labels,
      },
      data_+: {
        "database_name": "webserver_db",
        "database_user": "webserver_user",
        "database_password": "webserver_db_password",
        "replication_user": "replica_user",
        "replication_password": "replica_password",
        "root_user": "root_user",
        "root_password": "root_password"
    }},

    master: {
      local masterLabels = labels + {
        component: "master",
      },
      statefulset: kube.StatefulSet("mariadb-master") {
        metadata+: {
          labels+: masterLabels,
        },
        spec+: {
          template+: {
            spec+: {
              securityContext: {
                runAsUser: 1001,
                fsGroup: 1001,
              },
              containers_+: {
                default: kube.Container("mariadb") {
                  image: "bitnami/mariadb",
                  ports_+: { mysql: { containerPort: 3306 } },
                  env_+: {
                    MARIADB_REPLICATION_MODE: "master",
                    MARIADB_REPLICATION_USER: kube.SecretKeyRef($.backend.secret, "replication_user"),
                    MARIADB_REPLICATION_PASSWORD: kube.SecretKeyRef($.backend.secret, "replication_password"),
                    MARIADB_ROOT_USER: kube.SecretKeyRef($.backend.secret, "root_user"),
                    MARIADB_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, "root_password"),
                    MARIADB_USER: kube.SecretKeyRef($.backend.secret, "database_user"),
                    MARIADB_DATABASE: kube.SecretKeyRef($.backend.secret, "database_name"),
                    MARIADB_PASSWORD: kube.SecretKeyRef($.backend.secret, "database_password"),
                  },
                  livenessProbe: {
                    initialDelaySeconds: 40,
                    exec: {
                      command: [
                        "sh",
                        "-c",
                        "exec mysqladmin status -u$MARIADB_ROOT_USER -p$MARIADB_ROOT_PASSWORD",
                  ]}},
                  readinessProbe: self.livenessProbe {
                    initialDelaySeconds: 30,
                  },
                  volumeMounts_+: {
                    "mariadb-data": {
                      "mountPath": "/bitnami/mariadb",
                }}},
                metrics: kube.Container("metrics") {
                  image: "prom/mysqld-exporter:v0.10.0",
                  command: [
                    "sh",
                    "-c",
                    "DATA_SOURCE_NAME=\"$MARIADB_ROOT_USER:$MARIADB_ROOT_PASSWORD@(localhost:3306)/\" exec /bin/mysqld_exporter",
                  ],
                  ports_+: { metrics: { containerPort: 9104 } },
                  env_+: {
                    MARIADB_ROOT_USER: kube.SecretKeyRef($.backend.secret, "root_user"),
                    MARIADB_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, "root_password"),
                  },
                  livenessProbe: {
                    initialDelaySeconds: 15,
                    timeoutSeconds: 1,
                    httpGet: {
                      path: "/metrics",
                      port: 9104,
                  }},
                  readinessProbe: self.livenessProbe {
                    initialDelaySeconds: 5,
                    timeoutSeconds: 1,
          }}}}},
          volumeClaimTemplates_+: {
            "mariadb-data": {
              storage: "10Gi",
              metadata+: {
                labels+: masterLabels,
      }}}}},
      service: kube.Service("mariadb-master") {
        metadata+: {
          labels+: masterLabels,
          annotations+: {
            "prometheus.io/scrape": "true",
            "prometheus.io/port": "9104",
        }},
        target_pod: $.backend.master.statefulset.spec.template,
        spec+: {
          ports: [
            {
              name: "mariadb",
              port: 3306,
              targetPort: $.backend.master.statefulset.spec.template.spec.containers[0].ports[0].containerPort,
            },
            {
              name: "metrics",
              port: 9104,
              targetPort: $.backend.master.statefulset.spec.template.spec.containers[1].ports[0].containerPort,
    }]}}},

    slave: {
      local slaveLabels = labels + {
        component: "slave",
      },
      statefulset: kube.StatefulSet("mariadb-slave") {
        metadata+: {
          labels+: slaveLabels,
        },
        spec+: {
          template+: {
            spec+: {
              securityContext: {
                runAsUser: 1001,
                fsGroup: 1001,
              },
              containers_+: {
                default: kube.Container("mariadb") {
                  image: "bitnami/mariadb",
                  ports_+: { mysql: { containerPort: 3306 } },
                  env_+: {
                    MARIADB_REPLICATION_MODE: "slave",
                    MARIADB_REPLICATION_USER: kube.SecretKeyRef($.backend.secret, "replication_user"),
                    MARIADB_REPLICATION_PASSWORD: kube.SecretKeyRef($.backend.secret, "replication_password"),
                    MARIADB_MASTER_HOST: $.backend.master.service.metadata.name,
                    MARIADB_MASTER_ROOT_USER: kube.SecretKeyRef($.backend.secret, "root_user"),
                    MARIADB_MASTER_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, "root_password"),
                  },
                  livenessProbe: {
                    initialDelaySeconds: 40,
                    exec: {
                      command: [
                        "sh",
                        "-c",
                        "exec mysqladmin status -u$MARIADB_MASTER_ROOT_USER -p$MARIADB_MASTER_ROOT_PASSWORD",
                  ]}},
                  readinessProbe: self.livenessProbe {
                    initialDelaySeconds: 30,
                  },
                  volumeMounts_+: {
                    "mariadb-data": {
                      "mountPath": "/bitnami/mariadb",
                }}},
                metrics: kube.Container("metrics") {
                  image: "prom/mysqld-exporter:v0.10.0",
                  command: [
                    "sh",
                    "-c",
                    "DATA_SOURCE_NAME=\"$MARIADB_MASTER_ROOT_USER:$MARIADB_MASTER_ROOT_PASSWORD@(localhost:3306)/\" exec /bin/mysqld_exporter",
                  ],
                  ports_+: { metrics: { containerPort: 9104 } },
                  env_+: {
                    MARIADB_MASTER_ROOT_USER: kube.SecretKeyRef($.backend.secret, "root_user"),
                    MARIADB_MASTER_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, "root_password"),
                  },
                  livenessProbe: {
                    initialDelaySeconds: 15,
                    timeoutSeconds: 1,
                    httpGet: {
                      path: "/metrics",
                      port: 9104,
                  }},
                  readinessProbe: self.livenessProbe {
                    initialDelaySeconds: 5,
                    timeoutSeconds: 5,
          }}}}},
          volumeClaimTemplates_+: {
            "mariadb-data": {
              storage: "10Gi",
              metadata+: {
                labels+: slaveLabels,
      }}}}},
      service: kube.Service("mariadb-slave") {
        metadata+: {
          labels+: slaveLabels,
          annotations+: {
            "prometheus.io/scrape": "true",
            "prometheus.io/port": "9104",
        }},
        target_pod: $.backend.slave.statefulset.spec.template,
        spec+: {
          ports: [
            {
              name: "mariadb",
              port: 3306,
              targetPort: $.backend.slave.statefulset.spec.template.spec.containers[0].ports[0].containerPort,
            },
            {
              name: "metrics",
              port: 9104,
              targetPort: $.backend.slave.statefulset.spec.template.spec.containers[1].ports[0].containerPort,
}]}}}}}


================================================
FILE: examples/wordpress/frontend.jsonnet
================================================
local kube = import "../../kube.libsonnet";
local be = import "backend.jsonnet";

local labels = {
  tier: "frontend",
};

{
  frontend: {
    pvc: kube.PersistentVolumeClaim("wordpress") {
      metadata+: {
        labels+: labels,
      },
      storage:: "10Gi",
    },

    configmap: kube.ConfigMap("wordpress") {
      metadata+: {
        labels+: labels,
      },
      data: {
        "admin_first_name": "Admin",
        "admin_last_name": "User",
        "blog_name": "Kubernetes blog!",
    }},

    secret: kube.Secret("wordpress") {
      metadata+: {
        labels+: labels,
      },
      data_+: {
        "user": "user",
        "password": "bitnami",
        "mail": "user@example.com",
    }},

    deployment: kube.Deployment("wordpress") {
      metadata+: {
        labels+: labels,
      },
      spec+: {
        template+: {
          spec+: {
            containers_+: {
              default: kube.Container("wordpress") {
                image: "bitnami/wordpress",
                ports_+: { http: { containerPort: 80 } },
                env_+: {
                  MARIADB_HOST: be.backend.master.service.metadata.name,
                  WORDPRESS_DATABASE_USER: kube.SecretKeyRef(be.backend.secret, "database_user"),
                  WORDPRESS_DATABASE_NAME: kube.SecretKeyRef(be.backend.secret, "database_name"),
                  WORDPRESS_DATABASE_PASSWORD: kube.SecretKeyRef(be.backend.secret, "database_password"),
                  WORDPRESS_USERNAME: kube.SecretKeyRef($.frontend.secret, "user"),
                  WORDPRESS_EMAIL: kube.SecretKeyRef($.frontend.secret, "mail"),
                  WORDPRESS_PASSWORD: kube.SecretKeyRef($.frontend.secret, "password"),
                  WORDPRESS_BLOG_NAME: kube.ConfigMapRef($.frontend.configmap, "blog_name"),
                  WORDPRESS_FIRST_NAME: kube.ConfigMapRef($.frontend.configmap, "admin_first_name"),
                  WORDPRESS_LAST_NAME: kube.ConfigMapRef($.frontend.configmap, "admin_last_name"),
                },
                livenessProbe: {
                  initialDelaySeconds: 120,
                  httpGet:  {
                    path: "/wp-login.php",
                    port: 80
                }},
                readinessProbe: self.livenessProbe {
                  initialDelaySeconds: 60,
                },
                volumeMounts_+: {
                  "wordpress-data": {
                    "mountPath": "/bitnami",
          }}}},
          volumes_+: {
            "wordpress-data": {
              "persistentVolumeClaim": {
                "claimName": "wordpress",
    }}}}}}},

    service: kube.Service("wordpress") {
      metadata+: {
        labels+: labels,
      },
      target_pod: $.frontend.deployment.spec.template,
}}}


================================================
FILE: examples/wordpress/wordpress.jsonnet
================================================
// Copyright (c) 2018 Bitnami
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

// ```
// kubecfg update wordpress.jsonnet
//
// kubecfg delete wordpress.jsonnet
// ```

local kube = import "../../kube.libsonnet";
local fe = import "frontend.jsonnet";
local be = import "backend.jsonnet";

local findObjs(top) = std.flattenArrays([
  if (std.objectHas(v, "apiVersion") && std.objectHas(v, "kind")) then [v] else findObjs(v)
  for v in kube.objectValues(top)
]);

kube.List() {
  items_+: {
    frontend: fe,
    backend: be,
  },
  items: findObjs(self.items_),
}


================================================
FILE: kube-platforms.libsonnet
================================================
// Extend kube.libsonnet for platform specific CRDs, drop-in usage as:
//
// local kube = import "kube-platforms.jsonnet";
// {
//    my_deploy: kube.Deployment(...) { ... }
//    my_gke_cert: kube.gke.ManagedCertificate(...) { ... }
// }
(import "kube.libsonnet") {
  gke:: {
    ManagedCertificate(name): $._Object("networking.gke.io/v1beta1", "ManagedCertificate", name) {
      spec: {
        domains: error "spec.domains array is required",
      },
      assert std.length(self.spec.domains) > 0 : "ManagedCertificate '%s' spec.domains array must not be empty" % self.metadata.name,
    },

    BackendConfig(name): $._Object("cloud.google.com/v1beta1", "BackendConfig", name) {
      spec: {},
    },
  },
}


================================================
FILE: kube.libsonnet
================================================
// 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).

{
  // In case you may want/need to skip assertions for speed reasons (rather big configmaps/etc),
  // load the library with e.g.
  //   local kube = (import "lib/kube.libsonnet") { _assert:: false };
  _assert:: true,

  // resource contructors will use kinds/versions/fields compatible at least with version:
  minKubeVersion: {
    major: 1,
    minor: 19,
    version: "%s.%s" % [self.major, self.minor],
  },

  // 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 (!$._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)])
  ),

  boolXor(x, y):: ((if x then 1 else 0) + (if y then 1 else 0) == 1),

  _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",
    container_index:: 0,
    port:: self.target_pod.spec.containers[service.container_index].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
    // This has been adapted for Ingress with apiVersion: networking.k8s.io/v1
    name_port:: {
      local this = self,
      default_port:: service.spec.ports[0],
      port_spec:: if std.objectHas(this.default_port, "name") then { name: this.default_port.name } else { number: this.default_port.port },

      service+: {
        name: service.metadata.name,
        port+: this.port_spec,
      },

      assert (!$._assert) || $.boolXor(
        std.objectHas(this.port_spec, "name"),
        std.objectHas(this.port_spec, "number")
      ) : "Service '%s' name_port: `name` and `number` are mutually exclusive for Ingress spec" % name,

    },

    spec: {
      selector: service.target_pod.metadata.labels,
      ports: [
        {
          port: service.port,
          name: service.target_pod.spec.containers[service.container_index].ports[0].name,
          targetPort: service.target_pod.spec.containers[service.container_index].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/v1", "StorageClass", name) {
    provisioner: error "provisioner required",
  },

  PersistentVolumeClaim(name): $._Object("v1", "PersistentVolumeClaim", name) {
    local pvc = self,

    storageClass:: null,
    storage:: error "storage required",

    spec: {
      resources: {
        requests: {
          storage: pvc.storage,
        },
      },
      accessModes: ["ReadWriteOnce"],
      [if pvc.storageClass != null then "storageClassName"]: pvc.storageClass,
    },
  },

  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 {
        // Let `null` value stay as such (vs string-ified)
        name: x,
        value: if map[x] == null then null else std.toString(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 (!$._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: {
      assert (!$._assert) || $.boolXor(
        std.objectHas(self, "minAvailable"),
        std.objectHas(self, "maxUnavailable")
      ) : "PDB '%s': exactly one of minAvailable/maxUnavailable required" % name,
      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: (
      assert (!$._assert) || std.length(self.containers_) > 0 : "Pod must have at least one container (via containers_ map)";
      [{ 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 (!$._assert) || std.length(self.containers) > 0 : "Pod must have at least one container (via containers array)",

    // 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 (!$._assert) || std.length(nonstrings) == 0 : "data contains non-string values: %s" % [nonstrings],
  },

  // subtype of EnvVarSource
  ConfigMapRef(configmap, key): {
    assert (!$._assert) || std.objectHas(configmap.data, key) : "ConfigMap '%s' doesn't have '%s' field in configmap.data" % [configmap.metadata.name, 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 (!$._assert) || std.objectHas(secret.data, key) : "Secret '%s' doesn't have '%s' field in secret.data" % [secret.metadata.name, 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/v1", "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,

      // NB: Regular k8s default is to keep all revisions
      revisionHistoryLimit: 10,
    },
  },

  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 (!$._assert) || self.maxReplicas >= self.minReplicas,
    },
  },

  StatefulSet(name): $._Object("apps/v1", "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 (!$._assert) || self.replicas >= 1,
    },
  },

  Job(name): $._Object("batch/v1", "Job", name) {
    local job = self,

    spec: $.JobSpec {
      template+: {
        metadata+: {
          labels: job.metadata.labels,
        },
      },
    },
  },

  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",
      },
    },
    completions: 1,
    parallelism: 1,
  },

  DaemonSet(name): $._Object("apps/v1", "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("networking.k8s.io/v1", "Ingress", name) {
    spec: {},

    local rel_paths = [
      p.path
      for r in self.spec.rules
      for p in r.http.paths
      if std.objectHas(p, "path") && !std.startsWith(p.path, "/")
    ],
    assert (!$._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/v1",
    kind: "CustomResourceDefinition",
    metadata+: {
      name: this.spec.names.plural + "." + this.spec.group,
    },
    spec: {
      scope: "Namespaced",
      group: group,
      versions_:: {
        // Create an opinionated default_spec for the version, easy to override by the user,
        // specially if they had several versions to derived from the same "skeleton".
        default_spec:: {
          served: true,
          storage: true,
          schema: {
            openAPIV3Schema: {
              type: "object",
              properties: {
                spec: {
                  type: "object",
                },
              },
            },
          },
        },
        [version]: self.default_spec,
      },
      versions: $.mapToNamedList(self.versions_),
      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/v1", "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/v1", "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: encryptedData can be imported into a SealedSecret as follows:
  // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.encryptedData > sealedsecret.json
  //   encryptedData: std.parseJson(importstr "sealedsecret.json")
  SealedSecret(name): $._Object("bitnami.com/v1alpha1", "SealedSecret", name) {
    spec: {
      encryptedData: {},
    },
    assert (!$._assert) || std.length(std.objectFields(self.spec.encryptedData)) != 0 : "SealedSecret '%s' has empty encryptedData field" % name,
  },

  // 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_:: {},
      podSelector: {},
    },
  },

  VerticalPodAutoscaler(name):: $._Object("autoscaling.k8s.io/v1beta2", "VerticalPodAutoscaler", name) {
    local vpa = self,

    target:: error "target required",

    spec+: {
      targetRef: $.CrossVersionObjectReference(vpa.target),

      updatePolicy: {
        updateMode: "Auto",
      },
    },
  },
  // Helper function to ease VPA creation as e.g.:
  // foo_vpa:: kube.createVPAFor($.foo_deploy)
  createVPAFor(target, mode="Auto"):: $.VerticalPodAutoscaler(target.metadata.name) {
    target:: target,

    metadata+: {
      namespace: target.metadata.namespace,
      labels+: target.metadata.labels,
    },
    spec+: {
      updatePolicy+: {
        updateMode: mode,
      },
    },
  },
}


================================================
FILE: tests/Dockerfile
================================================
FROM bitnami/minideb:buster
MAINTAINER sre@bitnami.com

ARG jsonnet_version=0.14.0
ARG kubectl_version=v1.17.0
ARG kubecfg_version=v0.14.0

RUN install_packages jq make curl ca-certificates
RUN adduser --home /home/user --disabled-password --gecos User user

RUN curl -sLo /tmp/jsonnet-v${jsonnet_version}.tar.gz https://github.com/google/jsonnet/releases/download/v${jsonnet_version}/jsonnet-bin-v${jsonnet_version}-linux.tar.gz
RUN tar -zxf /tmp/jsonnet-v${jsonnet_version}.tar.gz -C /tmp && mv /tmp/jsonnet /tmp/jsonnetfmt /usr/local/bin

RUN curl -sLo /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/${kubectl_version}/bin/linux/amd64/kubectl
RUN chmod +x /usr/local/bin/kubectl

RUN curl -sLo /usr/local/bin/kubecfg https://github.com/bitnami/kubecfg/releases/download/${kubecfg_version}/kubecfg-linux-amd64
RUN chmod +x /usr/local/bin/kubecfg

USER user
WORKDIR /home/user
CMD ["/bin/bash", "-l"]


================================================
FILE: tests/Makefile
================================================
# K3S_<KUBE_MAJOR_RELEASE> as a "mapping" from KUBE_MAJOR_RELEASE to k3s tag,
# from https://hub.docker.com/r/rancher/k3s/tags
# K3S_V1_13=v0.3.0 # no-more to support v1.18 previous Ingress apiVersion deprecation
K3S_V1_14=v0.8.0
K3S_V1_15=v0.9.0
K3S_V1_16=v1.0.1
# Since v1.17 k3s image tags follow Kubernetes releases numbering
K3S_V1_17=v1.17.17-k3s1
K3S_V1_18=v1.18.19-k3s1
K3S_V1_19=v1.19.11-k3s1
K3S_V1_20=v1.20.7-k3s1
K3S_V1_21=v1.21.1-k3s1
K3S_V1_22=v1.22.2-k3s1
#
# Since https://github.com/bitnami-labs/kube-libsonnet/issues/32 we only support
# kubernetes v1.14+ (Ingress apiVersion deprecated in v1.18, available since v1.14).
#
# Kubernetes releases we cover with e2e testing,
# we'll run `docker-compose` for each to below rancher/k3s versions (tags)
E2E_K3S_VERSIONS=$(K3S_V1_19) $(K3S_V1_20) $(K3S_V1_21) $(K3S_V1_22)

SHELL=/bin/bash
# Rather arbitrary Bitnami style choice
JSONNET_FMT=--indent 2 --string-style d --comment-style s --no-pad-arrays --pad-objects --pretty-field-names

LIB_JSONNET=$(wildcard ../*.libsonnet)
# jsonnet manifests for kube-validate target, exercising `kubecfg validate ...`
# against a live Kube API endpoint
ALL_K8S_VALIDATE_JSONNET=$(wildcard *-validate.pass.jsonnet)

# Filenames provide expected testing result (pass|fail)
PASS_JSONNET=$(sort $(wildcard test*pass.jsonnet))
FAIL_JSONNET=$(sort $(wildcard test*fail.jsonnet))
UNIT_JSONNET=$(sort $(wildcard unittest*.jsonnet))

ALL_JSONNET=$(sort $(wildcard *.jsonnet))

# Phony %.diff and golden/*.json targets
PHONY_GOLDEN=$(patsubst %.jsonnet,golden/%.json,$(PASS_JSONNET))
PHONY_DIFF=$(patsubst %.jsonnet,%.diff,$(PASS_JSONNET))

# Phony %.eval-pass and %.eval-fail targets
PHONY_EVAL_PASS=$(patsubst %.jsonnet,%.eval-pass,$(PASS_JSONNET))
PHONY_EVAL_FAIL=$(patsubst %.jsonnet,%.eval-fail,$(FAIL_JSONNET))

## NOTE: below values need to be in-sync with docker-compose.yaml
##       (not automated to avoid over-engineering the manifests)
DOCKER_E2E=e2e-test
TMP_RANCHER=./tmp-rancher
PROJECT=kubelibsonnet

# All tests, run from docker-compose built docker containers,
# to (also) avoid the need for local tools install, see ./Dockerfile
tests: $(patsubst %,e2e-tests-%,$(E2E_K3S_VERSIONS))
	@echo "SUCCESS: verified Kubernetes versions:"
	@cat $(TMP_RANCHER)/report.txt
	@rm -rf ./$(TMP_RANCHER)

# These target is dynamically driven from e2e-test-% above, run as
# e.g. `e2e-tests-v1.17.2-k3s1`
e2e-tests-%: req-docker req-docker-compose
	install -d $(TMP_RANCHER)/root/etc && touch $(TMP_RANCHER)/root/etc/k3s.yaml
	env USERID=$$(id -u) K3S_VERSION=$(*) docker-compose -p $(PROJECT) up -d
	rc=$$(timeout 60s docker wait $(DOCKER_E2E)) || rc=255 ;\
	   test $$rc -ne 0 && docker logs k3s-api;\
	   docker logs $(DOCKER_E2E); \
	   exit $$rc
	@# Peek e2e test output for Kubernetes versions tested
	docker logs $(DOCKER_E2E)| egrep '^Server.Version.+' | sort -u >> $(TMP_RANCHER)/report.txt
	docker-compose -p $(PROJECT) down
	rm -rf ./$(TMP_RANCHER)/root

# Tests safe to run without a live Kube API endpoint,
# but still requiring local `jsonnet` install
local-tests: unittests lint assertion-tests golden-diff

# NB: unittest jsonnet files are also covered by eval-pass and diff targets,
#     called out here for convenience
unittests: req-jsonnet
	jsonnet $(UNIT_JSONNET)

lint: req-jsonnetfmt
	@set -e; errs=0; \
        for f in $(ALL_JSONNET) $(LIB_JSONNET); do \
	  if ! jsonnetfmt --test $(JSONNET_FMT) -- $$f; then \
	    echo "FAILED lint: $$f" >&2; \
	    errs=$$(( $$errs + 1 )); \
	  fi; \
	done; \
	if [ $$errs -gt 0 ]; then \
	  echo "NOTE: if the 'lint' target fails, run:"; \
	  echo "      $(MAKE) fix-lint lint"; \
	  exit 1; \
	fi

assertion-tests: req-jsonnet $(PHONY_EVAL_PASS) $(PHONY_EVAL_FAIL)

golden-diff: diff-help $(PHONY_DIFF)

# Used to initialize docker'ized KubeAPI via k3s
kube-init: req-kubectl req-kubecfg
	kubectl version --short | grep k3s # void falsely initializing live clusters
	kubecfg update init-kube.jsonnet

kube-validate: req-kubectl req-kubecfg
	timeout 10 kubectl api-versions > /dev/null \
	|| { echo "WARNING: no usable runtime kube context, skipping."; exit 0 ;} \
	&& kubectl version --short && kubecfg version && kubecfg validate --ignore-unknown=false $(ALL_K8S_VALIDATE_JSONNET)

%.diff: %.jsonnet
	diff -u golden/$(*).json <(jsonnet $(<))

%.eval-pass: %.jsonnet
	@echo "INFO: must PASS: $(<)"
	@(jsonnet $(<) > /dev/null)
	@echo "OK[PASS]: $(<)"

%.eval-fail: %.jsonnet
	@echo "INFO: must FAIL: $(<)"
	@echo -n "| "; (jsonnet $(<) > /dev/null) 2>&1 | grep RUNTIME.ERROR
	@echo "OK[FAIL]: $(<)"


golden/%.json: %.jsonnet
	jsonnet $(<) > $(@)

diff-help:
	@echo "NOTE: if the 'golden-diff' target fails, review output and run:"
	@echo "      $(MAKE) gen-golden golden-diff"
	@echo

fix-lint: req-jsonnetfmt
	@set -e; \
	for f in $(ALL_JSONNET) $(LIB_JSONNET); do \
	  echo jsonnetfmt -i $(JSONNET_FMT) -- $$f; \
	  jsonnetfmt -i $(JSONNET_FMT) -- $$f; \
	done

req-%:
	@which $(*) >/dev/null && exit 0; echo "ERROR: '$(*)' is required in PATH"; exit 1

gen-golden: $(PHONY_GOLDEN)

.PHONY: unittests lint eval-pass eval-fail validate golden-diff %.eval-pass %.evail-fail %.diff golden/%.json diff-help fix-lint gen-golden


================================================
FILE: tests/docker-compose.yaml
================================================
version: "3"
services:
  kube-api:
    image: rancher/k3s:${K3S_VERSION}
    command: server --disable-agent
    container_name: k3s-api
    # Volume mapping to "capture" k3s admin credentials
    # (aka "admin.conf" KUBECONFIG)
    volumes:
      - ./tmp-rancher/root:/.kube
      - ./tmp-rancher/root:/.rancher
      - ./tmp-rancher/root/etc:/etc/rancher/k3s
    expose:
      - 6443
    user: "${USERID}"
    environment:
      - USER=nobody
      - HOME=/
    tmpfs:
      - /var/run
      - /run
      - /tmp
  e2e-test:
    build: .
    container_name: e2e-test
    links:
      - "kube-api:kube-api"
    depends_on:
      - kube-api
    volumes:
      - ./tmp-rancher/root:/tmp/rancher
      - ..:/work
    working_dir: /work
    environment:
      - HOME=/
    user: "${USERID}"
    command:
      - tests/k3s-e2e-test.sh


================================================
FILE: tests/golden/init-kube.json
================================================
{
   "vpa_crd": {
      "apiVersion": "apiextensions.k8s.io/v1beta1",
      "kind": "CustomResourceDefinition",
      "metadata": {
         "name": "verticalpodautoscalers.autoscaling.k8s.io"
      },
      "spec": {
         "group": "autoscaling.k8s.io",
         "names": {
            "kind": "VerticalPodAutoscaler",
            "listKind": "VerticalPodAutoscalerList",
            "plural": "verticalpodautoscalers",
            "singular": "verticalpodautoscaler"
         },
         "scope": "Namespaced",
         "version": "v1beta1",
         "versions": [
            {
               "name": "v1beta1",
               "served": true,
               "storage": false
            },
            {
               "name": "v1beta2",
               "served": true,
               "storage": true
            }
         ]
      }
   }
}


================================================
FILE: tests/golden/test-Ingress-2ndport.pass.json
================================================
{
   "deploy": {
      "apiVersion": "apps/v1",
      "kind": "Deployment",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-deploy"
         },
         "name": "test-Ingress-pass-deploy"
      },
      "spec": {
         "minReadySeconds": 30,
         "replicas": 1,
         "revisionHistoryLimit": 10,
         "selector": {
            "matchLabels": {
               "name": "test-Ingress-pass-deploy"
            }
         },
         "strategy": {
            "rollingUpdate": {
               "maxSurge": "25%",
               "maxUnavailable": "25%"
            },
            "type": "RollingUpdate"
         },
         "template": {
            "metadata": {
               "annotations": { },
               "labels": {
                  "name": "test-Ingress-pass-deploy"
               }
            },
            "spec": {
               "containers": [
                  {
                     "args": [ ],
                     "env": [ ],
                     "image": "nginx:1.12",
                     "imagePullPolicy": "IfNotPresent",
                     "name": "test-Ingress-pass",
                     "ports": [
                        {
                           "containerPort": 80,
                           "name": "http"
                        },
                        {
                           "containerPort": 9099,
                           "name": "metrics"
                        }
                     ],
                     "stdin": false,
                     "tty": false,
                     "volumeMounts": [ ]
                  }
               ],
               "imagePullSecrets": [ ],
               "initContainers": [ ],
               "terminationGracePeriodSeconds": 30,
               "volumes": [ ]
            }
         }
      }
   },
   "ingress": {
      "apiVersion": "networking.k8s.io/v1",
      "kind": "Ingress",
      "metadata": {
         "annotations": {
            "cert-manager.io/cluster-issuer": "letsencrypt-prod-dns"
         },
         "labels": {
            "name": "test-Ingress-pass-ingress"
         },
         "name": "test-Ingress-pass-ingress"
      },
      "spec": {
         "rules": [
            {
               "host": "foo.g.dev.bitnami.net",
               "http": {
                  "paths": [
                     {
                        "backend": {
                           "service": {
                              "name": "test-Ingress-pass-svc",
                              "port": {
                                 "name": "metrics"
                              }
                           }
                        },
                        "path": "/",
                        "pathType": "ImplementationSpecific"
                     }
                  ]
               }
            }
         ],
         "tls": [
            {
               "hosts": [
                  "foo.g.dev.bitnami.net"
               ],
               "secretName": "test-Ingress-pass-ingress-cert"
            }
         ]
      }
   },
   "pod": {
      "apiVersion": "v1",
      "kind": "Pod",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-pod"
         },
         "name": "test-Ingress-pass-pod"
      },
      "spec": {
         "containers": [
            {
               "args": [ ],
               "env": [ ],
               "image": "nginx:1.12",
               "imagePullPolicy": "IfNotPresent",
               "name": "test-Ingress-pass",
               "ports": [
                  {
                     "containerPort": 80,
                     "name": "http"
                  },
                  {
                     "containerPort": 9099,
                     "name": "metrics"
                  }
               ],
               "stdin": false,
               "tty": false,
               "volumeMounts": [ ]
            }
         ],
         "imagePullSecrets": [ ],
         "initContainers": [ ],
         "terminationGracePeriodSeconds": 30,
         "volumes": [ ]
      }
   },
   "service": {
      "apiVersion": "v1",
      "kind": "Service",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-svc"
         },
         "name": "test-Ingress-pass-svc"
      },
      "spec": {
         "ports": [
            {
               "name": "http",
               "port": 80,
               "targetPort": 80
            }
         ],
         "selector": {
            "name": "test-Ingress-pass-deploy"
         },
         "type": "ClusterIP"
      }
   }
}


================================================
FILE: tests/golden/test-Ingress-port_num_only.pass.json
================================================
{
   "deploy": {
      "apiVersion": "apps/v1",
      "kind": "Deployment",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-deploy"
         },
         "name": "test-Ingress-pass-deploy"
      },
      "spec": {
         "minReadySeconds": 30,
         "replicas": 1,
         "revisionHistoryLimit": 10,
         "selector": {
            "matchLabels": {
               "name": "test-Ingress-pass-deploy"
            }
         },
         "strategy": {
            "rollingUpdate": {
               "maxSurge": "25%",
               "maxUnavailable": "25%"
            },
            "type": "RollingUpdate"
         },
         "template": {
            "metadata": {
               "annotations": { },
               "labels": {
                  "name": "test-Ingress-pass-deploy"
               }
            },
            "spec": {
               "containers": [
                  {
                     "args": [ ],
                     "env": [ ],
                     "image": "nginx:1.12",
                     "imagePullPolicy": "IfNotPresent",
                     "name": "test-Ingress-pass",
                     "ports": [
                        {
                           "containerPort": 80,
                           "name": "http"
                        },
                        {
                           "containerPort": 9099,
                           "name": "metrics"
                        }
                     ],
                     "stdin": false,
                     "tty": false,
                     "volumeMounts": [ ]
                  }
               ],
               "imagePullSecrets": [ ],
               "initContainers": [ ],
               "terminationGracePeriodSeconds": 30,
               "volumes": [ ]
            }
         }
      }
   },
   "ingress": {
      "apiVersion": "networking.k8s.io/v1",
      "kind": "Ingress",
      "metadata": {
         "annotations": {
            "cert-manager.io/cluster-issuer": "letsencrypt-prod-dns"
         },
         "labels": {
            "name": "test-Ingress-pass-ingress"
         },
         "name": "test-Ingress-pass-ingress"
      },
      "spec": {
         "rules": [
            {
               "host": "foo.g.dev.bitnami.net",
               "http": {
                  "paths": [
                     {
                        "backend": {
                           "service": {
                              "name": "test-Ingress-pass-svc",
                              "port": {
                                 "number": 4242
                              }
                           }
                        },
                        "path": "/",
                        "pathType": "ImplementationSpecific"
                     }
                  ]
               }
            }
         ],
         "tls": [
            {
               "hosts": [
                  "foo.g.dev.bitnami.net"
               ],
               "secretName": "test-Ingress-pass-ingress-cert"
            }
         ]
      }
   },
   "pod": {
      "apiVersion": "v1",
      "kind": "Pod",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-pod"
         },
         "name": "test-Ingress-pass-pod"
      },
      "spec": {
         "containers": [
            {
               "args": [ ],
               "env": [ ],
               "image": "nginx:1.12",
               "imagePullPolicy": "IfNotPresent",
               "name": "test-Ingress-pass",
               "ports": [
                  {
                     "containerPort": 80,
                     "name": "http"
                  },
                  {
                     "containerPort": 9099,
                     "name": "metrics"
                  }
               ],
               "stdin": false,
               "tty": false,
               "volumeMounts": [ ]
            }
         ],
         "imagePullSecrets": [ ],
         "initContainers": [ ],
         "terminationGracePeriodSeconds": 30,
         "volumes": [ ]
      }
   },
   "service": {
      "apiVersion": "v1",
      "kind": "Service",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Ingress-pass-svc"
         },
         "name": "test-Ingress-pass-svc"
      },
      "spec": {
         "ports": [
            {
               "name": "http",
               "port": 80,
               "targetPort": 80
            }
         ],
         "selector": {
            "name": "test-Ingress-pass-deploy"
         },
         "type": "ClusterIP"
      }
   }
}


================================================
FILE: tests/golden/test-SealedSecret.pass.json
================================================
{
   "apiVersion": "v1",
   "items": [
      {
         "apiVersion": "bitnami.com/v1alpha1",
         "kind": "SealedSecret",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo"
            },
            "name": "foo"
         },
         "spec": {
            "encryptedData": {
               "another_key": "dGVzdAo=",
               "some_key": "dGVzdAo="
            }
         }
      }
   ],
   "kind": "List"
}


================================================
FILE: tests/golden/test-Service-container_index.pass.json
================================================
{
   "deploy": {
      "apiVersion": "apps/v1",
      "kind": "Deployment",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Service-pass-deploy"
         },
         "name": "test-Service-pass-deploy"
      },
      "spec": {
         "minReadySeconds": 30,
         "replicas": 1,
         "revisionHistoryLimit": 10,
         "selector": {
            "matchLabels": {
               "name": "test-Service-pass-deploy"
            }
         },
         "strategy": {
            "rollingUpdate": {
               "maxSurge": "25%",
               "maxUnavailable": "25%"
            },
            "type": "RollingUpdate"
         },
         "template": {
            "metadata": {
               "annotations": { },
               "labels": {
                  "name": "test-Service-pass-deploy"
               }
            },
            "spec": {
               "containers": [
                  {
                     "args": [ ],
                     "env": [ ],
                     "image": "nginx:1.12",
                     "imagePullPolicy": "IfNotPresent",
                     "name": "test-Service-pass-default",
                     "ports": [
                        {
                           "containerPort": 80,
                           "name": "http"
                        },
                        {
                           "containerPort": 9099,
                           "name": "metrics"
                        }
                     ],
                     "stdin": false,
                     "tty": false,
                     "volumeMounts": [ ]
                  },
                  {
                     "args": [ ],
                     "env": [ ],
                     "image": "nginx:1.12",
                     "imagePullPolicy": "IfNotPresent",
                     "name": "test-Service-pass-sidecar",
                     "ports": [
                        {
                           "containerPort": 80,
                           "name": "http-sidecar"
                        },
                        {
                           "containerPort": 9099,
                           "name": "metrics"
                        }
                     ],
                     "stdin": false,
                     "tty": false,
                     "volumeMounts": [ ]
                  }
               ],
               "imagePullSecrets": [ ],
               "initContainers": [ ],
               "terminationGracePeriodSeconds": 30,
               "volumes": [ ]
            }
         }
      }
   },
   "pod": {
      "apiVersion": "v1",
      "kind": "Pod",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Service-pass-pod"
         },
         "name": "test-Service-pass-pod"
      },
      "spec": {
         "containers": [
            {
               "args": [ ],
               "env": [ ],
               "image": "nginx:1.12",
               "imagePullPolicy": "IfNotPresent",
               "name": "test-Service-pass-default",
               "ports": [
                  {
                     "containerPort": 80,
                     "name": "http"
                  },
                  {
                     "containerPort": 9099,
                     "name": "metrics"
                  }
               ],
               "stdin": false,
               "tty": false,
               "volumeMounts": [ ]
            },
            {
               "args": [ ],
               "env": [ ],
               "image": "nginx:1.12",
               "imagePullPolicy": "IfNotPresent",
               "name": "test-Service-pass-sidecar",
               "ports": [
                  {
                     "containerPort": 80,
                     "name": "http-sidecar"
                  },
                  {
                     "containerPort": 9099,
                     "name": "metrics"
                  }
               ],
               "stdin": false,
               "tty": false,
               "volumeMounts": [ ]
            }
         ],
         "imagePullSecrets": [ ],
         "initContainers": [ ],
         "terminationGracePeriodSeconds": 30,
         "volumes": [ ]
      }
   },
   "service": {
      "apiVersion": "v1",
      "kind": "Service",
      "metadata": {
         "annotations": { },
         "labels": {
            "name": "test-Service-pass-svc"
         },
         "name": "test-Service-pass-svc"
      },
      "spec": {
         "ports": [
            {
               "name": "http-sidecar",
               "port": 80,
               "targetPort": 80
            }
         ],
         "selector": {
            "name": "test-Service-pass-deploy"
         },
         "type": "ClusterIP"
      }
   }
}


================================================
FILE: tests/golden/test-gke-ManagedCertificate.pass.json
================================================
{
   "apiVersion": "v1",
   "items": [
      {
         "apiVersion": "networking.gke.io/v1beta1",
         "kind": "ManagedCertificate",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo"
            },
            "name": "foo"
         },
         "spec": {
            "domains": [
               "foo.example.com"
            ]
         }
      }
   ],
   "kind": "List"
}


================================================
FILE: tests/golden/test-simple-validate.pass.json
================================================
{
   "apiVersion": "v1",
   "items": [
      {
         "apiVersion": "v1",
         "data": {
            "foo_key": "bar_val"
         },
         "kind": "ConfigMap",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-config"
            },
            "name": "foo-config",
            "namespace": "foons"
         }
      },
      {
         "apiVersion": "batch/v1beta1",
         "kind": "CronJob",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-cronjob"
            },
            "name": "foo-cronjob",
            "namespace": "foons"
         },
         "spec": {
            "concurrencyPolicy": "Forbid",
            "failedJobsHistoryLimit": 20,
            "jobTemplate": {
               "spec": {
                  "completions": 1,
                  "parallelism": 1,
                  "template": {
                     "metadata": {
                        "labels": {
                           "name": "foo-cronjob"
                        }
                     },
                     "spec": {
                        "containers": [
                           {
                              "args": [ ],
                              "env": [ ],
                              "image": "busybox",
                              "imagePullPolicy": "IfNotPresent",
                              "name": "foo",
                              "ports": [ ],
                              "stdin": false,
                              "tty": false,
                              "volumeMounts": [ ]
                           }
                        ],
                        "imagePullSecrets": [ ],
                        "initContainers": [ ],
                        "restartPolicy": "OnFailure",
                        "terminationGracePeriodSeconds": 30,
                        "volumes": [ ]
                     }
                  }
               }
            },
            "schedule": "0 * * * *",
            "successfulJobsHistoryLimit": 10
         }
      },
      {
         "apiVersion": "apps/v1",
         "kind": "Deployment",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-deploy"
            },
            "name": "foo-deploy",
            "namespace": "foons"
         },
         "spec": {
            "minReadySeconds": 30,
            "replicas": 1,
            "revisionHistoryLimit": 10,
            "selector": {
               "matchLabels": {
                  "name": "foo-deploy"
               }
            },
            "strategy": {
               "rollingUpdate": {
                  "maxSurge": "25%",
                  "maxUnavailable": "25%"
               },
               "type": "RollingUpdate"
            },
            "template": {
               "metadata": {
                  "annotations": { },
                  "labels": {
                     "name": "foo-deploy"
                  }
               },
               "spec": {
                  "affinity": {
                     "podAntiAffinity": {
                        "preferredDuringSchedulingIgnoredDuringExecution": [
                           {
                              "podAffinityTerm": {
                                 "labelSelector": {
                                    "matchLabels": {
                                       "name": "foo-deploy"
                                    }
                                 },
                                 "topologyKey": "kubernetes.io/hostname"
                              },
                              "weight": 70
                           },
                           {
                              "podAffinityTerm": {
                                 "labelSelector": {
                                    "matchLabels": {
                                       "name": "foo-deploy"
                                    }
                                 },
                                 "topologyKey": "failure-domain.beta.kubernetes.io/zone"
                              },
                              "weight": 70
                           },
                           {
                              "podAffinityTerm": {
                                 "labelSelector": {
                                    "matchLabels": {
                                       "name": "foo-deploy"
                                    }
                                 },
                                 "topologyKey": "failure-domain.beta.kubernetes.io/region"
                              },
                              "weight": 70
                           }
                        ]
                     }
                  },
                  "containers": [
                     {
                        "args": [ ],
                        "env": [
                           {
                              "name": "my_secret",
                              "valueFrom": {
                                 "secretKeyRef": {
                                    "key": "sec_key",
                                    "name": "foo-secret"
                                 }
                              }
                           },
                           {
                              "name": "other_key",
                              "value": null
                           }
                        ],
                        "image": "nginx:1.12",
                        "imagePullPolicy": "IfNotPresent",
                        "name": "foo",
                        "ports": [
                           {
                              "containerPort": 80,
                              "name": "http"
                           },
                           {
                              "containerPort": 888,
                              "name": "udp-port",
                              "protocol": "UDP"
                           }
                        ],
                        "stdin": false,
                        "tty": false,
                        "volumeMounts": [
                           {
                              "mountPath": "/config",
                              "name": "config-vol"
                           }
                        ]
                     }
                  ],
                  "imagePullSecrets": [ ],
                  "initContainers": [ ],
                  "serviceAccountName": "foo-sa",
                  "terminationGracePeriodSeconds": 30,
                  "volumes": [
                     {
                        "configMap": {
                           "name": "foo-config"
                        },
                        "name": "config-vol"
                     }
                  ]
               }
            }
         }
      },
      {
         "apiVersion": "policy/v1beta1",
         "kind": "PodDisruptionBudget",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-deploy-pdb"
            },
            "name": "foo-deploy-pdb"
         },
         "spec": {
            "minAvailable": 1,
            "selector": {
               "matchLabels": {
                  "name": "foo-deploy"
               }
            }
         }
      },
      {
         "apiVersion": "autoscaling.k8s.io/v1beta2",
         "kind": "VerticalPodAutoscaler",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-deploy"
            },
            "name": "foo-deploy",
            "namespace": "foons"
         },
         "spec": {
            "targetRef": {
               "apiVersion": "apps/v1",
               "kind": "Deployment",
               "name": "foo-deploy"
            },
            "updatePolicy": {
               "updateMode": "Auto"
            }
         }
      },
      {
         "apiVersion": "apps/v1",
         "kind": "DaemonSet",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-ds"
            },
            "name": "foo-ds",
            "namespace": "foons"
         },
         "spec": {
            "selector": {
               "matchLabels": {
                  "name": "foo-ds"
               }
            },
            "template": {
               "metadata": {
                  "annotations": { },
                  "labels": {
                     "name": "foo-ds"
                  }
               },
               "spec": {
                  "containers": [
                     {
                        "args": [ ],
                        "env": [
                           {
                              "name": "my_secret",
                              "valueFrom": {
                                 "secretKeyRef": {
                                    "key": "sec_key",
                                    "name": "foo-secret"
                                 }
                              }
                           },
                           {
                              "name": "other_key",
                              "value": null
                           }
                        ],
                        "image": "nginx:1.12",
                        "imagePullPolicy": "IfNotPresent",
                        "name": "foo",
                        "ports": [
                           {
                              "containerPort": 80,
                              "name": "http"
                           },
                           {
                              "containerPort": 888,
                              "name": "udp-port",
                              "protocol": "UDP"
                           }
                        ],
                        "stdin": false,
                        "tty": false,
                        "volumeMounts": [
                           {
                              "mountPath": "/config",
                              "name": "config-vol"
                           }
                        ]
                     }
                  ],
                  "imagePullSecrets": [ ],
                  "initContainers": [ ],
                  "terminationGracePeriodSeconds": 30,
                  "volumes": [
                     {
                        "configMap": {
                           "name": "foo-config"
                        },
                        "name": "config-vol"
                     }
                  ]
               }
            },
            "updateStrategy": {
               "rollingUpdate": {
                  "maxUnavailable": 1
               },
               "type": "RollingUpdate"
            }
         }
      },
      {
         "apiVersion": "networking.k8s.io/v1",
         "kind": "Ingress",
         "metadata": {
            "annotations": {
               "cert-manager.io/cluster-issuer": "letsencrypt-prod-dns"
            },
            "labels": {
               "name": "foo-ingress"
            },
            "name": "foo-ingress",
            "namespace": "foons"
         },
         "spec": {
            "rules": [
               {
                  "host": "foo.g.dev.bitnami.net",
                  "http": {
                     "paths": [
                        {
                           "backend": {
                              "service": {
                                 "name": "foo-svc",
                                 "port": {
                                    "name": "http"
                                 }
                              }
                           },
                           "path": "/",
                           "pathType": "ImplementationSpecific"
                        }
                     ]
                  }
               }
            ],
            "tls": [
               {
                  "hosts": [
                     "foo.g.dev.bitnami.net"
                  ],
                  "secretName": "foo-ingress-cert"
               }
            ]
         }
      },
      {
         "apiVersion": "batch/v1",
         "kind": "Job",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-job"
            },
            "name": "foo-job",
            "namespace": "foons"
         },
         "spec": {
            "completions": 1,
            "parallelism": 1,
            "template": {
               "metadata": {
                  "labels": {
                     "name": "foo-job"
                  }
               },
               "spec": {
                  "containers": [
                     {
                        "args": [ ],
                        "env": [ ],
                        "image": "busybox",
                        "imagePullPolicy": "IfNotPresent",
                        "name": "foo",
                        "ports": [ ],
                        "stdin": false,
                        "tty": false,
                        "volumeMounts": [ ]
                     }
                  ],
                  "imagePullSecrets": [ ],
                  "initContainers": [ ],
                  "restartPolicy": "OnFailure",
                  "terminationGracePeriodSeconds": 30,
                  "volumes": [ ]
               }
            }
         }
      },
      {
         "apiVersion": "v1",
         "kind": "Namespace",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foons"
            },
            "name": "foons"
         }
      },
      {
         "apiVersion": "networking.k8s.io/v1",
         "kind": "NetworkPolicy",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-nsp-pods"
            },
            "name": "foo-nsp-pods",
            "namespace": "foons"
         },
         "spec": {
            "egress": [
               {
                  "ports": [
                     {
                        "port": 53,
                        "protocol": "UDP"
                     }
                  ],
                  "to": [
                     {
                        "namespaceSelector": {
                           "matchLabels": {
                              "name": "kube-system"
                           }
                        }
                     }
                  ]
               },
               {
                  "ports": [
                     {
                        "port": 80,
                        "protocol": "TCP"
                     },
                     {
                        "port": 888,
                        "protocol": "UDP"
                     }
                  ],
                  "to": [
                     {
                        "podSelector": {
                           "matchLabels": {
                              "name": "foo-sts"
                           }
                        }
                     }
                  ]
               }
            ],
            "ingress": [
               {
                  "from": [
                     {
                        "podSelector": {
                           "matchLabels": {
                              "name": "foo-job"
                           }
                        }
                     },
                     {
                        "podSelector": {
                           "matchLabels": {
                              "name": "foo-cronjob"
                           }
                        }
                     },
                     {
                        "namespaceSelector": {
                           "matchLabels": {
                              "name": "nginx-ingress"
                           }
                        }
                     }
                  ],
                  "ports": [
                     {
                        "port": 80,
                        "protocol": "TCP"
                     },
                     {
                        "port": 888,
                        "protocol": "UDP"
                     }
                  ]
               }
            ],
            "podSelector": {
               "matchLabels": {
                  "name": "foo-deploy"
               }
            },
            "policyTypes": [
               "Ingress",
               "Egress"
            ]
         }
      },
      {
         "apiVersion": "networking.k8s.io/v1",
         "kind": "Ingress",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-pathless-ingress"
            },
            "name": "foo-pathless-ingress",
            "namespace": "foons"
         },
         "spec": {
            "rules": [
               {
                  "host": "a.example.com",
                  "http": {
                     "paths": [
                        {
                           "backend": {
                              "service": {
                                 "name": "service-a",
                                 "port": {
                                    "name": "web"
                                 }
                              }
                           },
                           "pathType": "ImplementationSpecific"
                        }
                     ]
                  }
               },
               {
                  "host": "b.example.com",
                  "http": {
                     "paths": [
                        {
                           "backend": {
                              "service": {
                                 "name": "service-2",
                                 "port": {
                                    "name": "web"
                                 }
                              }
                           },
                           "pathType": "ImplementationSpecific"
                        }
                     ]
                  }
               }
            ]
         }
      },
      {
         "apiVersion": "v1",
         "kind": "Pod",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-pod"
            },
            "name": "foo-pod",
            "namespace": "foons"
         },
         "spec": {
            "containers": [
               {
                  "args": [ ],
                  "env": [
                     {
                        "name": "my_secret",
                        "valueFrom": {
                           "secretKeyRef": {
                              "key": "sec_key",
                              "name": "foo-secret"
                           }
                        }
                     },
                     {
                        "name": "other_key",
                        "value": null
                     }
                  ],
                  "image": "nginx:1.12",
                  "imagePullPolicy": "IfNotPresent",
                  "name": "foo",
                  "ports": [
                     {
                        "containerPort": 80,
                        "name": "http"
                     },
                     {
                        "containerPort": 888,
                        "name": "udp-port",
                        "protocol": "UDP"
                     }
                  ],
                  "stdin": false,
                  "tty": false,
                  "volumeMounts": [
                     {
                        "mountPath": "/config",
                        "name": "config-vol"
                     }
                  ]
               }
            ],
            "imagePullSecrets": [ ],
            "initContainers": [ ],
            "terminationGracePeriodSeconds": 30,
            "volumes": [
               {
                  "configMap": {
                     "name": "foo-config"
                  },
                  "name": "config-vol"
               }
            ]
         }
      },
      {
         "apiVersion": "rbac.authorization.k8s.io/v1",
         "kind": "Role",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-role"
            },
            "name": "foo-role",
            "namespace": "foons"
         },
         "rules": [
            {
               "apiGroups": [
                  ""
               ],
               "resources": [
                  "pods",
                  "secrets",
                  "configmaps",
                  "persistentvolumeclaims"
               ],
               "verbs": [
                  "get"
               ]
            },
            {
               "apiGroups": [
                  ""
               ],
               "resources": [
                  "pods"
               ],
               "verbs": [
                  "patch"
               ]
            }
         ]
      },
      {
         "apiVersion": "rbac.authorization.k8s.io/v1",
         "kind": "RoleBinding",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-rolebinding"
            },
            "name": "foo-rolebinding",
            "namespace": "foons"
         },
         "roleRef": {
            "apiGroup": "rbac.authorization.k8s.io",
            "kind": "Role",
            "name": "foo-role"
         },
         "subjects": [
            {
               "kind": "ServiceAccount",
               "name": "foo-sa",
               "namespace": "foons"
            }
         ]
      },
      {
         "apiVersion": "v1",
         "kind": "ServiceAccount",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-sa"
            },
            "name": "foo-sa",
            "namespace": "foons"
         }
      },
      {
         "apiVersion": "v1",
         "data": {
            "sec_key": "c2VjcmV0Cg=="
         },
         "kind": "Secret",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-secret"
            },
            "name": "foo-secret",
            "namespace": "foons"
         },
         "type": "Opaque"
      },
      {
         "apiVersion": "v1",
         "kind": "Service",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-svc"
            },
            "name": "foo-svc",
            "namespace": "foons"
         },
         "spec": {
            "ports": [
               {
                  "name": "http",
                  "port": 80,
                  "targetPort": 80
               }
            ],
            "selector": {
               "name": "foo-deploy"
            },
            "type": "ClusterIP"
         }
      },
      {
         "apiVersion": "apps/v1",
         "kind": "StatefulSet",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-sts"
            },
            "name": "foo-sts",
            "namespace": "foons"
         },
         "spec": {
            "replicas": 1,
            "selector": {
               "matchLabels": {
                  "name": "foo-sts"
               }
            },
            "serviceName": "foo-sts",
            "template": {
               "metadata": {
                  "annotations": { },
                  "labels": {
                     "name": "foo-sts"
                  }
               },
               "spec": {
                  "containers": [
                     {
                        "args": [ ],
                        "env": [
                           {
                              "name": "my_secret",
                              "valueFrom": {
                                 "secretKeyRef": {
                                    "key": "sec_key",
                                    "name": "foo-secret"
                                 }
                              }
                           },
                           {
                              "name": "other_key",
                              "value": null
                           }
                        ],
                        "image": "nginx:1.12",
                        "imagePullPolicy": "IfNotPresent",
                        "name": "foo",
                        "ports": [
                           {
                              "containerPort": 80,
                              "name": "http"
                           },
                           {
                              "containerPort": 888,
                              "name": "udp-port",
                              "protocol": "UDP"
                           }
                        ],
                        "stdin": false,
                        "tty": false,
                        "volumeMounts": [
                           {
                              "mountPath": "/config",
                              "name": "config-vol"
                           },
                           {
                              "mountPath": "/foo/data",
                              "name": "datadir"
                           }
                        ]
                     }
                  ],
                  "imagePullSecrets": [ ],
                  "initContainers": [ ],
                  "serviceAccountName": "foo-sa",
                  "terminationGracePeriodSeconds": 30,
                  "volumes": [
                     {
                        "configMap": {
                           "name": "foo-config"
                        },
                        "name": "config-vol"
                     }
                  ]
               }
            },
            "updateStrategy": {
               "rollingUpdate": {
                  "partition": 0
               },
               "type": "RollingUpdate"
            },
            "volumeClaimTemplates": [
               {
                  "metadata": {
                     "labels": {
                        "name": "datadir"
                     },
                     "name": "datadir",
                     "namespace": "foons"
                  },
                  "spec": {
                     "accessModes": [
                        "ReadWriteOnce"
                     ],
                     "resources": {
                        "requests": {
                           "storage": "10Gi"
                        }
                     }
                  }
               }
            ]
         }
      },
      {
         "apiVersion": "cert-manager.io/v1alpha2",
         "kind": "Certificate",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-cert"
            },
            "name": "foo-cert",
            "namespace": "foons"
         },
         "spec": {
            "commonName": "foo-cert",
            "dnsNames": [
               "foo-cert",
               "foo-cert.foons",
               "foo-cert.foons.svc"
            ],
            "issuerRef": {
               "kind": "ClusterIssuer",
               "name": "in-cluster-issuer"
            },
            "secretName": "foo-cert",
            "usages": [
               "digital signature",
               "key encipherment"
            ]
         }
      },
      {
         "apiVersion": "autoscaling.k8s.io/v1beta2",
         "kind": "VerticalPodAutoscaler",
         "metadata": {
            "annotations": { },
            "labels": {
               "name": "foo-vpa"
            },
            "name": "foo-vpa"
         },
         "spec": {
            "targetRef": {
               "apiVersion": "apps/v1",
               "kind": "Deployment",
               "name": "foo-deploy"
            },
            "updatePolicy": {
               "updateMode": "Auto"
            }
         }
      }
   ],
   "kind": "List"
}


================================================
FILE: tests/golden/unittests.pass.json
================================================
true


================================================
FILE: tests/init-kube.jsonnet
================================================
local kube = import "../kube.libsonnet";

local crds = {
  // A simplified VPA CRD from https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler
  vpa_crd: kube.CustomResourceDefinition("autoscaling.k8s.io", "v1beta1", "VerticalPodAutoscaler") {
    metadata+: {
      annotations: {
        "api-approved.kubernetes.io": "https://github.com/kubernetes/kubernetes/pull/78458",
      },
    },
    spec+: {
      versions_+: {
        v1beta2: self.default_spec { storage: false },
      },
    },
  },
  // Simplified cert-manager CRD from https://github.com/jetstack/cert-manager/blob/master/deploy/crds/crd-certificates.yaml,
  // enough to test bitnami.CertManager object(s)
  cm_certificate_crd: kube.CustomResourceDefinition("cert-manager.io", "v1alpha2", "Certificate"),
};

crds


================================================
FILE: tests/k3s-e2e-test.sh
================================================
#!/bin/sh
set -eu
echo "INFO: Starting tests: unit, lint ..."
(set -x
  make -C tests local-tests
)
export KUBECONFIG=/tmp/kubeconfig
echo "INFO: Waiting for kube-api to be available ..."
# Busy loop waiting for:
# - rancher/k3s to have written the k3s.yaml with admin creds
# - kube API to be ready
(set +e
until kubectl get nodes; do
  sleep 1
  # Found that k3s releases create k3s.yaml under diff paths,
  # redirecting stderr just to avoid red-herrings errors
  sed -e s/localhost/kube-api/ -e s/127.0.0.1/kube-api/ \
    /tmp/rancher/k3s.yaml /tmp/rancher/etc/k3s.yaml \
    > ${KUBECONFIG:?} 2>/dev/null
done
)
echo "INFO: initializing kube cluster: ..."
(set -x
  make -C tests kube-init
)
echo "INFO: Starting tests: test-kube ..."
(set -x
  make -C tests kube-validate
)


================================================
FILE: tests/test-Ingress-2ndport.pass.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";

local stack = {
  name:: "test-Ingress-pass",
  pod: kube.Pod($.name + "-pod") {
    spec+: {
      containers_+: {
        foo_cont: kube.Container($.name) {
          image: "nginx:1.12",
          ports_+: {
            http: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
      },
    },
  },
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    spec+: {
      template+: {
        spec+: $.pod.spec {
        },
      },
    },
  },
  service: kube.Service($.name + "-svc") {
    local this = self,
    target_pod: $.deploy.spec.template,
    name_port+:: {
      // Override default_port to 2nd port instead of 1st (default),
      default_port:: this.target_pod.spec.containers[0].ports[1],
    },
  },
  ingress: bitnami.Ingress($.name + "-ingress") {
    host: "foo.g.dev.bitnami.net",
    target_svc: $.service,
  },
};

stack {
  // Assert we got 2nd port dubbed "metrics" for Ingress
  assert (stack.ingress.spec.rules[0].http.paths[0].backend.service.port == { name: "metrics" }),
}


================================================
FILE: tests/test-Ingress-name_port.fail.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";

local stack = {
  name:: "test-Ingress-fail",
  pod: kube.Pod($.name + "-pod") {
    spec+: {
      containers_+: {
        foo_cont: kube.Container($.name) {
          image: "nginx:1.12",
          ports_+: {
            http: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
      },
    },
  },
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    spec+: {
      template+: {
        spec+: $.pod.spec {
        },
      },
    },
  },
  service: kube.Service($.name + "-svc") {
    local this = self,
    target_pod: $.deploy.spec.template,
    name_port+:: {
      // Force failing from having `name` _and_ `number` rendered for the
      // ingress spec (via name_port, see kube.libsonnet)
      port_spec+:: { number: 4242 },
    },
  },
  ingress: bitnami.Ingress($.name + "-ingress") {
    host: "foo.g.dev.bitnami.net",
    target_svc: $.service,
  },
};

stack


================================================
FILE: tests/test-Ingress-port_num_only.pass.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";

local stack = {
  name:: "test-Ingress-pass",
  pod: kube.Pod($.name + "-pod") {
    spec+: {
      containers_+: {
        foo_cont: kube.Container($.name) {
          image: "nginx:1.12",
          ports_+: {
            http: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
      },
    },
  },
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    spec+: {
      template+: {
        spec+: $.pod.spec {
        },
      },
    },
  },
  service: kube.Service($.name + "-svc") {
    local this = self,
    target_pod: $.deploy.spec.template,
    name_port+:: {
      // Force port to _only_ be below number (note no `+::` construct)
      port_spec:: { number: 4242 },
    },
  },
  ingress: bitnami.Ingress($.name + "-ingress") {
    host: "foo.g.dev.bitnami.net",
    target_svc: $.service,
  },
};

stack {
  // Assert we got expected port number for Ingress
  assert (stack.ingress.spec.rules[0].http.paths[0].backend.service.port == { number: 4242 }),
}


================================================
FILE: tests/test-PDB-no-spec.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local simple_validate = (import "test-simple-validate.pass.jsonnet").items_;
simple_validate {
  deploy_pdb+: {
    spec+: {
      minAvailable:: null,
    },
  },
}


================================================
FILE: tests/test-PDB-wrong-spec.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local simple_validate = (import "test-simple-validate.pass.jsonnet").items_;
simple_validate {
  deploy_pdb+: {
    spec+: {
      minAvailable: 1,
      maxUnavailable: 2,
    },
  },
}


================================================
FILE: tests/test-Pod-no_containers_array.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local simple_validate = (import "test-simple-validate.pass.jsonnet").items_;
simple_validate {
  pod+: {
    spec+: {
      containers: [],
    },
  },
}


================================================
FILE: tests/test-Pod-no_containers_map.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local simple_validate = (import "test-simple-validate.pass.jsonnet").items_;
simple_validate {
  pod+: {
    spec+: {
      containers_:: {},
    },
  },
}


================================================
FILE: tests/test-Pod-secretmount.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local simple_validate = (import "test-simple-validate.pass.jsonnet").items_;
simple_validate {
  pod+: {
    metadata+: {
      spec+: {
        containers_+: {
          foo_cont+: {
            env_+: {
              my_secret: kube.SecretKeyRef($.secret, "sec_key_nopes"),
            },
          },
        },
      },
    },
  },
}


================================================
FILE: tests/test-SealedSecret.fail.jsonnet
================================================
local kube = import "../kube.libsonnet";
local stack = {
  sealedsecret: kube.SealedSecret("foo") {
    spec+: {
      bar: std.parseJson(importstr "test-sealedsecrets.json"),
    },
  },
};

kube.List() {
  items_+: stack,
}


================================================
FILE: tests/test-SealedSecret.pass.json
================================================
{
  "some_key": "dGVzdAo=",
  "another_key": "dGVzdAo="
}


================================================
FILE: tests/test-SealedSecret.pass.jsonnet
================================================
local kube = import "../kube.libsonnet";
local stack = {
  sealedsecret: kube.SealedSecret("foo") {
    spec+: {
      encryptedData: std.parseJson(importstr "test-SealedSecret.pass.json"),
    },
  },
};

kube.List() {
  items_+: stack,
}


================================================
FILE: tests/test-Service-container_index.fail.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";

local stack = {
  name:: "test-Service-pass",
  pod: kube.Pod($.name + "-pod") {
    spec+: {
      containers_+: {
        default: kube.Container($.name + "-default") {
          image: "nginx:1.12",
          ports_+: {
            http: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
        sidecar: kube.Container($.name + "-sidecar") {
          image: "nginx:1.12",
          ports_+: {
            sidecar: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
      },
    },
  },
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    spec+: {
      template+: {
        spec+: $.pod.spec,
      },
    },
  },
  service: kube.Service($.name + "-svc") {
    local this = self,
    target_pod: $.deploy.spec.template,
    // Force fail by selecting an index out of range.
    container_index: 3,
  },
};

stack


================================================
FILE: tests/test-Service-container_index.pass.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";

local stack = {
  name:: "test-Service-pass",
  pod: kube.Pod($.name + "-pod") {
    spec+: {
      containers_+: {
        default: kube.Container($.name + "-default") {
          image: "nginx:1.12",
          ports_+: {
            http: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
        sidecar: kube.Container($.name + "-sidecar") {
          image: "nginx:1.12",
          ports_+: {
            http_sidecar: { containerPort: 80 },
            metrics: { containerPort: 9099 },
          },
        },
      },
    },
  },
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    spec+: {
      template+: {
        spec+: $.pod.spec,
      },
    },
  },
  service: kube.Service($.name + "-svc") {
    local this = self,
    target_pod: $.deploy.spec.template,
    container_index: 1,
  },
};

stack {
  // Assert we got the 2nd containers port named "http" for service
  assert (stack.service.spec.ports[0].name == "http-sidecar") : "Expected service port http-sidecar.",
}


================================================
FILE: tests/test-gke-ManagedCertificate.fail.jsonnet
================================================
local kube = import "../kube-platforms.libsonnet";
local stack = {
  foocert: kube.gke.ManagedCertificate("foo") {
    spec+: {
      domains: [],
    },
  },
};

kube.List() {
  items_+: stack,
}


================================================
FILE: tests/test-gke-ManagedCertificate.pass.jsonnet
================================================
local kube = import "../kube-platforms.libsonnet";
local stack = {
  foocert: kube.gke.ManagedCertificate("foo") {
    spec+: {
      domains: ["foo.example.com"],
    },
  },
};

kube.List() {
  items_+: stack,
}


================================================
FILE: tests/test-simple-validate.pass.jsonnet
================================================
local bitnami = import "../bitnami.libsonnet";
local kube = import "../kube.libsonnet";
local utils = import "../utils.libsonnet";

// Just a simple stack to exercise our kube.libsonnet
// objects, output is saved to tests/golden/test-simple-validate.pass.json
// to assert textual diff output.
local stack = {
  namespace:: "foons",
  name:: "foo",

  ns: kube.Namespace($.namespace),

  sa: kube.ServiceAccount($.name + "-sa") {
    metadata+: { namespace: $.namespace },
  },

  role: kube.Role($.name + "-role") {
    metadata+: { namespace: $.namespace },
    rules: [{
      apiGroups: [""],
      resources: ["pods", "secrets", "configmaps", "persistentvolumeclaims"],
      verbs: ["get"],
    }, {
      apiGroups: [""],
      resources: ["pods"],
      verbs: ["patch"],
    }],
  },

  rolebinding: kube.RoleBinding($.name + "-rolebinding") {
    metadata+: { namespace: $.namespace },
    roleRef_: $.role,
    subjects_+: [$.sa],
  },

  config: kube.ConfigMap($.name + "-config") {
    metadata+: { namespace: $.namespace },
    data: {
      foo_key: "bar_val",
    },
  },

  secret: kube.Secret($.name + "-secret") {
    metadata+: { namespace: $.namespace },
    data: {
      sec_key: "c2VjcmV0Cg==",
    },
  },

  // NB: making up an Ingress pointing to $.deploy Pod
  service: kube.Service($.name + "-svc") {
    metadata+: { namespace: $.namespace },
    target_pod: $.deploy.spec.template,
  },

  ingress: bitnami.Ingress($.name + "-ingress") {
    metadata+: { namespace: $.namespace },
    host: "foo.g.dev.bitnami.net",
    target_svc: $.service,
  },

  // An ingress with multiple hosts but none of the hosts specifies a path (#54).
  pathlessIngress: kube.Ingress($.name + "-pathless-ingress") {
    metadata+: { namespace: $.namespace },
    spec+: {
      rules+: [
        {
          host: "a.example.com",
          http: {
            paths: [{
              pathType: "ImplementationSpecific",
              backend: {
                service: {
                  name: "service-a",
                  port: {
                    name: "web",
                  },
                },
              },
            }],
          },
        },
        {
          host: "b.example.com",
          http: {
            paths: [{
              pathType: "ImplementationSpecific",
              backend: {
                service: {
                  name: "service-2",
                  port: {
                    name: "web",
                  },
                },
              },
            }],
          },
        },
      ],
    },
  },

  // NB: just a simple example pod
  pod: kube.Pod($.name + "-pod") {
    metadata+: { namespace: $.namespace },
    spec+: {
      containers_+: {
        foo_cont: kube.Container($.name) {
          image: "nginx:1.12",
          env_+: {
            my_secret: kube.SecretKeyRef($.secret, "sec_key"),
            other_key: null,
          },
          ports_+: {
            http: { containerPort: 80 },
            udp_port: { containerPort: 888, protocol: "UDP" },
          },
          volumeMounts_+: {
            config_vol: { mountPath: "/config" },
          },
        },
      },
      volumes_+: {
        config_vol: kube.ConfigMapVolume($.config),
      },
    },
  },

  // NB: all object below needing to spec a Pod will just
  // use above particular pod manifest just for convenience
  deploy: kube.Deployment($.name + "-deploy") {
    local this = self,
    metadata+: { namespace: $.namespace },
    spec+: {
      template+: {
        spec+: $.pod.spec {
          affinity+: utils.weakNodeDiversity(this.spec.selector),
          serviceAccountName: $.sa.metadata.name,
        },
      },
    },
  },

  deploy_pdb: kube.PodDisruptionBudget($.name + "-deploy-pdb") {
    target_pod: $.deploy.spec.template,
    spec+: {
      minAvailable: 1,
    },
  },

  sts: kube.StatefulSet($.name + "-sts") {
    metadata+: { namespace: $.namespace },
    spec+: {
      template+: {
        spec+: $.pod.spec {
          serviceAccountName: $.sa.metadata.name,
          containers_+: {
            foo_cont+: {
              volumeMounts_+: {
                datadir: { mountPath: "/foo/data" },
              },
            },
          },
        },
      },
      volumeClaimTemplates_+: {
        datadir: kube.PersistentVolumeClaim("datadir") {
          metadata+: { namespace: $.namespace },
          storage: "10Gi",
        },
      },
    },
  },

  ds: kube.DaemonSet($.name + "-ds") {
    metadata+: { namespace: $.namespace },
    spec+: {
      template+: {
        spec: $.pod.spec,
      },
    },
  },

  job: kube.Job($.name + "-job") {
    metadata+: { namespace: $.namespace },
    spec+: {
      template+: {
        spec+: {
          containers_+: {
            foo_cont: kube.Container($.name) {
              image: "busybox",
            },
          },
        },
      },
    },
  },

  cronjob: kube.CronJob($.name + "-cronjob") {
    metadata+: { namespace: $.namespace },
    spec+: {
      jobTemplate+: {
        spec+: {
          template+: {
            spec+: {
              containers_+: {
                foo_cont: kube.Container($.name) {
                  image: "busybox",
                },
              },
            },
          },
        },
      },
      schedule: "0 * * * *",
    },
  },

  // NB: create NSP from $.deploy Pod ref
  nsp_pods: kube.NetworkPolicy($.name + "-nsp-pods") {
    metadata+: { namespace: $.namespace },
    // NB: $.deploy has unique "foo-deploy" label (as well as other
    // podLabelsSelector() arg)
    spec+: kube.podLabelsSelector($.deploy) {
      // NB: making up $.deploy needing to get reached by $job, $.cronjob
      // and nginx-ingress-controller (running in its own NS named "nginx-ingress"
      ingress_: {
        from_jobs_and_ingressctl: {
          from: [
            kube.podLabelsSelector($.job),
            kube.podLabelsSelector($.cronjob),
            { namespaceSelector: { matchLabels: { name: "nginx-ingress" } } },
          ],
          ports: kube.podsPorts([$.deploy]),
        },
      },
      // NB: making up $.deploy needing to connect to $.sts, and
      // "kube-system" NS for DNS services
      egress_: {
        to_sts: {
          to: [
            kube.podLabelsSelector($.sts),
          ],
          ports: kube.podsPorts([$.sts]),
        },
        to_kube_dns: {
          to: [
            { namespaceSelector: { matchLabels: { name: "kube-system" } } },
          ],
          ports: [{ port: 53, protocol: "UDP" }],
        },
      },
    },
  },
  // NB: these VPAs need the VPA CRD added to the cluster, for local k3s testing
  // we add it via the `init-kube` Makefile target using `init-kube.jsonnet`
  vpa: kube.VerticalPodAutoscaler($.name + "-vpa") {
    spec+: {
      targetRef: {
        apiVersion: "apps/v1",
        kind: "Deployment",
        name: "foo-deploy",
      },
    },
  },
  deploy_vpa: kube.createVPAFor($.deploy),
  tls_cert: bitnami.CertManager.InCluster.Certificate("foo-cert", $.namespace),
};

kube.List() {
  items_+: stack,
}


================================================
FILE: tests/unittests.pass.jsonnet
================================================
local kube = import "../kube.libsonnet";
local utils = import "../utils.libsonnet";

local an_obj = kube._Object("v1", "Gentle", "foo");
local a_pod = kube.Pod("foo") {
  metadata+: { labels+: { foo: "bar", bar: "qxx" } },
  spec+: {
    containers_+: {
      foo: kube.Container("foo") {
        image: "nginx",
        ports_: {
          http: { containerPort: 8080 },
          https: { containerPort: 8443 },
          udp: { containerPort: 5353, protocol: "UDP" },
        },
      },
    },
  },
};
local a_deploy = kube.Deployment("foo") {
  spec+: { template+: { metadata+: a_pod.metadata, spec+: a_pod.spec } },
};
// Basic unittesting for methods that are not exercised by the other e2e-ish tests
// kube.libsonnet
std.assertEqual(kube.objectValues({ a: 1, b: 2 }), [1, 2]) &&
std.assertEqual(kube.objectItems({ a: 1, b: 2 }), [["a", 1], ["b", 2]]) &&
std.assertEqual(kube.hyphenate("foo_bar_baz"), ("foo-bar-baz")) &&
std.assertEqual(kube.mapToNamedList({ foo: { a: "b" } }), [{ name: "foo", a: "b" }]) &&
std.assertEqual(kube.filterMapByFields({ a: 1, b: 2, c: 3 }, ["a", "c", "d"]), { a: 1, c: 3 }) &&
std.assertEqual(kube.parseOctal("755"), 493) &&
std.assertEqual(kube.siToNum("42G"), 42 * 1e9) &&
std.assertEqual(kube.siToNum("42Gi"), 42 * std.pow(2, 30)) &&
std.assertEqual(kube.toUpper("ForTy 2"), "FORTY 2") &&
std.assertEqual(kube.toLower("ForTy 2"), "forty 2") &&
std.assertEqual(an_obj, {
  apiVersion: "v1",
  kind: "Gentle",
  metadata: { name: "foo", labels: { name: "foo" }, annotations: {} },
}) &&
std.assertEqual(
  [kube.podRef(a_deploy).spec.ports("TCP"), kube.podRef(a_deploy).spec.ports("UDP")],
  [[8080, 8443], [5353]]
) &&
std.assertEqual(
  // latest kubecfg produces stable output from maps hashes, so below shouldn't be flaky
  kube.podsPorts([a_deploy]),
  [
    { port: 8080, protocol: "TCP" },
    { port: 8443, protocol: "TCP" },
    { port: 5353, protocol: "UDP" },
  ]
) &&
std.assertEqual(
  kube.podLabelsSelector(a_deploy),
  { podSelector: { matchLabels: { name: "foo", foo: "bar", bar: "qxx" } } }
) &&
// utils.libsonnet
std.assertEqual(
  [utils.path_join("foo", "bar"), utils.path_join("foo/", "bar")],
  ["foo/bar", "foo/bar"]
) &&
std.assertEqual(
  utils.trimUrl("http://example.com/foo/"),
  "http://example.com/foo"
) &&
std.assertEqual(
  std.parseJson(utils.toJson('{ "foo": "bar\nqqq" }')),
  '{ "foo": "bar\nqqq" }',
) &&
std.assertEqual(
  utils.parentDomain("foo.example.com"),
  "example.com"
) &&
std.assertEqual(
  utils.parentDomain("foo.example.com"),
  "example.com"
) &&
std.assertEqual(
  std.uniq([
    x.podAffinityTerm.labelSelector
    for x in utils.weakNodeDiversity({ foo: "bar" }).podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution
  ]),
  [{ foo: "bar" }]
) &&
std.assertEqual(
  (utils.HashedConfigMap("hashed-cm")) { data+: { foo: "bar" } }.metadata.name,
  "hashed-cm-94232c5",
) &&
std.assertEqual(
  (
    (utils.HashedConfigMap("hashed-cm")) {
      data+: { foo: "bar" },
    }.metadata.name
    ==
    (utils.HashedConfigMap("hashed-cm")) {
      data+: { foo: "baz" },
    }.metadata.name
  ),
  false,
) &&
std.assertEqual(
  (utils.HashedSecret("hashed-secret")) { data+: { foo: std.base64("bar") } }.metadata.name,
  "hashed-secret-16f81db",
) &&
std.assertEqual(
  (
    (utils.HashedSecret("hashed-secret")) {
      data+: { foo: std.base64("bar") },
    }.metadata.name
    ==
    (utils.HashedSecret("hashed-secret")) {
      data+: { foo: std.base64("baz") },
    }.metadata.name
  ),
  false,
) &&
true


================================================
FILE: utils.libsonnet
================================================
/*
 * kube-libsonnet - A jsonnet helper library for Kubernetes
 *
 * Copyright 2018-2020 VMware Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Various opinionated helper functions, that might not be generally
// useful in other deployments.
local kube = import "kube.libsonnet";

{
  path_join(prefix, suffix):: (
    if std.endsWith(prefix, "/") then prefix + suffix
    else prefix + "/" + suffix
  ),

  trimUrl(str):: (
    if std.endsWith(str, "/") then
      std.substr(str, 0, std.length(str) - 1)
    else
      str
  ),

  toJson(x):: (
    if std.type(x) == "string" then std.escapeStringJson(x)
    else std.toString(x)
  ),

  parentDomain(fqdn):: (
    local parts = std.split(fqdn, ".");
    local tail = [parts[i] for i in std.range(1, std.length(parts) - 1)];
    assert std.length(tail) >= 1 : "Tried to use parent of top-level DNS domain %s" % fqdn;
    std.join(".", tail)
  ),

  // affinity=weakNodeDiversity to Try to spread across separate
  // nodes/zones (for fault-tolerance)
  weakNodeDiversity(selector):: {
    podAntiAffinity+: {
      preferredDuringSchedulingIgnoredDuringExecution+: [{
        weight: 70,
        podAffinityTerm: {
          labelSelector: selector,
          topologyKey: k,
        },
      } for k in [
        "kubernetes.io/hostname",
        "failure-domain.beta.kubernetes.io/zone",
        "failure-domain.beta.kubernetes.io/region",
      ]],
    },
  },

  TlsIngress(name):: kube.Ingress(name) {
    local this = self,
    metadata+: {
      annotations+: {
        "kubernetes.io/tls-acme": "true",
        "kubernetes.io/ingress.class": "nginx",
      },
    },
    spec+: {
      tls+: [{
        hosts: std.set([r.host for r in this.spec.rules]),
        secretName: this.metadata.name + "-tls",
      }],
    },
  },

  AuthIngress(name):: $.TlsIngress(name) {
    local this = self,
    host:: error "host is required",
    authHost:: "auth." + $.parentDomain(this.host),
    metadata+: {
      annotations+: {
        // NB: Our nginx-ingress no-auth-locations includes "/oauth2"
        "nginx.ingress.kubernetes.io/auth-signin": "https://%s/oauth2/start?rd=%%2F$server_name$escaped_request_uri" % this.authHost,
        "nginx.ingress.kubernetes.io/auth-url": "https://%s/oauth2/auth" % this.authHost,
        "nginx.ingress.kubernetes.io/auth-response-headers": "X-Auth-Request-User, X-Auth-Request-Email",
      },
    },
  },

  local hashed = {
    local this = self,
    metadata+: {
      local hash = std.substr(std.md5(std.toString(this.data)), 0, 7),
      local orig_name = super.name,
      name: orig_name + "-" + hash,
      labels+: { name: orig_name },
    },
  },
  HashedConfigMap(name):: kube.ConfigMap(name) + hashed,
  HashedSecret(name):: kube.Secret(name) + hashed,
}
Download .txt
gitextract_n5_iu0de/

├── .gitattributes
├── .travis.yml
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── bitnami.libsonnet
├── examples/
│   ├── guestbook/
│   │   └── guestbook.jsonnet
│   └── wordpress/
│       ├── backend.jsonnet
│       ├── frontend.jsonnet
│       └── wordpress.jsonnet
├── kube-platforms.libsonnet
├── kube.libsonnet
├── tests/
│   ├── Dockerfile
│   ├── Makefile
│   ├── docker-compose.yaml
│   ├── golden/
│   │   ├── init-kube.json
│   │   ├── test-Ingress-2ndport.pass.json
│   │   ├── test-Ingress-port_num_only.pass.json
│   │   ├── test-SealedSecret.pass.json
│   │   ├── test-Service-container_index.pass.json
│   │   ├── test-gke-ManagedCertificate.pass.json
│   │   ├── test-simple-validate.pass.json
│   │   └── unittests.pass.json
│   ├── init-kube.jsonnet
│   ├── k3s-e2e-test.sh
│   ├── test-Ingress-2ndport.pass.jsonnet
│   ├── test-Ingress-name_port.fail.jsonnet
│   ├── test-Ingress-port_num_only.pass.jsonnet
│   ├── test-PDB-no-spec.fail.jsonnet
│   ├── test-PDB-wrong-spec.fail.jsonnet
│   ├── test-Pod-no_containers_array.fail.jsonnet
│   ├── test-Pod-no_containers_map.fail.jsonnet
│   ├── test-Pod-secretmount.fail.jsonnet
│   ├── test-SealedSecret.fail.jsonnet
│   ├── test-SealedSecret.pass.json
│   ├── test-SealedSecret.pass.jsonnet
│   ├── test-Service-container_index.fail.jsonnet
│   ├── test-Service-container_index.pass.jsonnet
│   ├── test-gke-ManagedCertificate.fail.jsonnet
│   ├── test-gke-ManagedCertificate.pass.jsonnet
│   ├── test-simple-validate.pass.jsonnet
│   └── unittests.pass.jsonnet
└── utils.libsonnet
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (151K chars).
[
  {
    "path": ".gitattributes",
    "chars": 34,
    "preview": "tests/golden/* linguist-generated\n"
  },
  {
    "path": ".travis.yml",
    "chars": 370,
    "preview": "language: bash\n\nos:\n  - linux\n\nservices:\n  - docker\n\nbefore_install: # update to docker-ce\n  - curl -fsSL https://downlo"
  },
  {
    "path": "CODEOWNERS",
    "chars": 32,
    "preview": "*  @dbarranco @jbianquetti-nami\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 1383,
    "preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of the \"Bitnami Charts\" project, we pledge to respect eve"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 220,
    "preview": "# Originally taken from https://github.com/bitnami-labs/kube-manifests/,\n# trimmed down to only run lib testing.\n#\n# Pro"
  },
  {
    "path": "README.md",
    "chars": 1455,
    "preview": "## WARNING: This repository is no longer actively maintained by Bitnami/VMware.\nWe have made the difficult decision to s"
  },
  {
    "path": "SECURITY.md",
    "chars": 6168,
    "preview": "# Security Release Process\n\nThe community has adopted this security disclosure and response policy to ensure we responsi"
  },
  {
    "path": "bitnami.libsonnet",
    "chars": 5797,
    "preview": "// Generic stuff is in kube.libsonnet - this file contains\n// bitnami-specific conventions.\n\nlocal kube = import \"kube.l"
  },
  {
    "path": "examples/guestbook/guestbook.jsonnet",
    "chars": 3101,
    "preview": "// Copyright 2017 The kubecfg authors\n//\n//\n//    Licensed under the Apache License, Version 2.0 (the \"License\");\n//    "
  },
  {
    "path": "examples/wordpress/backend.jsonnet",
    "chars": 8137,
    "preview": "local kube = import \"../../kube.libsonnet\";\n\nlocal labels = {\n  tier: \"backend\",\n};\n\n{\n  backend: {\n    secret: kube.Sec"
  },
  {
    "path": "examples/wordpress/frontend.jsonnet",
    "chars": 2772,
    "preview": "local kube = import \"../../kube.libsonnet\";\nlocal be = import \"backend.jsonnet\";\n\nlocal labels = {\n  tier: \"frontend\",\n}"
  },
  {
    "path": "examples/wordpress/wordpress.jsonnet",
    "chars": 1098,
    "preview": "// Copyright (c) 2018 Bitnami\n//\n//    Licensed under the Apache License, Version 2.0 (the \"License\");\n//    you may not"
  },
  {
    "path": "kube-platforms.libsonnet",
    "chars": 716,
    "preview": "// Extend kube.libsonnet for platform specific CRDs, drop-in usage as:\n//\n// local kube = import \"kube-platforms.jsonnet"
  },
  {
    "path": "kube.libsonnet",
    "chars": 23051,
    "preview": "// Generic library of Kubernetes objects (https://github.com/bitnami-labs/kube-libsonnet)\n//\n// Objects in this file fol"
  },
  {
    "path": "tests/Dockerfile",
    "chars": 936,
    "preview": "FROM bitnami/minideb:buster\nMAINTAINER sre@bitnami.com\n\nARG jsonnet_version=0.14.0\nARG kubectl_version=v1.17.0\nARG kubec"
  },
  {
    "path": "tests/Makefile",
    "chars": 5186,
    "preview": "# K3S_<KUBE_MAJOR_RELEASE> as a \"mapping\" from KUBE_MAJOR_RELEASE to k3s tag,\n# from https://hub.docker.com/r/rancher/k3"
  },
  {
    "path": "tests/docker-compose.yaml",
    "chars": 830,
    "preview": "version: \"3\"\nservices:\n  kube-api:\n    image: rancher/k3s:${K3S_VERSION}\n    command: server --disable-agent\n    contain"
  },
  {
    "path": "tests/golden/init-kube.json",
    "chars": 846,
    "preview": "{\n   \"vpa_crd\": {\n      \"apiVersion\": \"apiextensions.k8s.io/v1beta1\",\n      \"kind\": \"CustomResourceDefinition\",\n      \"m"
  },
  {
    "path": "tests/golden/test-Ingress-2ndport.pass.json",
    "chars": 4680,
    "preview": "{\n   \"deploy\": {\n      \"apiVersion\": \"apps/v1\",\n      \"kind\": \"Deployment\",\n      \"metadata\": {\n         \"annotations\": "
  },
  {
    "path": "tests/golden/test-Ingress-port_num_only.pass.json",
    "chars": 4677,
    "preview": "{\n   \"deploy\": {\n      \"apiVersion\": \"apps/v1\",\n      \"kind\": \"Deployment\",\n      \"metadata\": {\n         \"annotations\": "
  },
  {
    "path": "tests/golden/test-SealedSecret.pass.json",
    "chars": 477,
    "preview": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"bitnami.com/v1alpha1\",\n         \"kind\": \"SealedSe"
  },
  {
    "path": "tests/golden/test-Service-container_index.pass.json",
    "chars": 4806,
    "preview": "{\n   \"deploy\": {\n      \"apiVersion\": \"apps/v1\",\n      \"kind\": \"Deployment\",\n      \"metadata\": {\n         \"annotations\": "
  },
  {
    "path": "tests/golden/test-gke-ManagedCertificate.pass.json",
    "chars": 435,
    "preview": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"networking.gke.io/v1beta1\",\n         \"kind\": \"Man"
  },
  {
    "path": "tests/golden/test-simple-validate.pass.json",
    "chars": 28180,
    "preview": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"v1\",\n         \"data\": {\n            \"foo_key\": \"b"
  },
  {
    "path": "tests/golden/unittests.pass.json",
    "chars": 5,
    "preview": "true\n"
  },
  {
    "path": "tests/init-kube.jsonnet",
    "chars": 806,
    "preview": "local kube = import \"../kube.libsonnet\";\n\nlocal crds = {\n  // A simplified VPA CRD from https://github.com/kubernetes/au"
  },
  {
    "path": "tests/k3s-e2e-test.sh",
    "chars": 781,
    "preview": "#!/bin/sh\nset -eu\necho \"INFO: Starting tests: unit, lint ...\"\n(set -x\n  make -C tests local-tests\n)\nexport KUBECONFIG=/t"
  },
  {
    "path": "tests/test-Ingress-2ndport.pass.jsonnet",
    "chars": 1153,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  name:: \"test-"
  },
  {
    "path": "tests/test-Ingress-name_port.fail.jsonnet",
    "chars": 1028,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  name:: \"test-"
  },
  {
    "path": "tests/test-Ingress-port_num_only.pass.jsonnet",
    "chars": 1118,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  name:: \"test-"
  },
  {
    "path": "tests/test-PDB-no-spec.fail.jsonnet",
    "chars": 207,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal simple_validate = (import \"test-simple-validate.pass.jsonnet\").items_;\nsi"
  },
  {
    "path": "tests/test-PDB-wrong-spec.fail.jsonnet",
    "chars": 228,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal simple_validate = (import \"test-simple-validate.pass.jsonnet\").items_;\nsi"
  },
  {
    "path": "tests/test-Pod-no_containers_array.fail.jsonnet",
    "chars": 195,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal simple_validate = (import \"test-simple-validate.pass.jsonnet\").items_;\nsi"
  },
  {
    "path": "tests/test-Pod-no_containers_map.fail.jsonnet",
    "chars": 197,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal simple_validate = (import \"test-simple-validate.pass.jsonnet\").items_;\nsi"
  },
  {
    "path": "tests/test-Pod-secretmount.fail.jsonnet",
    "chars": 379,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal simple_validate = (import \"test-simple-validate.pass.jsonnet\").items_;\nsi"
  },
  {
    "path": "tests/test-SealedSecret.fail.jsonnet",
    "chars": 226,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal stack = {\n  sealedsecret: kube.SealedSecret(\"foo\") {\n    spec+: {\n      b"
  },
  {
    "path": "tests/test-SealedSecret.pass.json",
    "chars": 58,
    "preview": "{\n  \"some_key\": \"dGVzdAo=\",\n  \"another_key\": \"dGVzdAo=\"\n}\n"
  },
  {
    "path": "tests/test-SealedSecret.pass.jsonnet",
    "chars": 240,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal stack = {\n  sealedsecret: kube.SealedSecret(\"foo\") {\n    spec+: {\n      e"
  },
  {
    "path": "tests/test-Service-container_index.fail.jsonnet",
    "chars": 1015,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  name:: \"test-"
  },
  {
    "path": "tests/test-Service-container_index.pass.jsonnet",
    "chars": 1141,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  name:: \"test-"
  },
  {
    "path": "tests/test-gke-ManagedCertificate.fail.jsonnet",
    "chars": 197,
    "preview": "local kube = import \"../kube-platforms.libsonnet\";\nlocal stack = {\n  foocert: kube.gke.ManagedCertificate(\"foo\") {\n    s"
  },
  {
    "path": "tests/test-gke-ManagedCertificate.pass.jsonnet",
    "chars": 214,
    "preview": "local kube = import \"../kube-platforms.libsonnet\";\nlocal stack = {\n  foocert: kube.gke.ManagedCertificate(\"foo\") {\n    s"
  },
  {
    "path": "tests/test-simple-validate.pass.jsonnet",
    "chars": 7092,
    "preview": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\nlocal utils = import \"../utils.l"
  },
  {
    "path": "tests/unittests.pass.jsonnet",
    "chars": 3516,
    "preview": "local kube = import \"../kube.libsonnet\";\nlocal utils = import \"../utils.libsonnet\";\n\nlocal an_obj = kube._Object(\"v1\", \""
  },
  {
    "path": "utils.libsonnet",
    "chars": 3283,
    "preview": "/*\n * kube-libsonnet - A jsonnet helper library for Kubernetes\n *\n * Copyright 2018-2020 VMware Inc.\n *\n * Licensed unde"
  }
]

About this extraction

This page contains the full source code of the bitnami-labs/kube-libsonnet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (136.5 KB), approximately 33.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!