# 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_BOOT ?= ansible-dracut ANSIBLE_DIR_PROV ?= ansible-roles-prov HASHER ?= sha256sum SHELL := /usr/bin/env bash BUILDS := builds ARCH := $(shell uname -m | sed 's/i686/i386/') AVAILABLE_BUILDERS := QEMU BUILDER_QEMU_EXE := qemu-system-$(ARCH) BUILDER_QEMU_NAME := qemu BUILDER_QEMU_PROV := $(BUILDER_QEMU_NAME) BUILDERS := qemu override BUILDER := qemu override PROVISIONER := qemu # The packer templates, detected as *.json (excluding base.json) TEMPLATES := $(patsubst %/,%,$(dir $(wildcard */base.pkrvars.hcl))) # The provisioning flavors, detected as ansible-/setup-.yml # Find all paths beginnig with "ansible-" and and save this suffix in PATTERNS PATTERNS := $(patsubst ansible-%, %, $(wildcard ansible-*)) # Extract from ansible-/setup-.yml using PATTERNS as FLAVORS := $(foreach p, $(PATTERNS), \ $(patsubst ansible-$(p)/setup-$(p).yml, $(p), \ $(wildcard ansible-$(p)/setup-$(p).yml))) # Extra flavor from variable ANSIBLE_DIR_PROV, using pattern ANSIBLE_DIR_PROV/setup-.yml FLAVORS += $(patsubst $(ANSIBLE_DIR_PROV)/setup-%.yml,%, \ $(wildcard $(ANSIBLE_DIR_PROV)/setup-*.yml)) FLAVORS := $(strip $(FLAVORS)) BASETARGETS := $(strip $(foreach t, $(TEMPLATES), $(t)/base)) PROVTARGETS := $(strip $(foreach t, $(TEMPLATES), \ $(foreach f, $(FLAVORS), $(t)/$(f)))) PROVIMAGES := $(strip $(foreach f, $(FLAVORS), \ $(foreach t, $(TEMPLATES), $(t)/$(f): %/$(f): %/base))) REPROVTARGETS := $(strip $(foreach t, $(TEMPLATES), \ $(foreach f, $(FLAVORS), \ $(filter-out $(t)/$(f).latest, \ $(wildcard $(t)/$(f).*))))) COMPRESSPOSSIBLE := $(foreach t, $(TEMPLATES), \ $(foreach f, $(FLAVORS), $(wildcard $(t)/$(f).*))) COMPRESSTARGETS := $(foreach p, $(COMPRESSPOSSIBLE), $(p)/compress) BOOTTARGETS := $(foreach t, $(TEMPLATES), $(t)/base/boot) BOOTTARGETS += $(foreach p, $(PROVTARGETS), $(p)/boot) BOOTTARGETS := $(strip $(BOOTTARGETS)) CURBOOTTARGETS := $(foreach p, $(REPROVTARGETS), $(p)/boot) BASERMTARGETS := $(foreach t, $(BASETARGETS), rm/$(t)) PROVRMTARGETS := $(foreach t, $(REPROVTARGETS), rm/$(t)) ifdef DEBUG VERBOSE := 1 ifeq ($(DEBUG),STEP) override PACKER_OPTS += -debug else override PACKER_OPTS += -on-error=ask endif endif ifdef FORCE override PACKER_OPTS += -force endif ifdef WINDOW override PACKER_OPTS += -var headless=false endif override PACKER_OPTS += -warn-on-undeclared-var # 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") ifdef VERBOSE $(info timestamp: $(TIMESTAMP)) $(info ) $(info packer: executable: $(PACKER)) $(info packer: options: $(PACKER_OPTS)) $(info hasher: $(HASHER)) $(info ) $(info ansible: boot: $(ANSIBLE_DIR_BOOT)) $(info ansible: flavors: $(FLAVORS)) $(info ) $(info builder: available: $(AVAILABLE_BUILDERS)) $(info builder: chosen: $(BUILDER)) $(info ) $(info targets: base: $(BASETARGETS)) $(info targets: boot: $(BOOTTARGETS)) $(info targets: provision: $(PROVTARGETS)) $(info targets: reprovision: $(REPROVTARGETS)) $(info ) override PACKER := PACKER_LOG=1 $(PACKER) endif .PHONY: help clean_except_last clean_bases clean_all clean_failed backinglock $(REPROVTARGETS) help: # Creating base images. When DRACUT_INIT is specified, the dracut module # repository will be initially cloned and its required components (dnbd3, # xmount, libxmount-qemu) will be pre-build to accelerate subsequent builds. $(BASETARGETS): $(info ** Building template '$(@D)' using '$(BUILDER)' **) $(eval INIT_TAG := $(if $(DRACUT_INIT), install, untagged)) $(PACKER) build -only=base.$(BUILDER).ansible \ $(PACKER_OPTS) \ -var-file=$(@D)/base.pkrvars.hcl \ -var http_dir=$(@D)/http \ -var output_directory=$(BUILDS)/$(@D)/base \ -var playbook_file=$(ANSIBLE_DIR_BOOT)/slx-builder.yml \ -var vm_name=$(@D) \ ./ $(HASHER) $(BUILDS)/$(@D)/base/$(@D) >$(BUILDS)/$(@D)/base/CHECKSUM # Provisioning images $(PROVIMAGES) $(PROVTARGETS) $(REPROVTARGETS): $(eval FLAVOR := $(basename $(@F))) $(eval ANSIBLE_DIR_PROV := $(if ansible-$(FLAVOR)/setup-$(FLAVOR).yml,\ ansible-$(FLAVOR))) $(eval VERSION := $(if $(suffix $(@)), $(subst .,,$(suffix $(@))), 0)) $(eval BASE_IMAGE := $(if $(wildcard $(BUILDS)/$(@)/$(@D)-$(FLAVOR)),\ $(BUILDS)/$(@)/$(@D)-$(FLAVOR),\ $(BUILDS)/$(@D)/base/$(@D))) $(eval BUILD_DIR := $(BUILDS)/$(@D)/$(FLAVOR).$(TIMESTAMP)) $(info ** Provisioning '$(@D)' with '$(FLAVOR)' **) @mkdir -p $(BUILD_DIR) @cp -r `readlink -f $(ANSIBLE_DIR_PROV)` $(BUILD_DIR)/$(ANSIBLE_DIR_PROV) @ln -sfn $(FLAVOR).$(TIMESTAMP) $(BUILDS)/$(@D)/$(FLAVOR).latest @ln -sfr $(BUILD_DIR) $(dir $(BASE_IMAGE))/$(TIMESTAMP).backinglock @-cp -r $(BUILDS)/$@/$(ANSIBLE_DIR_PROV) $(BUILD_DIR) @-cp -r $(BUILDS)/$@/$(ANSIBLE_DIR_PROV).* $(BUILD_DIR) $(PACKER) build -only=flavor.$(BUILDER).ansible \ $(PACKER_OPTS) \ -var disk_image=true \ -var iso_url=$(BASE_IMAGE) \ -var iso_checksum=file:$(dir $(BASE_IMAGE))CHECKSUM \ -var output_directory=$(BUILD_DIR)/image \ -var playbook_file=$(BUILD_DIR)/$(ANSIBLE_DIR_PROV)/setup-$(FLAVOR).yml \ -var vm_name=$(@D)-$(FLAVOR) \ ./ $(HASHER) $(BUILD_DIR)/image/$(@D)-$(FLAVOR) \ >$(BUILD_DIR)/image/CHECKSUM # Generating boot files $(BOOTTARGETS): %/boot: % $(BOOTTARGETS) $(CURBOOTTARGETS): $(eval FLAVOR := $(notdir $(@D))) $(eval BASE := $(patsubst %/, %, $(dir $(@D)))) $(eval BASE_DIR := $(if $(filter base, $(notdir $(BUILDS)/$(@D))),\ ,\ $(if $(wildcard $(BUILDS)/$(@D)/image/.),\ $(BUILDS)/$(@D),\ $(BUILDS)/$(@D).$(TIMESTAMP)))) $(eval BUILD_DIR := $(if $(BASE_DIR)/image,\ $(BASE_DIR)/image,\ $(BUILDS)/$(@D))) $(eval BUILD_DIR := $(if $(filter $(@), $(CURBOOTTARGETS)),\ $(BASE_DIR)/image,\ $(BUILD_DIR))) $(info ** Generating boot files for '$(BUILD_DIR)') $(PACKER) build -only=flavor.$(BUILDER).ansible \ $(PACKER_OPTS) \ -var disk_image=true \ -var iso_url=$(BUILD_DIR)/$(BASE)-$(FLAVOR) \ -var iso_checksum=file:$(dir $(BUILD_DIR))image/CHECKSUM \ -var output_directory=$(BUILD_DIR)/tmp \ -var playbook_file=$(ANSIBLE_DIR_BOOT)/slx-builder.yml \ -var vm_name=$(BASE)-$(FLAVOR).tmp \ ./ @mkdir -p $(BUILD_DIR)/boot @mv -f $(ANSIBLE_DIR_BOOT)/boot_files/* $(BUILD_DIR)/boot $(if $(DEBUG),,@rm -rf $(BUILD_DIR)/tmp) # Safe removal of images $(BASERMTARGETS): rm/%: %/*.backinglock $(eval BUILD_DIR := $(subst rm/,,$(@))) @rm -rf $(BUILD_DIR) $(PROVRMTARGETS): rm/%: %/build/*.backinglock $(eval BUILD_DIR := $(subst rm/,,$(@))/build) $(eval FATHER_BUILD := $(dir $(shell qemu-img info $(BUILD_DIR)/rootfs-image | grep "backing file" | cut -d\ -f3-))) $(eval BUILD_TIME := $(subst .,,$(suffix $(@)))) @rm -rf $(FATHER_BUILD)/$(BUILD_TIME).backinglock @rm -rf $(BUILD_DIR) %.backinglock: backinglock @qemu-img convert -f qcow2 $(@)/build/rootfs-image -O qcow2 $(@)/build/rootfs-image.tmp @rm $(@)/build/rootfs-image @mv $(@)/build/rootfs-image.tmp $(@)/build/rootfs-image @rm $(@) backinglock: clean_except_last: @-$(foreach template,$(TEMPLATES),\ $(eval exclusions := $(shell test -d $(template) && \ find $(template) \ -maxdepth 1 \ -type l \ -print0 \ | xargs -r -0 -n1 readlink))\ test -d $(template) && \ find $(template)/* \ -maxdepth 0 \ -type d \ $(foreach file,$(exclusions),-not -name $(file) ) \ -not -name base \ -print0 \ | xargs -r -0 -n1 rm -rf; ) clean_failed: @-$(foreach template,$(TEMPLATES),\ test -d $(template) && \ find $(template)/* \ -maxdepth 0 \ -type d \ -not -name base \ -print0 \ | xargs -r -0 -n1 -i \ $(SHELL) -c 'test -d "{}/build" || 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