summaryrefslogtreecommitdiffhomepage
path: root/tools/images.mk
blob: 2003da5bd22396043fbdfef8dc35e83bc60e0f8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/make -f

# Copyright 2018 The gVisor Authors.
#
# 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.

##
## Docker image targets.
##
##   Images used by the tests must also be built and available locally.
##   The canonical test targets defined below will automatically load
##   relevant images. These can be loaded or built manually via these
##   targets.
##
##   (*) Note that you may provide an ARCH parameter in order to build
##   and load images from an alternate archiecture (using qemu). When
##   bazel is run as a server, this has the effect of running an full
##   cross-architecture chain, and can produce cross-compiled binaries.
##

# ARCH is the architecture used for the build. This may be overriden at the
# command line in order to perform a cross-build (in a limited capacity).
ARCH := $(shell uname -m)
ifneq ($(ARCH),$(shell uname -m))
DOCKER_PLATFORM_ARGS := --platform=$(ARCH)
else
DOCKER_PLATFORM_ARGS :=
endif

# Note that the image prefixes used here must match the image mangling in
# runsc/testutil.MangleImage. Names are mangled in this way to ensure that all
# tests are using locally-defined images (that are consistent and idempotent).
REMOTE_IMAGE_PREFIX ?= gcr.io/gvisor-presubmit
LOCAL_IMAGE_PREFIX ?= gvisor.dev/images
ALL_IMAGES   := $(subst /,_,$(subst images/,,$(shell find images/ -name Dockerfile -o -name Dockerfile.$(ARCH) | xargs -n 1 dirname | uniq)))
SUB_IMAGES   := $(foreach image,$(ALL_IMAGES),$(if $(findstring _,$(image)),$(image),))
IMAGE_GROUPS := $(sort $(foreach image,$(SUB_IMAGES),$(firstword $(subst _, ,$(image)))))

define expand_group =
load-$(1): $$(patsubst $(1)_%, load-$(1)_%, $$(filter $(1)_%,$$(ALL_IMAGES)))
	@
.PHONY: load-$(1)
push-$(1): $$(patsubst $(1)_%, push-$(1)_%, $$(filter $(1)_%,$$(ALL_IMAGES)))
	@
.PHONY: push-$(1)
endef
$(foreach group,$(IMAGE_GROUPS),$(eval $(call expand_group,$(group))))

list-all-images: ## List all images.
	@for image in $(ALL_IMAGES); do echo $${image}; done
.PHONY: list-all-images

load-all-images: ## Load all images.
load-all-images: $(patsubst %,load-%,$(ALL_IMAGES))
.PHONY: load-all-images

push-all-images: ## Push all images.
push-all-images: $(patsubst %,push-%,$(ALL_IMAGES))
.PHONY: push-all-images

# path and dockerfile are used to extract the relevant path and dockerfile
# (depending on what's available for the given architecture).
path = images/$(subst _,/,$(1))
dockerfile = $$(if [ -f "$(call path,$(1))/Dockerfile.$(ARCH)" ]; then echo Dockerfile.$(ARCH); else echo Dockerfile; fi)

# The tag construct is used to memoize the image generated (see README.md).
# This scheme is used to enable aggressive caching in a central repository, but
# ensuring that images will always be sourced using the local files.
tag = $(shell cd images && find $(subst _,/,$(1)) -type f | sort | xargs -n 1 sha256sum | sha256sum - | cut -c 1-16)
remote_image = $(REMOTE_IMAGE_PREFIX)/$(subst _,/,$(1))_$(ARCH)
local_image = $(LOCAL_IMAGE_PREFIX)/$(subst _,/,$(1))

# Include all existing images as targets here.
#
# Note that we use a _ for the tag separator, instead of :, as the latter is
# interpreted by Make, unfortunately. tag_expand expands the generic rules to
# tag-specific targets. These is needed to provide sensible targets for load
# below, with caching. Basically, if there is a rule generated here, then the
# load will be skipped. If there is no load generated here, then the default
# rule for load will kick in.
#
# Note that if this rule does not successfully rule, we will simply have
# additional Docker pull commands that run for all images that are already
# pulled. No real harm done.
EXISTING_IMAGES = $(shell docker images --format '{{.Repository}}_{{.Tag}}' | grep -v '<none>')
define existing_image_rule =
loaded0_$(1)=load-$$(1): tag-$$(1) # Already available.
loaded1_$(1)=.PHONY: load-$$(1)
endef
$(foreach image, $(EXISTING_IMAGES), $(eval $(call existing_image_rule,$(image))))
define tag_expand_rule =
$(eval $(loaded0_$(call remote_image,$(1))_$(call tag,$(1))))
$(eval $(loaded1_$(call remote_image,$(1))_$(call tag,$(1))))
endef
$(foreach image, $(ALL_IMAGES), $(eval $(call tag_expand_rule,$(image))))

# tag tags a local image. This applies both the hash-based tag from above to
# ensure that caching works as expected, as well as the "latest" tag that is
# used by the tests.
local_tag = \
  docker tag $(call remote_image,$(1)):$(call tag,$(1)) $(call local_image,$(1)):$(call tag,$(1)) >&2
latest_tag = \
  docker tag $(call local_image,$(1)):$(call tag,$(1)) $(call local_image,$(1)) >&2
tag-%: ## Tag a local image.
	@$(call header,TAG $*)
	@$(call local_tag,$*) && $(call latest_tag,$*)

# pull forces the image to be pulled.
pull = \
  $(call header,PULL $(1)) && \
  docker pull $(DOCKER_PLATFORM_ARGS) $(call remote_image,$(1)):$(call tag,$(1)) >&2 && \
  $(call local_tag,$(1)) && \
  $(call latest_tag,$(1))
pull-%: register-cross ## Force a repull of the image.
	@$(call pull,$*)

# rebuild builds the image locally. Only the "remote" tag will be applied. Note
# we need to explicitly repull the base layer in order to ensure that the
# architecture is correct. Note that we use the term "rebuild" here to avoid
# conflicting with the bazel "build" terminology, which is used elsewhere.
rebuild = \
  $(call header,REBUILD $(1)) && \
  (T=$$(mktemp -d) && cp -a $(call path,$(1))/* $$T && \
  $(foreach image,$(shell grep FROM "$(call path,$(1))/$(call dockerfile,$(1))" 2>/dev/null | cut -d' ' -f2),docker pull $(DOCKER_PLATFORM_ARGS) $(image) >&2 &&) \
  docker build $(DOCKER_PLATFORM_ARGS) \
    -f "$$T/$(call dockerfile,$(1))" \
    -t "$(call remote_image,$(1)):$(call tag,$(1))" \
    $$T >&2 && \
  rm -rf $$T) && \
  $(call local_tag,$(1)) && \
  $(call latest_tag,$(1))
rebuild-%: register-cross ## Force rebuild an image locally.
	@$(call rebuild,$*)

# load will either pull the "remote" or build it locally. This is the preferred
# entrypoint, as it should never fail. The local tag should always be set after
# this returns (either by the pull or the build).
load-%: register-cross ## Pull or build an image locally.
	@($(call pull,$*)) || ($(call rebuild,$*))

# push pushes the remote image, after either pulling (to validate that the tag
# already exists) or building manually. Note that this generic rule will match
# the fully-expanded remote image tag.
push-%: load-% ## Push a given image.
	@docker push $(call remote_image,$*):$(call tag,$*) >&2

# register-cross registers the necessary qemu binaries for cross-compilation.
# This may be used by any target that may execute containers that are not the
# native format. Note that this will only apply on the first execution.
register-cross:
ifneq ($(ARCH),$(shell uname -m))
ifeq (,$(wildcard /proc/sys/fs/binfmt_misc/qemu-*))
	@docker run --rm --privileged multiarch/qemu-user-static --reset --persistent yes >&2
else
	@
endif
else
	@
endif