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
[](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,
}
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.