Skip to content

Instantly share code, notes, and snippets.

@mdelillo
Last active July 21, 2020 21:01
Show Gist options
  • Save mdelillo/f587716e78780375820759a24b496df2 to your computer and use it in GitHub Desktop.
Save mdelillo/f587716e78780375820759a24b496df2 to your computer and use it in GitHub Desktop.
Create a builder from specified buildpacks, buildpackages, and mixins
#!/usr/bin/env bash
set -euo pipefail
timestamp="$(date +%s)"
builder_name="builder-${timestamp}"
mixins=""
buildpackages=()
buildpacks=()
usage() {
cat <<-EOF
Usage:
$0 [OPTIONS]
Options:
-b /path/to/buildpack1.tgz -b https://url/to/buildpack2.tgz ...
-c /path/to/buildpackage1.cnb -c gcr.io/path/to/buildpackage2 ...
-m <mixin1,build:mixin2,run:mixin3>
-n <builder-name>
EOF
}
parse_opts() {
while getopts ":b:c:hm:n:" opt; do
case ${opt} in
b)
buildpacks+=("${OPTARG}")
;;
c)
buildpackages+=("${OPTARG}")
;;
h)
usage
exit 0
;;
m)
mixins="${OPTARG//,/ }"
;;
n)
builder_name="${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
usage
exit 1
;;
:)
echo "Option -${OPTARG} requires an argument." >&2
usage
exit 1
;;
esac
done
}
get_image_packages() {
image="$1"
packages=""
for mixin in ${mixins}; do
if [[ ${mixin} = *set=* ]]; then
continue
fi
if [[ ${mixin} = ${image}:* ]]; then
packages="${packages} ${mixin#"${image}:"}"
elif [[ ${mixin} != build:* ]] && [[ ${mixin} != run:* ]]; then
packages="${packages} ${mixin}"
fi
done
echo -n "${packages}"
}
get_mixin_labels() {
image="$1"
all_build_packages="$(docker run --rm -it "${builder_name}-build" dpkg-query -f '${Package}\n' -W | sort -u | tr -d '\r')"
all_run_packages="$(docker run --rm -it "${builder_name}-run" dpkg-query -f '${Package}\n' -W | sort -u | tr -d '\r')"
shared_packages="$(comm -12 <(echo "${all_build_packages}") <(echo "${all_run_packages}"))"
if [[ $image == "build" ]]; then
exclusive_packages="$(comm -23 <(echo "${all_build_packages}") <(echo "${all_run_packages}"))"
elif [[ $image == "run" ]]; then
exclusive_packages="$(comm -13 <(echo "${all_build_packages}") <(echo "${all_run_packages}"))"
else
>&2 echo "Cannot get mixin labels for image $image"
exit 1
fi
additional_mixins=""
for mixin in ${mixins}; do
if [[ ${mixin} = set=* ]] || [[ ${mixin} = ${image}:set=* ]]; then
additional_mixins="${additional_mixins}\n${mixin#"${image}:"}"
fi
done
image_mixins="$(cat <<EOF
${shared_packages}
$([[ -n "${shared_packages}" ]] && echo "${shared_packages}" | sed "s/^/${image}:/")
$([[ -n "${exclusive_packages}" ]] && echo "${exclusive_packages}" | sed "s/^/${image}:/")
$([[ -n "${additional_mixins}" ]] && echo -e "${additional_mixins}")
EOF
)"
echo "${image_mixins}" | grep -v '^$' | jq -cnR '[inputs | select(length>0)]'
}
build_build_image() {
packages="$1"
dockerfile_dir="$(mktemp -d)"
cat >"${dockerfile_dir}/Dockerfile" <<-EOF
FROM ubuntu:bionic
RUN apt-get -qqy update && \
apt-get -qqy install \
build-essential \
ca-certificates \
curl \
git \
jq \
libssl1.1 \
openssl \
xz-utils \
zlib1g-dev \
${build_image_packages} \
&& rm -rf /var/lib/apt/lists/*
RUN curl -sL -o /usr/local/bin/yj https://github.com/sclevine/yj/releases/latest/download/yj-linux \
&& chmod +x /usr/local/bin/yj
EOF
docker build -t "${builder_name}-build" "${dockerfile_dir}"
}
build_run_image() {
packages="$1"
dockerfile_dir="$(mktemp -d)"
cat >"${dockerfile_dir}/Dockerfile" <<-EOF
FROM ubuntu:bionic
RUN apt-get -qqy update && \
apt-get -qqy install \
ca-certificates \
libssl1.1 \
openssl \
zlib1g \
${run_image_packages} \
&& rm -rf /var/lib/apt/lists/*
EOF
docker build -t "${builder_name}-run" "${dockerfile_dir}"
}
build_build_cnb_image() {
mixins_label="$1"
dockerfile_dir="$(mktemp -d)"
cat >"${dockerfile_dir}/Dockerfile" <<-EOF
FROM ${builder_name}-build
RUN groupadd cnb --gid 1000 && \
useradd --uid 1000 --gid 1000 -m -s /bin/bash cnb
ENV CNB_USER_ID=1000
ENV CNB_GROUP_ID=1000
ENV CNB_STACK_ID=io.buildpacks.stacks.bionic
USER 1000:1000
LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic
LABEL io.buildpacks.stack.mixins="$(echo "${mixins_label}" | sed 's/"/\\"/g')"
EOF
docker build -t "${builder_name}-build-cnb" "${dockerfile_dir}"
}
build_run_cnb_image() {
mixins_label="$1"
dockerfile_dir="$(mktemp -d)"
cat >"${dockerfile_dir}/Dockerfile" <<-EOF
FROM ${builder_name}-run
RUN groupadd cnb --gid 1000 && \
useradd --uid 1000 --gid 1000 -m -s /bin/bash cnb
USER 1000:1000
LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic
LABEL io.buildpacks.stack.mixins="$(echo "${mixins_label}" | sed 's/"/\\"/g')"
EOF
docker build -t "${builder_name}-run-cnb" "${dockerfile_dir}"
}
build_builder() {
builder_toml="$(mktemp)"
cat >"${builder_toml}" <<-EOF
description = "${builder_name}"
[stack]
id = "io.buildpacks.stacks.bionic"
run-image = "${builder_name}-run-cnb"
build-image = "${builder_name}-build-cnb"
EOF
if [[ ${#buildpackages[@]} -gt 0 ]]; then
for buildpackage in "${buildpackages[@]}"; do
if [[ -f "${buildpackage}" ]]; then
buildpackage_path="$(cd "$(dirname "${buildpackage}")" && pwd)/$(basename "${buildpackage}")"
manifest_filename="/blobs/$(tar -O -xf "${buildpackage_path}" /index.json 2>/dev/null | jq -r .manifests[0].digest | tr ':' '/')"
config_filename="/blobs/$(tar -O -xf "${buildpackage_path}" "${manifest_filename}" | jq -r .config.digest | tr ':' '/')"
metadata="$(tar -O -xf "${buildpackage_path}" "${config_filename}" | jq -r '.config.Labels."io.buildpacks.buildpackage.metadata"')"
meta_buildpack_id="$(jq -r .id <<< "${metadata}")"
cat >>"${builder_toml}" <<-EOF
[[buildpacks]]
id = "${meta_buildpack_id}"
uri = "${buildpackage_path}"
EOF
elif docker pull "${buildpackage}"; then
meta_buildpack_id="$(docker inspect "${buildpackage}" | jq -r '.[0].Config.Labels."io.buildpacks.buildpackage.metadata"' | jq -r .id)"
cat >>"${builder_toml}" <<-EOF
[[buildpacks]]
id = "${meta_buildpack_id}"
image = "${buildpackage}"
EOF
else
echo "Unknown buildpackage '${buildpackage}'"
exit 1
fi
cat >>"${builder_toml}" <<-EOF
[[order]]
[[order.group]]
id = "${meta_buildpack_id}"
EOF
done
fi
if [[ ${#buildpacks[@]} -gt 0 ]]; then
for buildpack in "${buildpacks[@]}"; do
if [[ -f "${buildpack}" ]]; then
buildpack_path="$(cd "$(dirname "${buildpack}")" && pwd)/$(basename "${buildpack}")"
buildpack_id="$(tar -O -xf "${buildpack_path}" buildpack.toml | yj -tj | jq -r .buildpack.id)"
elif curl -fsL "${buildpack}" >/dev/null 2>&1; then
buildpack_path="${buildpack}"
buildpack_id="$(curl -sL "${buildpack}" | tar -O -x buildpack.toml | yj -tj | jq -r .buildpack.id)"
else
echo "Unknown buildpack '${buildpack}'"
exit 1
fi
cat >>"${builder_toml}" <<-EOF
[[buildpacks]]
id = "${buildpack_id}"
uri = "${buildpack_path}"
[[order]]
[[order.group]]
id = "${buildpack_id}"
EOF
done
fi
pack create-builder "${builder_name}" --config "${builder_toml}"
echo "######## builder.toml:"
cat "${builder_toml}"
}
main() {
parse_opts "$@"
build_image_packages="$(get_image_packages "build")"
run_image_packages="$(get_image_packages "run")"
build_build_image "${build_image_packages}"
build_run_image "${run_image_packages}"
build_image_mixin_labels="$(get_mixin_labels "build")"
run_image_mixin_labels="$(get_mixin_labels "run")"
build_build_cnb_image "${build_image_mixin_labels}"
build_run_cnb_image "${run_image_mixin_labels}"
build_builder
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment