...

Text file src/github.com/datawire/ambassador/v2/build-aux/docker.mk

Documentation: github.com/datawire/ambassador/v2/build-aux

     1# Copyright 2019 Datawire. All rights reserved.
     2#
     3# Makefile snippet for building, tagging, and pushing Docker images.
     4#
     5## Eager inputs ##
     6#  - Variables     : docker.tag.$(GROUP)     # define %.docker.tag.$(GROUP) and %.docker.push.$(GROUP) targets
     7## Lazy inputs ##
     8#  - Target:       : $(NAME).docker          # build untagged image; define this for each image $(NAME)
     9## Outputs ##
    10#
    11#  - Variable      : HAVE_DOCKER             # non-empty if true, empty if false
    12#  - Variable      : docker.LOCALHOST        # "host.docker.internal" on Docker for Desktop, "localhost" on Docker CE
    13#
    14#  - Target        : %.docker.tag.$(GROUP)   # tag image as $(docker.tag.$(GROUP))
    15#  - Target        : %.docker.push.$(GROUP)  # push tag(s) $(docker.tag.$(GROUP))
    16#  - .PHONY Target : %.docker.clean          # remove image and tags
    17#
    18## common.mk targets ##
    19#  (none)
    20#
    21# This Makefile snippet helps you manage Docker images as files from a
    22# Makefile.  Think of it as glue.  It doesn't dictate how to build
    23# your images or what flags you pass to `docker build`--you must
    24# provide your own rule that calls `docker build`.  It doesn't dictate
    25# how your image tags are named.  It provides glue to keep track of
    26# those image, and flexibly but coherently push them to any of
    27# multiple remote Docker repositories.  All while being careful to not
    28# leave dangling images in your Docker cache that force you to run
    29# `docker image prune` an unreasonable amount.
    30#
    31# ## Building ##
    32#
    33#    For each Docker IMAGE you would like to build, you need to
    34#    provide your own build-rule for `IMAGE.docker`.  There are 2
    35#    requirements for the rule:
    36#
    37#     1. It must write a file named `IMAGE.docker` (for your value of
    38#        IMAGE) containing just the Image ID.  (This is trivially
    39#        accomplished using the --iidfile argument to `docker build`.)
    40#
    41#     2. It must only adjust the timestamp of IMAGE.docker if the
    42#        contents of the file change.  (This is trivially accomplished
    43#        using any of the `$(tools/*-ifchanged)` helper programs.)
    44#
    45#    The simplest version of that is:
    46#
    47#        IMAGE.docker: $(tools/move-ifchanged) FORCE
    48#        	docker build --iidfile=$@.tmp .
    49#        	$(tools/move-ifchanged) $@.tmp $@
    50#
    51#    If you have multiple `Dockerfile` at `IMAGE/Dockerfile`, you
    52#    might write a pattern rule:
    53#
    54#        %.docker: %/Dockerfile $(tools/move-ifchanged) FORCE
    55#        	docker build --iidfile=$@.tmp $*
    56#        	$(tools/move-ifchanged) $@.tmp $@
    57#
    58#    Unless you have a good reason to, you shouldn't concern yourself
    59#    with tagging the image in this rule.
    60#
    61#    See the "More build-rule examples" section below for more
    62#    examples.
    63#
    64# ## Tagging ##
    65#
    66#    You can tag an image after being built by depending on
    67#    `IMAGE.docker.tag.GROUP`, where you've set up GROUP by writing
    68#
    69#        docker.tag.GROUP = EXPR
    70#
    71#    _before_ including `docker.mk`, where GROUP is the suffix of the
    72#    target that you'd like to depend on in your Makefile, and EXPR is
    73#    a Makefile expression that evaluates to one-or-more tag names; it
    74#    is evaluated in the context of `IMAGE.docker.tag.GROUP`;
    75#    specifically:
    76#
    77#     * `$*` is set to IMAGE
    78#     * `$<` is set to a file containing the image ID
    79#
    80#    Additionally, you can override the EXPR on a per-image basis
    81#    by overriding the `docker.tag.GROUP` variable on a per-target
    82#    basis:
    83#
    84#        IMAGE.docker.tag.GROUP: docker.tag.GROUP = EXPR
    85#
    86#     > For example:
    87#     >
    88#     >   For the mast part, the Ambassador Pro images are
    89#     >    - built as  : `docker/$(NAME).docker`
    90#     >    - built from: `docker/$(NAME)/Dockerfile`
    91#     >    - pushed as : `docker.io/datawire/ambassador_pro:$(NAME)-$(VERSION)`
    92#     >   However, as an exception, the Ambassador Core image is
    93#     >    - built as  : `ambassador/ambassador.docker`
    94#     >    - pushed as : `docker.io/datawire/ambassador_pro:amb-core-$(VERSION)`
    95#     >
    96#     >   Additionally, we want to be able to push to a private
    97#     >   in-cluster registry for testing before we do a release.  The
    98#     >   tag names pushed to the cluster should be based on the image
    99#     >   ID, so that we don't need to configure a funny
   100#     >   ImagePullPolicy during testing.
   101#     >
   102#     >   We accomplish this by saying:
   103#     >
   104#     >       docker.tag.release = docker.io/datawire/ambassador_pro:$(notdir $*)-$(VERSION)
   105#     >       include build-aux/docker-cluster.mk # docker-cluster.mk sets the `docker.tag.cluster` variable
   106#     >       include build-aux/docker.mk
   107#     >       # The above will cause docker.mk to define targets:
   108#     >       #  - %.docker.tag.release
   109#     >       #  - %.docker.push.release
   110#     >       #  - %.docker.tag.cluster
   111#     >       #  - %.docker.push.cluster
   112#     >
   113#     >       # Override the release name a specific image.
   114#     >       # Release ambassador/ambassador.docker
   115#     >       #  - based on the above    : docker.io/datawire/ambassador_pro:ambassador-$(VERSION)
   116#     >       #  - after being overridden: docker.io/datawire/ambassador_pro:amb-core-$(VERSION)
   117#     >       ambassador/ambassador.docker.tag.release: docker.tag.release = docker.io/datawire/ambassador_pro:amb-core-$(VERSION)
   118#     >
   119#     >   and having our
   120#     >    - `build` target depend on `NAME.docker.tag.release` (for each NAME).
   121#
   122# ## Pushing ##
   123#
   124#    Pushing a tag: You can push tags that have been created with
   125#    `IMAGE.docker.tag.GROUP` (see above) by depending on
   126#    `IMAGE.docker.push.GROUP`.
   127#
   128#     > For example:
   129#     >
   130#     >   Based on the above Ambassador Pro example in the "Tagging"
   131#     >   section, we have our
   132#     >    - `check` target depend on `NAME.docker.push.cluster` (for each NAME).
   133#     >    - `release` target depend on `NAME.docker.push.release` (for each NAME).
   134#
   135# ## Cleaning ##
   136#
   137#     - Clean up: You can untag (if there are any tags) and remove an
   138#       image by having your `clean` target depend on
   139#       `SOMEPATH.docker.clean`.  Because docker.mk does not have a
   140#       listing of all the images you may ask it to build, these are
   141#       NOT automatically added to the common.mk 'clean' target, and
   142#       you MUST do that yourself.
   143#
   144# ## More build-rule examples ##
   145#
   146#     1. You might want to specify `docker build` arguments, like
   147#        `--build-arg=` or `-f`:
   148#
   149#            # Set a custom --build-arg, and use a funny Dockerfile name
   150#            myimage.docker: $(tools/move-ifchanged) FORCE
   151#            	docker build --iidfile=$(@D)/.tmp.$(@F).tmp --build-arg=FOO=BAR -f Dockerfile.myimage .
   152#            	$(tools/move-ifchanged) $(@D)/.tmp.$(@F).tmp $@
   153#
   154#     2. In `ambassador.git`, building the Envoy binary is slow, so we
   155#        might want to try pulling it from a build-cache Docker
   156#        repository, instead of building it locally:
   157#
   158#            # Building this is expensive, so try grabbing a cached
   159#            # version before trying to build it.  This goes ahead and
   160#            # tags the image, for caching purposes.
   161#            base-envoy.docker: $(tools/write-ifchanged) $(var.)BASE_ENVOY_IMAGE_CACHE
   162#            	if ! docker run --rm --entrypoint=true $(BASE_ENVOY_IMAGE_CACHE); then \
   163#            		$(MAKE) envoy-bin/envoy-static-stripped
   164#            		docker build -t $(BASE_ENVOY_IMAGE_CACHE) -f Dockerfile.base-envoy; \
   165#            	fi
   166#            	docker image inspect $(BASE_ENVOY_IMAGE_CACHE) --format='{{.Id}}' | $(tools/write-ifchanged) $@
   167#
   168#     3. In `apro.git`, we have many Docker images to build; each with
   169#        a Dockerfile at `docker/NAME/Dockerfile`.  We accomplish this
   170#        with a simple pattern rule only slightly more complex than
   171#        the one given in the "Building" section:
   172#
   173#            %.docker: %/Dockerfile $(tools/move-ifchanged) FORCE
   174#            # Try with --pull, fall back to without --pull
   175#            	docker build --iidfile=$(@D)/.tmp.$(@F).tmp --pull $* || docker build --iidfile=$(@D)/.tmp.$(@F).tmp $*
   176#            	$(tools/move-ifchanged) $(@D)/.tmp.$(@F).tmp $@
   177#
   178#        The `--pull` is a good way to ensure that we incorporate any
   179#        patches to the base images that we build ours FROM.  However,
   180#        sometimes `--pull` doesn't work, because in at least one case
   181#        the the Dockerfile refers to a local image ID hash from a
   182#        previously built image; trying to pull that ID hash will
   183#        fail.
   184#
   185#        For many of these images, we have have Makefile-built
   186#        artifacts that we would like to include in the image.  We
   187#        accomplish this by simply declaring dependencies off of the
   188#        `docker/NAME.docker` files, and writing rules to copy the
   189#        artifacts in to the `docker/NAME/` directory:
   190#
   191#            # In this example, the `docker/app-sidecar/Dockerfile` image
   192#            # needs an already-compiled `ambex` binary.
   193#
   194#            # Declare the dependency...
   195#            docker/app-sidecar.docker: docker/app-sidecar/ambex
   196#
   197#            # ... and copy it in to the Docker context
   198#            docker/app-sidecar/ambex: bin_linux_amd64/ambex
   199#            	cp $< $@
   200
   201ifeq ($(words $(filter $(abspath $(lastword $(MAKEFILE_LIST))),$(abspath $(MAKEFILE_LIST)))),1)
   202_docker.mk := $(lastword $(MAKEFILE_LIST))
   203include $(dir $(_docker.mk))prelude.mk
   204
   205#
   206# Inputs
   207
   208_docker.tag.groups = $(patsubst docker.tag.%,%,$(filter docker.tag.%,$(.VARIABLES)))
   209# clean.groups is separate from tag.groups as a special-case for docker-cluster.mk
   210_docker.clean.groups += $(_docker.tag.groups)
   211
   212#
   213# Output variables
   214
   215HAVE_DOCKER      = $(call lazyonce,HAVE_DOCKER,$(shell which docker 2>/dev/null))
   216docker.LOCALHOST = $(if $(filter darwin,$(GOHOSTOS)),host.docker.internal,localhost)
   217
   218#
   219# Output targets
   220
   221%.docker.clean: $(addprefix %.docker.clean.,$(_docker.clean.groups))
   222	if [ -e $*.docker ]; then docker image rm "$$(cat $*.docker)" || true; fi
   223	rm -f $*.docker $(*D)/.$(*F).docker.stamp
   224.PHONY: %.docker.clean
   225
   226# Evaluate _docker.tag.rule with _docker.tag.group=TAG_GROUPNAME for
   227# each docker.tag.TAG_GROUPNAME variable.
   228#
   229# Add a set of %.docker.tag.TAG_GROUPNAME and
   230# %.docker.push.TAG_GROUPNAME targets that tag and push the docker image.
   231define _docker.tag.rule
   232  # file contents:
   233  #   line 1: image ID
   234  #   line 2: tag 1
   235  #   line 3: tag 2
   236  #   ...
   237  %.docker.tag.$(_docker.tag.group): %.docker $$(tools/write-dockertagfile) FORCE
   238  # The 'foreach' is to handle newlines as normal whitespace
   239	printf '%s\n' $$$$(cat $$<) $$(foreach v,$$(docker.tag.$(_docker.tag.group)),$$v) | $$(tools/write-dockertagfile) $$@
   240
   241  # file contents:
   242  #   line 1: image ID
   243  #   line 2: tag 1
   244  #   line 3: tag 2
   245  #   ...
   246  %.docker.push.$(_docker.tag.group): %.docker.tag.$(_docker.tag.group) FORCE
   247	@set -e; { \
   248	  if cmp -s $$< $$@; then \
   249	    printf "$${CYN}==> $${GRN}Already pushed $${BLU}$$$$(sed -n 2p $$@)$${END}\n"; \
   250	  else \
   251	    printf "$${CYN}==> $${GRN}Pushing $${BLU}$$$$(sed -n 2p $$<)$${GRN}...$${END}\n"; \
   252	    sed 1d $$< | xargs -n1 docker push; \
   253	    cat $$< > $$@; \
   254	  fi; \
   255	}
   256
   257  %.docker.clean.$(_docker.tag.group):
   258	if [ -e $$*.docker.tag.$(_docker.tag.group) ]; then docker image rm -- $$$$(sed 1d $$*.docker.tag.$(_docker.tag.group)) || true; fi
   259	rm -f $$*.docker.tag.$(_docker.tag.group) $$*.docker.push.$(_docker.tag.group)
   260  .PHONY: %.docker.clean.$(_docker.tag.group)
   261endef
   262$(foreach _docker.tag.group,$(_docker.tag.groups),$(eval $(_docker.tag.rule)))
   263
   264endif

View as plain text