At this point in the tutorial, we have a bundle on the local file system, but how to we get it into OCI format into a container registry for consumption?
Info
👍 What’s a bundle? You will learn more about bundles in the imgpkg section later, but for now think of a bundle as a configuration file and a list of references to one or more container images.
Now it’s time to create our first custom resource, the Package.
- Create the
PackageMetadataobject first.
cat <<- _EOF_ > "${ROOT_DIR}/packages/${PACKAGE_NAME}/PackageMetadata.yaml"
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
name: ${PACKAGE_NAME}.${PACKAGE_FQN}
spec:
displayName: "${PACKAGE_DISPLAY}"
shortDescription: "${PACKAGE_DESC_SHORT}"
longDescription: "${PACKAGE_DESC_LONG}"
categories:
- demo
_EOF_
Question: Wait, now hold up. What is this PackageMetadata object and all this about?
Answer: This is a custom resource that is used by the kapp-controller to manage packages. It is a way to store metadata about the package, such as the name, description, and categories. For linux users, a good analogy is thinking of it like apt in debian based distros. First you apt update the metadata to see what packages are available in the repository, then you apt install the package you want. A similar model is used with carvel.
You can refer to the documentation here for additional depth in the custom resource definitions.
- Now let’s generate the OpenAPI schema from the values. When writing schemas, the cheatsheet is your friend. Bookmark it!
Warning
⚠️ HERE BE DRAGONS This is an amazing capability of carvel to validate schemas before installing packages, but it is not without it’s challenges.
This part is challenging to include in a tutorial for several reasons.
- There is not one single method to create Kubernetes applications, but several…
- There is however one format the schema is required in, OpenAPI v3+.
This may take more or less effort based on the upstream sources you have been handed.
Below are just a few examples of what you might come across.
Note
IMPORTANT: For this tutorial, you only need to follow the “generic helm” option. The others are provided for reference about possible options you might encounter as a package author.
- For a
yttpackage, generating a schema is as simple as;
# (You don't need to run this for this tutorial)
# NOTE: We place the output from this into a temporary file, as we will use it later.
ytt \
--file "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/bundle/ytt/schema.yaml" \
--data-values-schema-inspect \
--output openapi-v3 \
> "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/schema/openapi.yaml"
For a Helm chart however…
- Is it a Bitnami Helm Chart?
# YES: -> Then try the Bitnami schema generator tool.
# https://github.com/bitnami-labs/readme-generator-for-helm
- Is it a generic Helm chart? (This tutorial is a generic Helm chart)
# YES: -> Try the Helm schema plugin
# NOTE: You need the plugin, yq and sed available for this to work.
# Part 1. Generate the schema from the Helm chart
helm plugin install https://github.com/karuppiah7890/helm-schema-gen.git
helm schema-gen \
"${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/bundle/vendor/helm/values.yaml" \
| \
yq \
--input-format yaml \
--output-format yaml \
--prettyPrint \
| \
tail -n +3 \
| \
yq \
--input-format yaml \
--output-format yaml \
--prettyPrint \
| \
sed 's|^| |g' \
> "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/schema/helm.yaml"
# Part 2. Create the OpenAPI schema from the Helm schema
cat <<- _EOF_ > "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/schema/openapi.yaml"
openapi: 3.0.0
info:
version: ${PACKAGE_VERSION}
title: Schema for ${PACKAGE_NAME} ${PACKAGE_VERSION}
paths: {}
components:
schemas:
dataValues:
$(cat "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/schema/helm.yaml")
_EOF_
- No matter which option you have performed, you should always manually validate the schema looks correct before continuing as this process can be quite fragile.
# Look at schema.dataValues.properties ...
yq \
--input-format yaml \
--output-format yaml \
--prettyPrint \
"${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/schema/openapi.yaml"
- Create a template for the
Packageobject next. This is used later to generate the package resource for theRepository.
# This step varies wildly per-package. This is an example for this tutorial.
# When creating real packages, this file is usually created once, and lasts for the lifetime of the package.
cat <<- _EOF_ > "${ROOT_DIR}/packages/${PACKAGE_NAME}/${PACKAGE_VERSION}/templates/Package.yaml"
#@ load("@ytt:data", "data")
#@ load("@ytt:yaml", "yaml")
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
name: #@ "${PACKAGE_NAME}.${PACKAGE_FQN}." + data.values.version
namespace: ${PACKAGE_NAMESPACE}
spec:
refName: ${PACKAGE_NAME}.${PACKAGE_FQN}
version: #@ data.values.version
releaseNotes: |
${PACKAGE_RELEASE_NOTES}
capacityRequirementsDescription: "RAM: 1GB CPU: 1"
licenses:
- "Unlicense"
valuesSchema:
openAPIv3: #@ yaml.decode(data.values.openapi)["components"]["schemas"]["dataValues"]
template:
spec:
fetch:
- imgpkgBundle:
image: #@ "${OCI_REGISTRY}/${OCI_PROJECT}/packages/${PACKAGE_NAME}:" + data.values.version
template:
- helmTemplate:
path: "vendor/helm"
name: ${PACKAGE_NAME}
namespace: ${DEPLOY_NAMESPACE}
valuesFrom:
- secretRef:
name: ${PACKAGE_NAME}-values
- ytt:
paths:
- "-"
- "ytt"
- kbld:
paths:
- "kbld/Config.yaml"
- "-"
- ".imgpkg/images.yml"
deploy:
- kapp: {}
_EOF_
Ok, now take a break to digest what happened, there was a lot going on this section!
