Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'make airgap-package' target and associated scripts for bundling images for air gap #475

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
squizzi marked this conversation as resolved.
Show resolved Hide resolved
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