Skip to content

Commit

Permalink
Add script and Makefile targets for bundling images for airgap
Browse files Browse the repository at this point in the history
* Pull charts referenced by providers k0s.extensions during helm-package

  In order to support airgap customers will need to place all of the charts
  a ManagedCluster may pull upon into a registry.  This adds support to our
  'helm-package' Makefile to ensure we package up all of the charts our templates
  reference so that they can be included in an airgap bundle.

* Place k0s.extensions images in a seperate image tarball

  For the first iteration of airgap we're using k0s airgap
  bundle for the management cluster but the ManagedClusters we
  deploy will still need the extensions images that the extension
  charts reference.

  This patch modifies the bundle-images target so it creates two
  bundles, one that will serve as the management cluster bundle
  and one that will serve as the ManagedCluster bundle.

* Add airgap-push script

  This script can be used to push images and Helm charts needed for an
  airgapped ManagedCluster deployment to a given image registry and chart
  repository.

* Package the airgap-push script with the bundle

Signed-off-by: Kyle Squizzato <[email protected]>
  • Loading branch information
squizzi committed Nov 7, 2024
1 parent e1432fe commit 3381013
Show file tree
Hide file tree
Showing 8 changed files with 520 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ vendor

# mkdocs folder
mkdocs

# airgap-push script directories
hmc-airgap
25 changes: 20 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,35 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
add-license: addlicense
$(ADDLICENSE) -c "" -ignore ".github/**" -ignore "config/**" -ignore "templates/**" -ignore ".*" -y 2024 .

##@ Build
##@ Package

TEMPLATES_DIR := templates
PROVIDER_TEMPLATES_DIR := $(TEMPLATES_DIR)/provider
CHARTS_PACKAGE_DIR ?= $(LOCALBIN)/charts
EXTENSION_CHARTS_PACKAGE_DIR ?= $(LOCALBIN)/charts/extensions
$(EXTENSION_CHARTS_PACKAGE_DIR): | $(LOCALBIN)
mkdir -p $(EXTENSION_CHARTS_PACKAGE_DIR)
$(CHARTS_PACKAGE_DIR): | $(LOCALBIN)
rm -rf $(CHARTS_PACKAGE_DIR)
mkdir -p $(CHARTS_PACKAGE_DIR)
IMAGES_PACKAGE_DIR ?= $(LOCALBIN)/images
$(IMAGES_PACKAGE_DIR): | $(LOCALBIN)
rm -rf $(IMAGES_PACKAGE_DIR)
mkdir -p $(IMAGES_PACKAGE_DIR)

TEMPLATE_FOLDERS = $(patsubst $(TEMPLATES_DIR)/%,%,$(wildcard $(TEMPLATES_DIR)/*))

.PHONY: helm-package
helm-package: $(CHARTS_PACKAGE_DIR) helm
helm-package: $(CHARTS_PACKAGE_DIR) $(EXTENSION_CHARTS_PACKAGE_DIR) helm
@make $(patsubst %,package-%-tmpl,$(TEMPLATE_FOLDERS))

bundle-images: dev-apply $(IMAGES_PACKAGE_DIR) ## Create a tarball with all images used by HMC.
@BUNDLE_TARBALL=$(IMAGES_PACKAGE_DIR)/hmc-images-$(VERSION).tgz EXTENSIONS_BUNDLE_TARBALL=$(IMAGES_PACKAGE_DIR)/hmc-extension-images-$(VERSION).tgz IMG=$(IMG) KUBECTL=$(KUBECTL) YQ=$(YQ) HELM=$(HELM) NAMESPACE=$(NAMESPACE) TEMPLATES_DIR=$(TEMPLATES_DIR) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) $(SHELL) "scripts/bundle-images.sh"

airgap-package: bundle-images ## Create a tarball with all images and Helm charts used by HMC, useful for deploying in air-gapped environments.
@TEMPLATES_DIR=$(TEMPLATES_DIR) EXTENSION_CHARTS_PACKAGE_DIR=$(EXTENSION_CHARTS_PACKAGE_DIR) HELM=$(HELM) YQ=$(YQ) $(SHELL) "scripts/package-k0s-extensions-helm.sh"
cd $(LOCALBIN) && mkdir -p scripts && cp ../scripts/airgap-push.sh scripts/airgap-push.sh && tar -czf hmc-airgap-$(VERSION).tgz scripts/airgap-push.sh $(shell basename $(CHARTS_PACKAGE_DIR)) $(shell basename $(IMAGES_PACKAGE_DIR))

package-%-tmpl:
@make TEMPLATES_SUBDIR=$(TEMPLATES_DIR)/$* $(patsubst %,package-chart-%,$(shell ls $(TEMPLATES_DIR)/$*))

Expand All @@ -154,6 +168,8 @@ lint-chart-%:
package-chart-%: lint-chart-%
$(HELM) package --destination $(CHARTS_PACKAGE_DIR) $(TEMPLATES_SUBDIR)/$*

##@ Build

LD_FLAGS?= -s -w
LD_FLAGS += -X github.com/Mirantis/hmc/internal/build.Version=$(VERSION)
LD_FLAGS += -X github.com/Mirantis/hmc/internal/telemetry.segmentToken=$(SEGMENT_TOKEN)
Expand Down Expand Up @@ -349,11 +365,11 @@ dev-creds-apply: dev-$(DEV_PROVIDER)-creds

.PHONY: dev-aws-nuke
dev-aws-nuke: envsubst awscli yq cloud-nuke ## Warning: Destructive! Nuke all AWS resources deployed by 'DEV_PROVIDER=aws dev-mcluster-apply'
@CLUSTER_NAME=$(CLUSTER_NAME) YQ=$(YQ) AWSCLI=$(AWSCLI) bash -c "./scripts/aws-nuke-ccm.sh elb"
@CLUSTER_NAME=$(CLUSTER_NAME) YQ=$(YQ) AWSCLI=$(AWSCLI) $(SHELL) "./scripts/aws-nuke-ccm.sh elb"
@CLUSTER_NAME=$(CLUSTER_NAME) $(ENVSUBST) < config/dev/aws-cloud-nuke.yaml.tpl > config/dev/aws-cloud-nuke.yaml
DISABLE_TELEMETRY=true $(CLOUDNUKE) aws --region $$AWS_REGION --force --config config/dev/aws-cloud-nuke.yaml --resource-type vpc,eip,nat-gateway,ec2,ec2-subnet,elb,elbv2,ebs,internet-gateway,network-interface,security-group
@rm config/dev/aws-cloud-nuke.yaml
@CLUSTER_NAME=$(CLUSTER_NAME) YQ=$(YQ) AWSCLI=$(AWSCLI) bash -c "./scripts/aws-nuke-ccm.sh ebs"
@CLUSTER_NAME=$(CLUSTER_NAME) YQ=$(YQ) AWSCLI=$(AWSCLI) $(SHELL) "./scripts/aws-nuke-ccm.sh ebs"

.PHONY: dev-azure-nuke
dev-azure-nuke: envsubst azure-nuke ## Warning: Destructive! Nuke all Azure resources deployed by 'DEV_PROVIDER=azure dev-mcluster-apply'
Expand Down Expand Up @@ -529,7 +545,6 @@ $(AWSCLI): | $(LOCALBIN)
exit 1; \
fi; \


# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
# $2 - package url which can be installed
Expand Down
12 changes: 12 additions & 0 deletions docs/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,15 @@ objects). For example:
CSI expects single Secret with configuration in `ini` format
([documented here](https://docs.vmware.com/en/VMware-vSphere-Container-Storage-Plug-in/2.0/vmware-vsphere-csp-getting-started/GUID-BFF39F1D-F70A-4360-ABC9-85BDAFBE8864.html)).
Options are similar to CCM and same defaults/considerations are applicable.

## Generating the airgap bundle
Use the `make airgap-package` target to manually generate the airgap bundle,
to ensure the correctly tagged HMC controller image is present in the bundle
prefix the `IMG` env var with the desired image, for example:

```bash
IMG="ghcr.io/mirantis/hmc:0.0.3" make airgap-package
```

Not setting an `IMG` var will use the default image name/tag generated by the
Makefile.
196 changes: 196 additions & 0 deletions scripts/airgap-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/bin/bash
# Copyright 2024
#
# 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.
#
# This script can be used to help users re-tag and push images and Helm charts
# into a private registry for use when deploying HMC ManagedClusters into an
# air-gapped environment. This script is packaged as part of the airgap bundle
# for convenience.

REPO=""
CHART_REPO=""
AIRGAP_BUNDLE=""
HELP=""
EXTENSION_TARBALL_PREFIX="hmc-extension-images"
WORK_DIR="$(pwd)/hmc-airgap"

# Print the help message
function print_help() {
echo "Usage:"
echo " airgap-push.sh [OPTIONS]"
echo "Ensure repositories are logged into via 'helm' and 'docker' before running this script."
echo "Options:"
echo " -h, --help"
echo " Print this help message"
echo " -r, --image-repo (required)"
echo " The image repo to push the images to"
echo " -c, --chart-repo (required)"
echo " The repository to push the Helm charts to, for OCI prefix use oci://"
echo " -i, --insecure-registry"
echo " Use insecure registry for pushing Helm charts"
echo " -a, --airgap-bundle (required)"
echo " The path to the airgap bundle"
}

function ctrl_c() {
echo "Caught CTRL-C, exiting..."
exit 1
}

trap ctrl_c INT

# Parse the options
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-h|--help)
HELP="true"
shift
;;
-r|--image-repo)
REPO="$2"
shift
shift
;;
-c|--chart-repo)
CHART_REPO="$2"
shift
shift
;;
-i|--insecure-registry)
INSECURE_REGISTRY="true"
shift
;;
-a|--airgap-bundle)
AIRGAP_BUNDLE="$2"
shift
shift
;;
*)
echo "Unknown option: $1"
print_help
exit 1
;;
esac
done


if [ ! -z "$HELP" ]; then
print_help
exit 0
fi

if [ -z "$REPO" ]; then
echo "The repository must be set"
print_help
exit 1
fi

if [ -z "$CHART_REPO" ]; then
echo "The chart repository must be set"
print_help
exit 1
fi

if [ -z "$AIRGAP_BUNDLE" ]; then
echo "The airgap bundle must be set"
exit 1
else
# Validate the airgap bundle
if [ ! -f "$AIRGAP_BUNDLE" ]; then
echo "The provided airgap bundle: ${AIRGAP_BUNDLE} does not exist"
exit 1
fi
fi

if [ ! $(command -v jq) ]; then
echo "'jq' could not be found, install 'jq' to continue"
exit 1
fi

mkdir -p ${WORK_DIR}

# Extract extension images from the airgap bundle.
echo "Extracting extension images from airgap bundle: ${AIRGAP_BUNDLE}..."
extension_tarball_name=$(tar tf ${AIRGAP_BUNDLE} | grep "${EXTENSION_TARBALL_PREFIX}")
tar -C ${WORK_DIR} -xf ${AIRGAP_BUNDLE} ${extension_tarball_name}
if [ $? -ne 0 ]; then
echo "Failed to extract extension images from the airgap bundle"
exit 1
fi

# Load the extension images into the Docker daemon for re-tagging and pushing.
echo "Loading extension images into Docker..."
docker load -i ${WORK_DIR}/${extension_tarball_name}
if [ $? -ne 0 ]; then
echo "Failed to load extension images into Docker"
exit 1
fi


# Extract the repositories json file from the extensions bundle.
echo "Retagging and pushing extension images to ${REPO}..."
tar -C ${WORK_DIR} -xf ${WORK_DIR}/${extension_tarball_name} "repositories"
for image in $(cat ${WORK_DIR}/repositories | jq -r 'to_entries[] | .key'); do
image_name=$(echo ${image} | grep -o '[^/]*$')

# docker images -a may return multiple images with the same name but
# different tags. We need to retag and push all of them.
for old_image in $(docker images -a | grep ${image} | awk '{print $1":"$2}'); do
tag=${old_image#*:}
new_image="${REPO}/${image_name}:${tag}"

echo "Retagging image: ${old_image} with ${new_image}..."

docker tag ${old_image} ${new_image}
if [ $? -ne 0 ]; then
echo "Failed to retag image: ${old_image} with ${new_image}"
exit 1
fi

echo "Pushing image: ${new_image}..."

docker push ${new_image}
if [ $? -ne 0 ]; then
echo "Failed to push image: ${new_image}"
exit 1
fi
done
done

# Extract all of the Helm charts from the airgap bundle.
echo "Extracting Helm charts from airgap bundle: ${AIRGAP_BUNDLE}..."
tar -C ${WORK_DIR} -xf ${AIRGAP_BUNDLE} "charts"
if [ $? -ne 0 ]; then
echo "Failed to extract Helm charts from the airgap bundle"
exit 1
fi

# Next, use Helm to push the charts to the given chart repository.
echo "Pushing Helm charts to ${CHART_REPO}..."
if [ ! -z "$INSECURE_REGISTRY" ]; then
insecure_registry_flag="--insecure-skip-tls-verify"
fi

for chart in $(find ${WORK_DIR}/charts -name "*.tgz"); do
helm push ${insecure_registry_flag} ${chart} ${CHART_REPO}
if [ $? -ne 0 ]; then
echo "Failed to push Helm chart: ${chart}"
exit 1
fi
done

# Clean up any extracted files.
echo "Cleaning up..."
rm -rf ${WORK_DIR}
Loading

0 comments on commit 3381013

Please sign in to comment.