#!/bin/bash # Clones the NetBox repository with git from Github and builds the Dockerfile echo "▶️ $0 $*" set -e if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then cat < [--push|--push-only] branch The branch or tag to build. Required. --push Pushes the built Docker image to the registry. --push-only Only pushes the Docker image to the registry, but does not build it. You can use the following ENV variables to customize the build: SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). Default: netbox-community SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). Default: netbox URL Where to fetch the code from. Must be a git repository. Can be private. Default: https://github.com/\${SRC_ORG}/\${SRC_REPO}.git NETBOX_PATH The path where netbox will be checkout out. Must not be outside of the netbox-docker repository (because of Docker)! Default: .netbox SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered. This may be useful, if you are manually managing the NETBOX_PATH. Default: undefined TAG The version part of the docker tag. Default: When =master: latest When =develop: snapshot Else: same as DOCKER_REGISTRY The Docker repository's registry (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'') Used for tagging the image. Default: docker.io DOCKER_ORG The Docker repository's organisation (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'') Used for tagging the image. Default: netboxcommunity DOCKER_REPO The Docker repository's name (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'') Used for tagging the image. Default: netbox DOCKER_TAG The name of the tag which is applied to the image. Useful for pushing into another registry than hub.docker.com. Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG} DOCKER_SHORT_TAG The name of the short tag which is applied to the image. This is used to tag all patch releases to their containing version e.g. v2.5.1 -> v2.5 Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:. DOCKERFILE The name of Dockerfile to use. Default: Dockerfile DOCKER_FROM The base image to use. Default: 'alpine:3.14' DOCKER_TARGET A specific target to build. It's currently not possible to pass multiple targets. Default: main ldap BUILDX_PLATFORMS Specifies the platform(s) to build the image for. Example: linux/amd64,linux/arm64 Default: linux/amd64 BUILDX_BUILDER_NAME If defined, the image build will be assigned to the given builder. If you specify this variable, make sure that the builder exists. If this value is not defined, a new builx builder with the directory name of the current directory (i.e. '$(basename "${PWD}")') is created. Example: clever_lovelace Default: undefined BUILDX_KEEP_BUILDER If defined and if BUILDX_BUILDER_NAME is undefined, then the buildx builder created by this script is not removed. This is useful if you want to re-use the builder in a later build on the same system. By default, all buildx builders created by this script are removed at the end. Default: undefined HTTP_PROXY The proxy to use for http requests. Example: http://proxy.domain.tld:3128 Default: undefined NO_PROXY Comma-separated list of domain extensions proxy should not be used for. Example: .domain1.tld,.domain2.tld Default: undefined DEBUG If defined, the script does not stop when certain checks are unsatisfied. Default: undefined DRY_RUN Prints all build statements instead of running them. Default: undefined GH_ACTION If defined, special 'echo' statements are enabled that set the following environment variables in Github Actions: - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable Default: undefined Examples: ${0} master This will fetch the latest 'master' branch, build a Docker Image and tag it 'netboxcommunity/netbox:latest'. ${0} develop This will fetch the latest 'develop' branch, build a Docker Image and tag it 'netboxcommunity/netbox:snapshot'. ${0} v2.6.6 This will fetch the 'v2.6.6' tag, build a Docker Image and tag it 'netboxcommunity/netbox:v2.6.6' and 'netboxcommunity/netbox:v2.6'. ${0} develop-2.7 This will fetch the 'develop-2.7' branch, build a Docker Image and tag it 'netboxcommunity/netbox:develop-2.7'. SRC_ORG=cimnine ${0} feature-x This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'. SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, build a Docker Image and tag it 'cimnine/netbox:feature-x'. PLATFORMS=linux/amd64,linux/arm64 ${0} master This will fetch the latest 'master' branch, build a Docker Image and tag it 'netboxcommunity/netbox:latest'. It will produce an ARM64 and an AMD64 version of the image. END_OF_DOCS if [ "${1}x" == "x" ]; then exit 1 else exit 0 fi fi ### # Enabling dry-run mode ### if [ -z "${DRY_RUN}" ]; then DRY="" else echo "⚠️ DRY_RUN MODE ON ⚠️" DRY="echo" fi ### # Variables for fetching the NetBox source ### SRC_ORG="${SRC_ORG-netbox-community}" SRC_REPO="${SRC_REPO-netbox}" NETBOX_BRANCH="${1}" URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}" NETBOX_PATH="${NETBOX_PATH-.netbox}" ### # Fetching the NetBox source ### if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l) if [ "${REMOTE_EXISTS}" == "0" ]; then echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do" if [ -n "${GH_ACTION}" ]; then echo "::set-output name=skipped::true" fi exit 0 fi echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'" if [ ! -d "${NETBOX_PATH}" ]; then $DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}" fi ( $DRY cd "${NETBOX_PATH}" # shellcheck disable=SC2030 if [ -n "${HTTP_PROXY}" ]; then git config http.proxy "${HTTP_PROXY}" fi $DRY git remote set-url origin "${URL}" $DRY git fetch -qp --depth 10 origin "${NETBOX_BRANCH}" $DRY git checkout -qf FETCH_HEAD $DRY git prune ) echo "✅ Checked out NetBox" fi ### # Determining the value for DOCKERFILE # and checking whether it exists ### DOCKERFILE="${DOCKERFILE-Dockerfile}" if [ ! -f "${DOCKERFILE}" ]; then echo "🚨 The Dockerfile ${DOCKERFILE} doesn't exist." if [ -z "${DEBUG}" ]; then exit 1 else echo "⚠️ Would exit here with code '1', but DEBUG is enabled." fi fi ### # Determining the value for DOCKER_FROM ### if [ -z "$DOCKER_FROM" ]; then DOCKER_FROM="alpine:3.14" fi ### # Variables for labelling the docker image ### BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')" if [ -d ".git" ]; then GIT_REF="$(git rev-parse HEAD)" fi # Read the project version from the `VERSION` file and trim it, see https://stackoverflow.com/a/3232433/172132 PROJECT_VERSION="${PROJECT_VERSION-$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' VERSION)}" # Get the Git information from the netbox directory if [ -d "${NETBOX_PATH}/.git" ]; then NETBOX_GIT_REF=$( cd "${NETBOX_PATH}" git rev-parse HEAD ) NETBOX_GIT_BRANCH=$( cd "${NETBOX_PATH}" git rev-parse --abbrev-ref HEAD ) NETBOX_GIT_URL=$( cd "${NETBOX_PATH}" git remote get-url origin ) fi ### # Variables for tagging the docker image ### DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}" DOCKER_ORG="${DOCKER_ORG-netboxcommunity}" DOCKER_REPO="${DOCKER_REPO-netbox}" case "${NETBOX_BRANCH}" in master) TAG="${TAG-latest}" ;; develop) TAG="${TAG-snapshot}" ;; *) TAG="${TAG-$NETBOX_BRANCH}" ;; esac ### # Determine targets to build ### DEFAULT_DOCKER_TARGETS=("main" "ldap") DOCKER_TARGETS=("${DOCKER_TARGET:-"${DEFAULT_DOCKER_TARGETS[@]}"}") echo "🏭 Building the following targets:" "${DOCKER_TARGETS[@]}" ### # Build each target ### export DOCKER_BUILDKIT=${DOCKER_BUILDKIT-1} for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do echo "🏗 Building the target '${DOCKER_TARGET}'" ### # composing the final TARGET_DOCKER_TAG ### TARGET_DOCKER_TAG="${DOCKER_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:${TAG}}" if [ "${DOCKER_TARGET}" != "main" ]; then TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}" fi if [ -n "${GH_ACTION}" ]; then echo "FINAL_DOCKER_TAG=${TARGET_DOCKER_TAG}" >>"$GITHUB_ENV" echo "::set-output name=skipped::false" fi ### # composing the additional DOCKER_SHORT_TAG, # i.e. "v2.6.1" becomes "v2.6", # which is only relevant for version tags # Also let "latest" follow the highest version ### if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:v${MAJOR}.${MINOR}}" TARGET_DOCKER_LATEST_TAG="${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:latest" if [ "${DOCKER_TARGET}" != "main" ]; then TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}" TARGET_DOCKER_LATEST_TAG="${TARGET_DOCKER_LATEST_TAG}-${DOCKER_TARGET}" fi fi ### # If `--push-only` is passed, just push and then quit ### if [ "${2}" == "--push-only" ]; then source ./build-functions/docker-functions.sh push_image_to_registry "${TARGET_DOCKER_TAG}" if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then push_image_to_registry "${TARGET_DOCKER_SHORT_TAG}" push_image_to_registry "${TARGET_DOCKER_LATEST_TAG}" fi exit 1 fi ### # Checking if the build is necessary, # meaning build only if one of those values changed: # - Python base image digest (Label: PYTHON_BASE_DIGEST) # - netbox git ref (Label: NETBOX_GIT_REF) # - netbox-docker git ref (Label: org.label-schema.vcs-ref) ### # Load information from registry (only for docker.io) SHOULD_BUILD="false" BUILD_REASON="" if [ -z "${GH_ACTION}" ]; then # Asuming non Github builds should always proceed SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} interactive" elif [ "$DOCKER_REGISTRY" = "docker.io" ]; then source ./build-functions/get-public-image-config.sh IFS=':' read -ra DOCKER_FROM_SPLIT <<<"${DOCKER_FROM}" if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then # Need to use "library/..." for images the have no two part name DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}" fi PYTHON_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}") mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}") NETBOX_GIT_REF_OLD=$(get_image_label NETBOX_GIT_REF "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}") GIT_REF_OLD=$(get_image_label org.label-schema.vcs-ref "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}") if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} alpine" fi if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} netbox" fi if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} netbox-docker" fi else SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} no-check" fi ### # Building the docker image ### if [ "${SHOULD_BUILD}" != "true" ]; then echo "Build skipped because sources didn't change" echo "::set-output name=skipped::true" else ### # Composing all arguments for `docker build` ### DOCKER_BUILD_ARGS=( --pull --output=type=image --target "${DOCKER_TARGET}" -f "${DOCKERFILE}" -t "${TARGET_DOCKER_TAG}" ) if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_SHORT_TAG}") DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_LATEST_TAG}") fi # --label DOCKER_BUILD_ARGS+=( --label "ORIGINAL_TAG=${TARGET_DOCKER_TAG}" --label "org.label-schema.build-date=${BUILD_DATE}" --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.label-schema.version=${PROJECT_VERSION}" --label "org.opencontainers.image.version=${PROJECT_VERSION}" ) if [ -d ".git" ]; then DOCKER_BUILD_ARGS+=( --label "org.label-schema.vcs-ref=${GIT_REF}" --label "org.opencontainers.image.revision=${GIT_REF}" ) fi if [ -d "${NETBOX_PATH}/.git" ]; then DOCKER_BUILD_ARGS+=( --label "NETBOX_GIT_BRANCH=${NETBOX_GIT_BRANCH}" --label "NETBOX_GIT_REF=${NETBOX_GIT_REF}" --label "NETBOX_GIT_URL=${NETBOX_GIT_URL}" ) fi if [ -n "${BUILD_REASON}" ]; then BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON") DOCKER_BUILD_ARGS+=(--label "BUILD_REASON=${BUILD_REASON}") fi # --build-arg DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}") if [ -n "${DOCKER_FROM}" ]; then DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}") fi # shellcheck disable=SC2031 if [ -n "${HTTP_PROXY}" ]; then DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}") DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}") fi if [ -n "${NO_PROXY}" ]; then DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}") fi # --platform DOCKER_BUILD_ARGS+=(--platform "${BUILDX_PLATFORMS-linux/amd64}") # --cache-from / --cache-to # DOCKER_BUILD_ARGS+=("--cache-from=type=registry,ref=${TARGET_DOCKER_TAG}-cache,mode=max") # DOCKER_BUILD_ARGS+=("--cache-to=type=registry,ref=${TARGET_DOCKER_TAG}-cache,mode=max") ### # Pushing the docker images if `--push` is passed ### if [ "${2}" == "--push" ]; then DOCKER_BUILD_ARGS+=(--push) fi if [ -z "${BUILDX_BUILDER_NAME}" ]; then BUILDX_BUILDER_NAME="$(basename "${PWD}")" if ! docker buildx ls | grep --quiet --word-regexp "${BUILDX_BUILDER_NAME}"; then echo "👷 Creating new Buildx Builder '${BUILDX_BUILDER_NAME}'" $DRY docker buildx create --name "${BUILDX_BUILDER_NAME}" BUILDX_BUILDER_CREATED="yes" fi fi echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}' on '${BUILDX_BUILDER_NAME}'." echo " Build reason set to: ${BUILD_REASON}" $DRY docker buildx \ --builder "${BUILDX_BUILDER_NAME}" \ build \ "${DOCKER_BUILD_ARGS[@]}" \ . echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'" echo "🔎 Inspecting labels on '${TARGET_DOCKER_TAG}'" $DRY docker inspect "${TARGET_DOCKER_TAG}" --format "{{json .Config.Labels}}" if [ -z "${BUILDX_KEEP_BUILDER}" ] && [ "${BUILDX_BUILDER_CREATED}" == "yes" ]; then echo "👷 Removing Buildx Builder '${BUILDX_BUILDER_NAME}'" $DRY docker buildx rm "${BUILDX_BUILDER_NAME}" fi fi done