# Simple Makefile to build base VM images using packer and ansible
# TODO
# * support ssh as user instead of root
# * testing target
PACKER ?= packer
ANSIBLE_DIR ?= ansible-roles
HASHER ?= sha256sum
# check which hypervisors are available
ifndef BUILDER
ifeq ($(shell which qemu-system-$(shell uname -m | sed 's/i686/i386/') 2>&1 > /dev/null && echo $$?), 0)
AVAILABLE_BUILDERS += qemu
endif
ifeq ($(shell which virtualbox 2>&1 > /dev/null && echo $$?), 0)
AVAILABLE_BUILDERS += virtualbox-iso
endif
ifeq ($(shell which vmplayer 2>&1 > /dev/null && echo $$?), 0)
AVAILABLE_BUILDERS += vmware-iso
endif
BUILDER := $(firstword $(AVAILABLE_BUILDERS))
endif
# The packer templates, detected as *.json (excluding base.json)
TEMPLATES := $(basename $(filter-out base.json,$(wildcard *.json)))
# The provisioning flavors, detected as ansible-roles/setup-<flavor>.yml
FLAVORS := $(patsubst $(ANSIBLE_DIR)/setup-%.yml,%, $(wildcard $(ANSIBLE_DIR)/setup-*.yml))
BASETARGETS := $(foreach template, $(TEMPLATES), $(template)/base)
PROVTARGETS := $(foreach template, $(TEMPLATES), $(foreach flavor, $(FLAVORS), $(template)/$(flavor)))
BOOTTARGETS := $(foreach template, $(TEMPLATES), $(template)/base/boot)
BOOTTARGETS += $(foreach prov, $(PROVTARGETS), $(prov)/boot)
PACKER_OPTS += -var-file=base.json
ifdef DEBUG
VERBOSE := 1
PACKER_OPTS += -debug
endif
ifdef WINDOW
PACKER_OPTS += -var='headless=false'
endif
# We support parallel Provisioning packer builds.
# To ensure data consistency we save the used ansible roles before executing in
# its own environment, tagged by the current date & time.
TIMESTAMP := $(shell date "+%Y-%m-%d_%H-%M-%S")
# The ROOTPW is only needed for the base, boot and provisioning targets.
# In every other case it must not be checked as it should be possible to call
# help or cleanup without the need of defining the password.
PW_NEEDED := $(filter $(strip $(PROVTARGETS) $(BOOTTARGETS)),$(MAKECMDGOALS))
ifneq ($(PW_NEEDED),)
ifeq ($(strip $(ROOTPW)),)
$(error No root password is set, set it as ROOTPW in your environment.)
else
$(foreach cur,$(PW_NEEDED),\
$(shell printf "$(ROOTPW)" | $(HASHER) --check --status "$(firstword $(subst /, ,$(cur)))/base/rootpw.$(HASHER)")\
$(if $(filter 1,$(.SHELLSTATUS)),\
$(error The wrong ROOTPW is set. Please correct it)))
endif
endif
ifdef VERBOSE
$(info root password: $(ROOTPW))
$(info hasher: $(HASHER))
$(info )
$(info timestamp: $(TIMESTAMP))
$(info )
$(info packer: executable: $(PACKER))
$(info packer: options: $(PACKER_OPTS))
$(info )
$(info ansible: directory: $(ANSIBLE_DIR))
$(info )
$(info builder: available: $(AVAILABLE_BUILDERS))
$(info builder: chosen: $(BUILDER))
$(info )
$(info targets: base: $(strip $(BASETARGETS)))
$(info targets: boot: $(strip $(BOOTTARGETS)))
$(info targets: provision: $(strip $(PROVTARGETS)))
$(info )
endif
.PHONY: help clean_except_last clean_bases clean_all
help:
# Creating base images
$(BASETARGETS):
$(info ** Building template '$(@D)' using '$(BUILDER)' **)
$(PACKER) build -only=$(BUILDER) \
$(PACKER_OPTS) \
-var='vm_name=rootfs-image' \
-var='output_directory=$(@D)/base' \
$(@D).json
@echo "$(ROOTPW)" | $(HASHER) > $(@D)/base/rootpw.$(HASHER)
# Provisioning images
$(PROVTARGETS):
$(foreach flav, $(FLAVORS), %/$(flav)): %/base
$(eval BUILD_DIR := $(@D)/$(@F).$(TIMESTAMP))
$(info ** Provisioning '$(@D)' with '$(@F)' **)
@mkdir -p $(BUILD_DIR)
@cp -r $(ANSIBLE_DIR) $(BUILD_DIR)/$(ANSIBLE_DIR)
@ln -sfn $(@F).$(TIMESTAMP) $(@D)/$(@F).latest
$(PACKER) build -only=$(BUILDER) \
$(PACKER_OPTS) \
-var='vm_name=rootfs-image' \
-var='output_directory=$(BUILD_DIR)/build' \
-var='base_image=$(@D)/base/rootfs-image' \
-var='playbook=setup-$(@F).yml' \
$(BUILD_DIR)/$(ANSIBLE_DIR)/run-playbook-only.json
# Generating boot files
$(BOOTTARGETS):
%/boot: %
$(eval BUILD_DIR := $(@D).$(TIMESTAMP))
$(info ** Generating boot files for '$(BUILD_DIR)')
$(PACKER) build -only=$(BUILDER) \
$(PACKER_OPTS) \
-var='vm_name=rootfs-image.tmp' \
-var='output_directory=$(BUILD_DIR)/build/tmp' \
-var='base_image=$(BUILD_DIR)/build/rootfs-image' \
-var='playbook=build-dracut-initramfs.yml' \
$(BUILD_DIR)/$(ANSIBLE_DIR)/run-playbook-only.json
@mv $(BUILD_DIR)/$(ANSIBLE_DIR)/boot_files $(BUILD_DIR)/build/boot
$(if $(DEBUG),,@rm -rf $(BUILD_DIR)/build/tmp)
clean_except_last:
@-$(foreach template,$(TEMPLATES),\
$(foreach flavor,$(FLAVORS),\
test -d $(template) && \
find $(template)/* -maxdepth 0 -type d \
-not -name base \
-not -wholename $(template)/$$(readlink $(template)/$(flavor).latest) \
-print0 | xargs -0 rm -rf \
);\
)
clean_bases:
@-$(foreach template,$(TEMPLATES),\
test -d $(template) && rm -rf $(template)/base;\
)
clean_all:
@-$(foreach template,$(TEMPLATES),\
test -d $(template) && rm -rf $(template);\
)
help:
@printf "Usage:\n\tmake <template>/<flavor>[/boot]\n"
@echo
@echo "Base images targets:"
@for T in $(BASETARGETS); do printf "\t%s\n" "$$T"; done
@echo
@echo "Provisioning images targets: "
@for P in $(PROVTARGETS); do printf "\t%s\n" "$$P"; done
@echo
@echo "Generate boot files targets:"
@printf "\t<{base,provisioning}_target>/boot\n"
@echo
@echo "Available options are:"
@printf "\tANSIBLE_DIR: Set directory with ansible roles (def: ansible-roles)\n"
@printf "\tBUILDER: Set a builder, do not autodetect\n"
@printf "\tDEBUG: Enable debug mode in packer (includes VERBOSE)\n"
@printf "\tHASHER: Set wanted hasher (def: sha256sum)\n"
@printf "\tPACKER: Set packer executable (def: packer)\n"
@printf "\tPACKER_OPTS: Set packer options\n"
@printf "\tROOTPW: Set root password for output image\n"
@printf "\tVERBOSE: Enable verbose output\n"
@printf "\tWINDOW: Disable headless mode\n"