T3664: initial implementation of the build flavor system

This commit is contained in:
Daniil Baturin 2022-10-06 12:08:40 -04:00
parent 7149a2aa2e
commit 3979b25dcf
28 changed files with 711 additions and 538 deletions

View File

@ -7,34 +7,11 @@ all:
@echo "Make what specifically?"
@echo "The most common target is 'iso'"
.PHONY: check_build_config
check_build_config:
@scripts/check-config
.PHONY: prepare
prepare:
@set -e
@echo "Starting VyOS ISO image build"
rm -rf build/config/*
mkdir -p build/config
cp -r data/live-build-config/* build/config/
@scripts/live-build-config
@scripts/import-local-packages
@scripts/make-version-file
@scripts/build-flavour
.PHONY: iso
.ONESHELL:
iso: check_build_config clean prepare
@echo "It's not like I'm building this specially for you or anything!"
cd $(build_dir)
iso: clean
set -o pipefail
lb build 2>&1 | tee build.log; if [ $$? -ne 0 ]; then exit 1; fi
cd ..
@scripts/copy-image
@./build-vyos-image iso
exit 0
.PHONY: prepare-package-env
@ -44,50 +21,6 @@ prepare-package-env:
@scripts/pbuilder-config
@scripts/pbuilder-setup
.PHONY: AWS
.ONESHELL:
AWS: clean prepare
@set -e
@echo "It's not like I'm building this specially for you or anything!"
mkdir -p build/config/includes.chroot/etc/cloud/cloud.cfg.d
cp tools/cloud-init/AWS/90_dpkg.cfg build/config/includes.chroot/etc/cloud/cloud.cfg.d/
cp tools/cloud-init/AWS/cloud-init.list.chroot build/config/package-lists/
cp -f tools/cloud-init/AWS/config.boot.default build/config/includes.chroot/opt/vyatta/etc/
cd $(build_dir)
lb build 2>&1 | tee build.log
cd ..
@scripts/copy-image
.PHONY: vep4600
.ONESHELL:
vep4600: check_build_config clean prepare
@set -e
@echo "It's not like I'm building this specially for you or anything!"
mkdir -p build/config/includes.chroot/etc/systemd/network
mkdir -p build/config/includes.chroot/usr/share/initramfs-tools/hooks
cp tools/dell/90-vep.chroot build/config/hooks/live/
cp tools/dell/vep4600/*.link build/config/includes.chroot/etc/systemd/network/
cp tools/dell/vep-hook build/config/includes.chroot/usr/share/initramfs-tools/hooks/
cd $(build_dir)
lb build 2>&1 | tee build.log
cd ..
@scripts/copy-image
.PHONY: vep1400
.ONESHELL:
vep1400: check_build_config clean prepare
@set -e
@echo "It's not like I'm building this specially for you or anything!"
mkdir -p build/config/includes.chroot/etc/systemd/network
mkdir -p build/config/includes.chroot/usr/share/initramfs-tools/hooks
cp tools/dell/90-vep.chroot build/config/hooks/live/
cp tools/dell/vep1400/*.link build/config/includes.chroot/etc/systemd/network/
cp tools/dell/vep-hook build/config/includes.chroot/usr/share/initramfs-tools/hooks/
cd $(build_dir)
lb build 2>&1 | tee build.log
cd ..
@scripts/copy-image
.PHONY: checkiso
.ONESHELL:
checkiso:
@ -125,6 +58,7 @@ testraid: checkiso
.ONESHELL:
clean:
@set -e
mkdir -p $(build_dir)
cd $(build_dir)
lb clean

1
build-vyos-image Symbolic link
View File

@ -0,0 +1 @@
scripts/build-vyos-image

1
configure vendored
View File

@ -1 +0,0 @@
scripts/build-config

View File

@ -0,0 +1,10 @@
# Packages added to images for x86 by default
packages = [
"grub2",
"grub-pc",
"vyos-linux-firmware",
"vyos-intel-qat",
"telegraf"
]
kernel_flavor = "amd64-vyos"

View File

@ -0,0 +1,2 @@
# Packages included in ARM64 images by default
packages = ["grub-efi-arm"]

View File

@ -0,0 +1,2 @@
# Packages included in armhf images by default
packages = ["grub-efi-arm"]

View File

@ -0,0 +1,76 @@
# VyOS build flavors
VyOS supports multiple different hardware and virtual platforms.
Those platforms often need custom packages and may require custom
configs. To make maintenance of existing flavors simpler
and to allow everyone to make and maintain their own flavors,
the build scripts support storing flavor configuration in [TOML](https://toml.io) files.
Flavor files must be in `data/build-flavors`. Here's an example:
```toml
# Generic (aka "universal") ISO image
image_format = "iso"
# Include these packages in the image regardless of the architecture
packages = [
# QEMU and Xen guest tools exist for multiple architectures
"qemu-guest-agent",
"vyos-xe-guest-utilities",
]
[architectures.amd64]
# Hyper-V and VMware guest tools are x86-only
packages = ["hyperv-daemons", "vyos-1x-vmware"]
```
## Image format
The `image_format` option specifies the default format to build.
```toml
image_format = "iso"
```
**Note:** currently, ISO is the only supported format,
support for different flavors is in progress.
## Including custom packages
If you want the build scripts to include custom packages from repositories
in the image, you can list them in the `packages` field.
For example, this is how to include the GNU Hello package:
```toml
packages = ['hello']
```
It's possible to include packages only in images with certain build architectures
by placing them in a subtable.
If you want to include GNU Hello only in AMD64 images, do this:
```toml
[architectures.amd64]
packages = ['hello']
```
## Including custom files
You can include files inside the SquashFS filesystem by adding entries
to the `includes_chroot` array.
```toml
[[includes_chroot]]
path = "etc/question.txt"
data = '''
Can you guess how this file ended up in the image?
'''
path = "etc/answer.txt"
data = '''
It was in the flavor file!
'''
```

View File

@ -0,0 +1,5 @@
image_format = "iso"
packages = ["waagent"]

View File

@ -0,0 +1,46 @@
# ISO image for EdgeCore routers
image_format = "iso"
# udev rules for correct ordering of onboard NICs
[[includes_chroot]]
path = "lib/udev/rules.d/64-vyos-SAF51015I-net.rules"
data = '''
ATTR{[dmi/id]board_name}!="SAF51015I-0318-EC", GOTO="end_ec_nic"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:02:00.0", ENV{VYOS_IFNAME}="eth1"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:03:00.0", ENV{VYOS_IFNAME}="eth2"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:04:00.0", ENV{VYOS_IFNAME}="eth3"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:05:00.0", ENV{VYOS_IFNAME}="eth4"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:06:00.0", ENV{VYOS_IFNAME}="eth5"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:06:00.1", ENV{VYOS_IFNAME}="eth6"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:06:00.2", ENV{VYOS_IFNAME}="eth7"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:06:00.3", ENV{VYOS_IFNAME}="eth8"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:0a:00.0", ENV{VYOS_IFNAME}="eth9"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:0a:00.1", ENV{VYOS_IFNAME}="eth10"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:0b:00.0", ENV{VYOS_IFNAME}="eth11"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:0b:00.1", ENV{VYOS_IFNAME}="eth12"
LABEL="end_ec_nic"
'''
[[includes_chroot]]
path = "lib/udev/rules.d/64-vyos-SAF51003I-net.rules"
data = '''
ATTR{[dmi/id]board_name}!="SAF51003I", GOTO="end_ec_nic"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:02:00.0", ENV{VYOS_IFNAME}="eth1", ATTR{ifalias}="LAN1"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:02:00.1", ENV{VYOS_IFNAME}="eth2", ATTR{ifalias}="LAN2"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:02:00.2", ENV{VYOS_IFNAME}="eth3", ATTR{ifalias}="LAN3"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:02:00.3", ENV{VYOS_IFNAME}="eth4", ATTR{ifalias}="LAN4"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:05:00.0", ENV{VYOS_IFNAME}="eth5", ATTR{ifalias}="LAN5"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:05:00.1", ENV{VYOS_IFNAME}="eth6", ATTR{ifalias}="LAN6"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:05:00.2", ENV{VYOS_IFNAME}="eth7", ATTR{ifalias}="LAN7"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:05:00.3", ENV{VYOS_IFNAME}="eth8", ATTR{ifalias}="LAN8"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:08:00.0", ENV{VYOS_IFNAME}="eth9", ATTR{ifalias}="DMZ"
ACTION=="add", SUBSYSTEM=="net", KERNELS=="0000:08:00.1", ENV{VYOS_IFNAME}="eth10", ATTR{ifalias}="WAN"
LABEL="end_ec_nic"
'''

View File

@ -0,0 +1,14 @@
# Generic (aka "universal") ISO image
image_format = "iso"
# Include these packages in the image regardless of the architecture
packages = [
# QEMU and Xen guest tools exist for multiple architectures
"qemu-guest-agent",
"vyos-xe-guest-utilities",
]
[architectures.amd64]
# Hyper-V and VMware guest tools are x86-only
packages = ["hyperv-daemons", "vyos-1x-vmware"]

View File

@ -0,0 +1,6 @@
# Installation ISO for the XCP-ng virtualization platform
image_formats = "iso"
# Include these packages in the image
packages = ["xe-guest-utilities"]

View File

@ -0,0 +1,8 @@
packages = [
"gdb",
"strace",
"apt-rdepends",
"tshark",
"vim",
"vyos-1x-smoketest"
]

View File

View File

@ -1,17 +0,0 @@
{
"architecture": "amd64",
"debian_mirror": "http://deb.debian.org/debian",
"debian_security_mirror": "http://deb.debian.org/debian",
"debian_distribution": "bullseye",
"vyos_mirror": "http://dev.packages.vyos.net/repositories/current",
"vyos_branch": "current",
"kernel_version": "5.15.72",
"kernel_flavor": "amd64-vyos",
"release_train": "sagitta",
"bootloaders": "syslinux,grub-efi",
"additional_repositories": [
"deb [arch=amd64] https://repo.saltproject.io/py3/debian/11/amd64/3004 bullseye main",
"deb [arch=amd64] http://repo.powerdns.com/debian bullseye-rec-47 main"
],
"custom_packages": []
}

21
data/defaults.toml Normal file
View File

@ -0,0 +1,21 @@
build_type = "development"
architecture = "amd64"
debian_distribution = "bullseye"
debian_mirror = "http://deb.debian.org/debian"
debian_security_mirror = "http://deb.debian.org/debian-security"
vyos_mirror = "http://dev.packages.vyos.net/repositories/current"
vyos_branch = "current"
release_train = "current"
kernel_version = "5.15.72"
additional_repositories = [
"deb [arch=amd64] https://repo.saltproject.io/py3/debian/11/amd64/3004 bullseye main",
"deb [arch=amd64] http://repo.powerdns.com/debian bullseye-rec-45 main"
]

View File

@ -1,10 +0,0 @@
{
"inherit_from": "data/defaults.json",
"architecture": "arm64",
"kernel_flavor": "v8-arm64-vyos",
"bootloaders": "grub-efi",
"additional_repositories": [
"deb [arch=arm64] http://repo.powerdns.com/debian bullseye-rec-45 main",
"deb [arch=arm64] https://repos.influxdata.com/debian bullseye stable"
]
}

View File

@ -1 +0,0 @@
grub-efi-arm

View File

@ -1,6 +0,0 @@
gdb
strace
apt-rdepends
tshark
vim
vyos-1x-smoketest

View File

@ -1,8 +0,0 @@
grub2
grub-pc
qemu-guest-agent
hyperv-daemons
vyos-xe-guest-utilities
vyos-1x-vmware
vyos-linux-firmware
vyos-intel-qat

View File

@ -1,168 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019, VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: build-config
# Purpose:
# This script serves the same purpose as ./configure in traditional
# autoconf setups.
# It takes build configuration options from command line, checks them,
# builds a config dictionary, augments it with some default and/or
# computed values and saves it to build/build-config.json
# for other build scripts to read.
import argparse
import re
import sys
import os
import getpass
import platform
import json
import defaults
# argparse converts hyphens to underscores,
# so for lookups in the original options hash we have to
# convert them back
def field_to_option(s):
return re.sub(r'_', '-', s)
def get_default_build_by():
return "{user}@{host}".format(user= getpass.getuser(), host=platform.node())
def get_validator(optdict, name):
try:
return optdict[name][2]
except KeyError:
return None
def load_config(filename):
with open(filename, 'r') as f:
print(f'Loading {filename}')
this_config = json.load(f)
if not 'inherit_from' in this_config:
print(f'No inheritance detected')
return this_config
inherited_config = load_config(this_config['inherit_from'])
del this_config['inherit_from']
inherited_config.update(this_config)
return inherited_config
# Load the build flavor file
build_flavor = os.getenv('VYOS_BUILD_FLAVOR')
if build_flavor is None:
build_flavor = defaults.DEFAULT_BUILD_FLAVOR
try:
build_defaults = load_config(build_flavor)
except Exception as e:
print("Failed to open the build flavor file {0}: {1}".format(build_flavor, e))
sys.exit(1)
# Options dict format:
# '$option_name_without_leading_dashes': { ('$help_string', $default_value_generator_thunk, $value_checker_thunk) }
options = {
'architecture': ('Image target architecture (amd64 or i386 or armhf or arm64)', lambda: build_defaults['architecture'], lambda x: x in ['amd64', 'i386', 'armhf', 'arm64']),
'build-by': ('Builder identifier (e.g. jrandomhacker@example.net)', get_default_build_by, None),
'debian-mirror': ('Debian repository mirror for ISO build', lambda: build_defaults['debian_mirror'], None),
'debian-security-mirror': ('Debian security updates mirror', lambda: build_defaults['debian_security_mirror'], None),
'pbuilder-debian-mirror': ('Debian repository mirror for pbuilder env bootstrap', lambda: build_defaults['debian_mirror'], None),
'vyos-mirror': ('VyOS package mirror', lambda: build_defaults["vyos_mirror"], None),
'build-type': ('Build type, release or development', lambda: 'development', lambda x: x in ['release', 'development']),
'version': ('Version number (release builds only)', None, None),
'build-comment': ('Optional build comment', lambda: '', None)
}
# Create the option parser
parser = argparse.ArgumentParser()
for k, v in options.items():
help_string, default_value_thunk = v[0], v[1]
if default_value_thunk is None:
parser.add_argument('--' + k, type=str, help=help_string)
else:
parser.add_argument('--' + k, type=str, help=help_string, default=default_value_thunk())
# The debug option is a bit special since it's different type
parser.add_argument('--debug', help="Enable debug output", action='store_true')
# Custom APT entry and APT key options can be used multiple times
parser.add_argument('--custom-apt-entry', help="Custom APT entry", action='append')
parser.add_argument('--custom-apt-key', help="Custom APT key file", action='append')
parser.add_argument('--custom-package', help="Custom package to install from repositories", action='append')
args = vars(parser.parse_args())
# Validate options
for k, v in args.items():
key = field_to_option(k)
func = get_validator(options, k)
if func is not None:
if not func(v):
print("{v} is not a valid value for --{o} option".format(o=key, v=v))
sys.exit(1)
# Some fixup for mirror settings.
# The idea is: if --debian-mirror is specified but --pbuilder-debian-mirror is not,
# use the --debian-mirror value for both lb and pbuilder bootstrap
if (args['debian_mirror'] != build_defaults["debian_mirror"]) and \
(args['pbuilder_debian_mirror'] == build_defaults["debian_mirror"]):
args['pbuilder_debian_mirror'] = args['debian_mirror']
# Version can only be set for release builds,
# for dev builds it hardly makes any sense
if args['build_type'] == 'development':
if args['version'] is not None:
print("Version can only be set for release builds")
print("Use --build-type=release option if you want to set version number")
sys.exit(1)
# Populate some defaults that are not configurable,
# but that are handy to have in the options hash
args['distribution'] = build_defaults["debian_distribution"]
args['build_dir'] = defaults.BUILD_DIR
args['pbuilder_config'] = defaults.PBUILDER_CONFIG
args['vyos_branch'] = build_defaults["vyos_branch"]
args['release_train'] = build_defaults["release_train"]
# Add custom packages from build defaults
if not args['custom_package']:
args['custom_package'] = []
args['custom_package'] = args['custom_package'] + build_defaults['custom_packages']
if not args['custom_apt_entry']:
args['custom_apt_entry'] = []
args['custom_apt_entry'] = args['custom_apt_entry'] + build_defaults['additional_repositories']
# Check the build environment and dependencies
env_check_retval = os.system("scripts/check-build-env")
if env_check_retval > 0:
print("Build environment check failed, fix the issues and retry")
args['kernel_version'] = build_defaults['kernel_version']
args['kernel_flavor'] = build_defaults['kernel_flavor']
args['bootloaders'] = build_defaults['bootloaders']
# Save to file
os.makedirs(defaults.BUILD_DIR, exist_ok=True)
print("Saving the build config to {0}".format(defaults.BUILD_CONFIG))
with open(defaults.BUILD_CONFIG, 'w') as f:
json.dump(args, f, indent=4, sort_keys=True)
print("\n", file=f)

View File

@ -1,37 +0,0 @@
#!/bin/sh
# Copyright (C) 2016 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: build-flavour
# Purpose: Adds various data files to the build config
# depending on the build flavour.
BUILD_TYPE=$(scripts/query-json build/build-config.json build_type)
BUILD_ARCH=$(scripts/query-json build/build-config.json architecture)
# Add debug tools if it's a development image
if [ $BUILD_TYPE = "development" ]; then
cp data/package-lists/vyos-dev.list.chroot build/config/package-lists/
fi
# Install grub-pc if it's an x86 build
if [ $BUILD_ARCH = 'amd64' -o $BUILD_ARCH = 'i386' ]; then
cp data/package-lists/vyos-x86.list.chroot build/config/package-lists/
fi
# Install grub-efi-arm if it's an arm build
if [ $BUILD_ARCH = 'armhf' -o $BUILD_ARCH = 'armel' -o $BUILD_ARCH = 'arm' ]; then
cp data/package-lists/vyos-arm.list.chroot build/config/package-lists/
fi

485
scripts/build-vyos-image Executable file
View File

@ -0,0 +1,485 @@
#!/usr/bin/env python3
#
# Copyright (C) 2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: build-vyos-image
# Purpose: builds VyOS images using a fork of Debian's live-build tool
import re
import os
import sys
import uuid
import glob
import shutil
import getpass
import platform
import argparse
import datetime
import functools
import json
try:
import toml
import jinja2
import git
except ModuleNotFoundError as e:
print("Cannot load a required library: {}".format(e))
print("Please make sure the following Python3 modules are installed: toml jinja2 git")
import vyos_build_utils as utils
import vyos_build_defaults as defaults
# argparse converts hyphens to underscores,
# so for lookups in the original options hash we have to convert them back
def field_to_option(s):
return re.sub(r'_', '-', s)
def get_default_build_by():
return "{user}@{host}".format(user= getpass.getuser(), host=platform.node())
def get_validator(optdict, name):
try:
return optdict[name][2]
except KeyError:
return None
def merge_dicts(source, destination):
""" Merge two dictionaries and return a new dict which has the merged key/value pairs.
Merging logic is as follows:
Sub-dicts are merged.
List values are combined.
Scalar values are set to those from the source dict.
"""
from copy import deepcopy
tmp = deepcopy(destination)
for key, value in source.items():
if key not in tmp:
tmp[key] = value
elif isinstance(source[key], dict):
tmp[key] = dict_merge(source[key], tmp[key])
elif isinstance(source[key], list):
tmp[key] = source[key] + tmp[key]
else:
tmp[key] = source[key]
return tmp
def has_nonempty_key(config, key):
if key in config:
if config[key]:
return True
return False
def make_toml_path(dir, file_basename):
return os.path.join(dir, file_basename + ".toml")
if __name__ == "__main__":
## Check if the script is running wirh root permissions
## Since live-build requires privileged calls such as chroot(),
## there's no real way around it.
if os.getuid() != 0:
print("E: this script requires root privileges")
sys.exit(1)
## Check if there are missing build dependencies
deps = {
'packages': [
'sudo',
'make',
'live-build',
'pbuilder',
'devscripts',
'python3-pystache',
'python3-git',
'qemu-utils'
],
'binaries': []
}
print("I: Checking if packages required for VyOS image build are installed")
try:
checker = utils.check_system_dependencies(deps)
except OSError as e:
print(e)
sys.exit(1)
## Load the file with default build configuration options
try:
with open(defaults.DEFAULTS_FILE, 'r') as f:
build_defaults = toml.load(f)
except Exception as e:
print("Failed to open the defaults file {0}: {1}".format(defaults.DEFAULTS_FILE, e))
sys.exit(1)
## Get a list of available build flavors
build_flavors = list(map(lambda f: os.path.splitext(f)[0], os.listdir(defaults.BUILD_FLAVORS_DIR)))
## Set up the option parser
## XXX: It uses values from the default configuration file for its option defaults,
## which is why it's defined after loading the defaults.toml file data.
# Options dict format:
# '$option_name_without_leading_dashes': { ('$help_string', $default_value_generator_thunk, $value_checker_thunk) }
options = {
'architecture': ('Image target architecture (amd64 or arm64)',
lambda: build_defaults['architecture'], lambda x: x in ['amd64', 'arm64']),
'build-by': ('Builder identifier (e.g. jrandomhacker@example.net)', get_default_build_by, None),
'debian-mirror': ('Debian repository mirror', lambda: build_defaults['debian_mirror'], None),
'debian-security-mirror': ('Debian security updates mirror', lambda: build_defaults['debian_security_mirror'], None),
'pbuilder-debian-mirror': ('Debian repository mirror for pbuilder env bootstrap', lambda: build_defaults['debian_mirror'], None),
'vyos-mirror': ('VyOS package mirror', lambda: build_defaults["vyos_mirror"], None),
'build-type': ('Build type, release or development', lambda: 'development', lambda x: x in ['release', 'development']),
'version': ('Version number (release builds only)', None, None),
'build-comment': ('Optional build comment', lambda: '', None)
}
# Create the option parser
parser = argparse.ArgumentParser()
for k, v in options.items():
help_string, default_value_thunk = v[0], v[1]
if default_value_thunk is None:
parser.add_argument('--' + k, type=str, help=help_string)
else:
parser.add_argument('--' + k, type=str, help=help_string, default=default_value_thunk())
# The debug option is a bit special since it different type is different
parser.add_argument('--debug', help='Enable debug output', action='store_true')
parser.add_argument('--dry-run', help='Check build configuration and exit', action='store_true')
# Custom APT entry and APT key options can be used multiple times
parser.add_argument('--custom-apt-entry', help="Custom APT entry", action='append')
parser.add_argument('--custom-apt-key', help="Custom APT key file", action='append')
parser.add_argument('--custom-package', help="Custom package to install from repositories", action='append')
# Build flavor is a positional argument
parser.add_argument('build_flavor', help='Build flavor', nargs='?', action='store')
args = vars(parser.parse_args())
debug = args["debug"]
# Validate options
for k, v in args.items():
key = field_to_option(k)
func = get_validator(options, k)
if func is not None:
if not func(v):
print("{v} is not a valid value for --{o} option".format(o=key, v=v))
sys.exit(1)
if not args["build_flavor"]:
print("E: Build flavor is not specified!")
print("E: For example, to build the generic ISO, run {} iso".format(sys.argv[0]))
print("Available build flavors:\n")
print("\n".join(build_flavors))
sys.exit(1)
# Some fixup for mirror settings.
# The idea is: if --debian-mirror is specified but --pbuilder-debian-mirror is not,
# use the --debian-mirror value for both lb and pbuilder bootstrap
if (args['debian_mirror'] != build_defaults["debian_mirror"]) and \
(args['pbuilder_debian_mirror'] == build_defaults["debian_mirror"]):
args['pbuilder_debian_mirror'] = args['debian_mirror']
# Version can only be set for release builds,
# for dev builds it hardly makes any sense
if args['build_type'] == 'development':
if args['version'] is not None:
print("Version can only be set for release builds")
print("Use --build-type=release option if you want to set version number")
sys.exit(1)
if not args['custom_apt_entry']:
args['custom_apt_entry'] = []
args['custom_apt_entry'] = args['custom_apt_entry'] + build_defaults['additional_repositories']
## Inject some useful hardcoded options
args['build_dir'] = defaults.BUILD_DIR
args['pbuilder_config'] = os.path.join(defaults.BUILD_DIR, defaults.PBUILDER_CONFIG)
## Combine the arguments with non-configurable defaults
build_config = merge_dicts(build_defaults, args)
## Load the flavor file and mix-ins
with open(make_toml_path(defaults.BUILD_TYPES_DIR, build_config["build_type"]), 'r') as f:
build_type_config = toml.load(f)
build_config = merge_dicts(build_config, build_type_config)
with open(make_toml_path(defaults.BUILD_ARCHES_DIR, build_config["architecture"]), 'r') as f:
build_arch_config = toml.load(f)
build_config = merge_dicts(build_config, build_arch_config)
with open(make_toml_path(defaults.BUILD_FLAVORS_DIR, build_config["build_flavor"]), 'r') as f:
flavor_config = toml.load(f)
build_config = merge_dicts(build_config, flavor_config)
## Rename and merge some fields for simplicity
## E.g. --custom-packages is for the user, but internally
## it's added to the same package list as everything else
if has_nonempty_key(build_config, "custom_package"):
build_config["packages"] += build_config["custom_package"]
del build_config["custom_package"]
## Add architecture-dependent packages from the flavor
if has_nonempty_key(build_config, "architectures"):
arch = build_config["architecture"]
if arch in build_config["architectures"]:
build_config["packages"] += build_config["architectures"][arch]["packages"]
## Dump the complete config if the user enabled debug mode
if debug:
import json
print("D: Effective build config:\n")
print(json.dumps(build_config, indent=4))
## Clean up the old build config and set up a fresh copy
lb_config_dir = os.path.join(defaults.BUILD_DIR, defaults.LB_CONFIG_DIR)
print(lb_config_dir)
shutil.rmtree(lb_config_dir, ignore_errors=True)
shutil.copytree("data/live-build-config/", lb_config_dir)
os.makedirs(lb_config_dir, exist_ok=True)
## Create the version file
# Create a build timestamp
now = datetime.datetime.today()
build_timestamp = now.strftime("%Y%m%d%H%M")
# FIXME: use aware rather than naive object
build_date = now.strftime("%a %d %b %Y %H:%M UTC")
# Assign a (hopefully) unique identifier to the build (UUID)
build_uuid = str(uuid.uuid4())
# Initialize Git object from our repository
try:
repo = git.Repo('.')
# Retrieve the Git commit ID of the repository, 14 charaters will be sufficient
build_git = repo.head.object.hexsha[:14]
# If somone played around with the source tree and the build is "dirty", mark it
if repo.is_dirty():
build_git += "-dirty"
# Retrieve git branch name
git_branch = repo.active_branch.name
except Exception as e:
print("Could not retrieve information from git: {0}".format(str(e)))
build_git = ""
git_branch = ""
git_commit = ""
# Create the build version string
if build_config['build_type'] == 'development':
try:
if not git_branch:
raise ValueError("git branch could not be determined")
# Load the branch to version mapping file
with open('data/versions') as f:
version_mapping = json.load(f)
branch_version = version_mapping[git_branch]
version = "{0}-rolling-{1}".format(branch_version, build_timestamp)
except Exception as e:
print("Could not build a version string specific to git branch, falling back to default: {0}".format(str(e)))
version = "999.{0}".format(build_timestamp)
else:
# Release build, use the version from ./configure arguments
version = build_config['version']
if build_config['build_type'] == 'development':
lts_build = False
else:
lts_build = True
version_data = {
'version': version,
'built_by': build_config['build_by'],
'built_on': build_date,
'build_uuid': build_uuid,
'build_git': build_git,
'build_branch': git_branch,
'release_train': build_config['release_train'],
'lts_build': lts_build,
'build_comment': build_config['build_comment']
}
os_release = f"""
PRETTY_NAME="VyOS {version} ({build_config['release_train']})"
NAME="VyOS"
VERSION_ID="{version}"
VERSION="{version} ({build_config['release_train']})"
VERSION_CODENAME=buster
ID=vyos
HOME_URL="https://vyos.io"
SUPPORT_URL="https://support.vyos.io"
BUG_REPORT_URL="https://phabricator.vyos.net"
"""
chroot_includes_dir = os.path.join(defaults.BUILD_DIR, defaults.CHROOT_INCLUDES_DIR)
vyos_data_dir = os.path.join(chroot_includes_dir, "usr/share/vyos")
os.makedirs(vyos_data_dir, exist_ok=True)
with open(os.path.join(vyos_data_dir, 'version.json'), 'w') as f:
json.dump(version_data, f)
# For backwards compatibility with 'add system image' script from older versions
# we need a file in the old format so that script can find out the version of the image
# for upgrade
os.makedirs(os.path.join(chroot_includes_dir, 'opt/vyatta/etc/'), exist_ok=True)
with open(os.path.join(chroot_includes_dir, 'opt/vyatta/etc/version'), 'w') as f:
print("Version: {0}".format(version), file=f)
# Define variables that influence to welcome message on boot
os.makedirs(os.path.join(chroot_includes_dir, 'usr/lib/'), exist_ok=True)
with open(os.path.join(chroot_includes_dir, 'usr/lib//os-release'), 'w') as f:
print(os_release, file=f)
## Switch to the build directory, this is crucial for the live-build work work
## because the efective build config files etc. are there
os.chdir(defaults.BUILD_DIR)
## Clean up earlier build state and artifacts
print("I: Cleaning the build workspace")
os.system("lb clean")
#iter(lambda p: shutil.rmtree(p, ignore_errors=True),
# ['config/binary', 'config/bootstrap', 'config/chroot', 'config/common', 'config/source'])
artifacts = functools.reduce(
lambda x, y: x + y,
map(glob.glob, ['*.iso', '*.raw', '*.img', '*.xz', '*.ova', '*.ovf']))
iter(os.remove, artifacts)
## Create live-build configuration files
# Add the additional repositories to package lists
print("I: Setting up additional APT entries")
vyos_repo_entry = "deb {0} {1} main\n".format(build_config['vyos_mirror'], build_config['vyos_branch'])
apt_file = defaults.VYOS_REPO_FILE
if debug:
print("D: Adding these entries to {0}:".format(apt_file))
print("\t", vyos_repo_entry)
with open(apt_file, 'w') as f:
f.write(vyos_repo_entry)
# Add custom APT entries
if build_config['custom_apt_entry']:
custom_apt_file = defaults.CUSTOM_REPO_FILE
entries = "\n".join(build_config['custom_apt_entry'])
if debug:
print("D: Adding custom APT entries:")
print(entries)
with open(custom_apt_file, 'w') as f:
f.write(entries)
f.write("\n")
# Add custom APT keys
if has_nonempty_key(build_config, 'custom_apt_key'):
key_dir = ARCHIVES_DIR
for k in build_config['custom_apt_key']:
dst_name = '{0}.key.chroot'.format(os.path.basename(k))
shutil.copy(k, os.path.join(key_dir, dst_name))
# Add custom packages
if has_nonempty_key(build_config, 'packages'):
package_list_file = defaults.PACKAGE_LIST_FILE
packages = "\n".join(build_config['packages'])
with open (package_list_file, 'w') as f:
f.write(packages)
## Create includes
if has_nonempty_key(build_config, "includes_chroot"):
for i in build_config["includes_chroot"]:
file_path = os.path.join(includes_chroot_dir, i["path"])
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w') as f:
f.write(i["data"])
## Configure live-build
lb_config_tmpl = jinja2.Template("""
lb config noauto \
--architectures {{architecture}} \
--bootappend-live "boot=live components hostname=vyos username=live nopersistence noautologin nonetworking union=overlay console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
--bootappend-live-failsafe "live components memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
--linux-flavours {{kernel_flavor}} \
--linux-packages linux-image-{{kernel_version}} \
--bootloader syslinux,grub-efi \
--binary-images iso-hybrid \
--checksums 'sha256 md5' \
--debian-installer none \
--distribution {{debian_distribution}} \
--iso-application "VyOS" \
--iso-publisher "{{build_by}}" \
--iso-volume "VyOS" \
--debootstrap-options "--variant=minbase --exclude=isc-dhcp-client,isc-dhcp-common,ifupdown --include=apt-utils,ca-certificates,gnupg2" \
--mirror-bootstrap {{debian_mirror}} \
--mirror-chroot {{debian_mirror}} \
--mirror-chroot-security {{debian_security_mirror}} \
--mirror-binary {{debian_mirror}} \
--mirror-binary-security {{debian_security_mirror}} \
--archive-areas "main contrib non-free" \
--firmware-chroot false \
--firmware-binary false \
--updates true \
--security true \
--backports true \
--apt-recommends false \
--apt-options "--yes -oAPT::Default-Release="{{release_train}}" -oAPT::Get::allow-downgrades=true" \
--apt-indices false
"${@}"
""")
lb_config_command = lb_config_tmpl.render(build_config)
print("I: Configuring live-build")
if debug:
print("D: live-build configuration command")
print(lb_config_command)
result = os.system(lb_config_command)
if result > 0:
print("E: live-build config failed")
sys.exit(1)
## In dry-run mode, exit at this point
if build_config["dry_run"]:
print("I: dry-run, not starting image build")
sys.exit(0)
## Build the image
print("I: Starting image build")
if debug:
print("D: It's not like I'm building this specially for you or anything!")
res = os.system("lb build 2>&1")
if res > 0:
sys.exit(res)
# Copy the image
shutil.copy("live-image-{0}.hybrid.iso".format(build_config["architecture"]),
"vyos-{0}-{1}.iso".format(version_data["version"], build_config["architecture"]))

View File

@ -1,38 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (C) 2015 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: check-config
# Purpose:
# Checks if the build config file (build/build-config.json) exists.
# This is to prevent accidental execution of plumbing make targets
# from going too far and failing with confusing errors.
import sys
import json
import defaults
print("Checking build configuration")
try:
with open(defaults.BUILD_CONFIG, 'r') as f:
build_config = json.load(f)
except:
print("Build config does not exist or is not a valid JSON file")
print("Please run the ./configure script and try again")
sys.exit(1)

View File

@ -1,7 +0,0 @@
#!/bin/sh
BUILD_DIR="$(scripts/query-json build/build-config.json build_dir)"
BUILD_ARCH="$(scripts/query-json build/build-config.json architecture)"
VERSION="$(cat $BUILD_DIR/version)"
cp "$BUILD_DIR/live-image-$BUILD_ARCH.hybrid.iso" "$BUILD_DIR/vyos-$VERSION-$BUILD_ARCH.iso"

View File

@ -1,123 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: live-build-config
# Purpose:
# Creates a live-build config command from template using the build config
# and executes it, to prepare the system for building the installation ISO.
import sys
import os
import shutil
import json
import pystache
import defaults
import util
util.check_build_config()
lb_config_tmpl = """
lb config noauto \
--architectures {{architecture}} \
--bootappend-live "boot=live components hostname=vyos username=live nopersistence noautologin nonetworking union=overlay console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
--bootappend-live-failsafe "live components memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
--linux-flavours {{kernel_flavor}} \
--linux-packages linux-image-{{kernel_version}} \
--bootloader {{bootloaders}} \
--binary-images iso-hybrid \
--checksums 'sha256 md5' \
--debian-installer none \
--distribution {{distribution}} \
--iso-application "VyOS" \
--iso-publisher "{{build_by}}" \
--iso-volume "VyOS" \
--debootstrap-options "--variant=minbase --exclude=isc-dhcp-client,isc-dhcp-common,ifupdown --include=apt-utils,ca-certificates,gnupg2" \
--mirror-bootstrap {{debian_mirror}} \
--mirror-chroot {{debian_mirror}} \
--mirror-chroot-security {{debian_security_mirror}} \
--mirror-binary {{debian_mirror}} \
--mirror-binary-security {{debian_security_mirror}} \
--archive-areas "main contrib non-free" \
--firmware-chroot false \
--firmware-binary false \
--updates true \
--security false \
--backports true \
--utc-time true \
--debug \
--apt-recommends false \
--apt-options "--yes -oAPT::Get::allow-downgrades=true" \
--apt-indices false
"${@}"
"""
with open(defaults.BUILD_CONFIG, 'r') as f:
build_config = json.load(f)
debug = build_config['debug']
# Add the additional repositories to package lists
print("Setting up additional APT entries")
vyos_repo_entry = "deb {0} {1} main\n".format(build_config['vyos_mirror'], build_config['vyos_branch'])
apt_file = os.path.join(build_config['build_dir'], defaults.VYOS_REPO_FILE)
if debug:
print("Adding these entries to {0}:".format(apt_file))
print("\t", vyos_repo_entry)
with open(apt_file, 'w') as f:
f.write(vyos_repo_entry)
# Add custom APT entries
if build_config['custom_apt_entry']:
custom_apt_file = os.path.join(build_config['build_dir'], defaults.CUSTOM_REPO_FILE)
entries = "\n".join(build_config['custom_apt_entry'])
if debug:
print("Adding custom APT entries:")
print(entries)
with open(custom_apt_file, 'w') as f:
f.write(entries)
f.write("\n")
# Add custom APT keys
if build_config['custom_apt_key']:
key_dir = os.path.join(build_config['build_dir'], defaults.ARCHIVES_DIR)
for k in build_config['custom_apt_key']:
dst_name = '{0}.key.chroot'.format(os.path.basename(k))
shutil.copy(k, os.path.join(key_dir, dst_name))
# Add custom packages
if build_config['custom_package']:
package_list_file = os.path.join(build_config['build_dir'], defaults.CUSTOM_PACKAGE_LIST_FILE)
packages = "\n".join(build_config['custom_package'])
with open (package_list_file, 'w') as f:
f.write(packages)
# Configure live-build
lb_config_command = pystache.render(lb_config_tmpl, build_config)
print("Configuring live-build")
os.chdir(defaults.BUILD_DIR)
result = os.system(lb_config_command)
if result > 0:
print("live-build config failed")
sys.exit(1)

View File

@ -1,42 +0,0 @@
#!/usr/bin/python3
#
# Copyright (C) 2016 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: query-config
# Purpose: Extracts field values from a flat JSON file,
# for use in languages that can't handle JSON easily,
# (I'm looking at you, Bourne shell!)
import sys
import json
import defaults
import util
if len(sys.argv) < 3:
print("Usage: {0} <flat JSON file> <config field name>".format(sys.argv[0]))
sys.exit(1)
# Note: lack of error handling is deliberate, if some field is expected to be there
# but isn't, it's better if the failure will be obvious and spectacular
file = sys.argv[1]
key = sys.argv[2]
with open(file, 'r') as f:
json_data = json.load(f)
print(json_data[key])

View File

@ -18,19 +18,28 @@
import os
# Relative to the repository directory
BUILD_DIR = 'build'
BUILD_CONFIG = os.path.join(BUILD_DIR, 'build-config.json')
BUILD_CONFIG = os.path.join(BUILD_DIR, 'build-config.toml')
PBUILDER_CONFIG = os.path.join(BUILD_DIR, 'pbuilderrc')
PBUILDER_DIR = os.path.join(BUILD_DIR, 'pbuilder')
DEFAULTS_FILE = 'data/defaults.toml'
LB_CONFIG_DIR = os.path.join(BUILD_DIR, 'config')
CHROOT_INCLUDES_DIR = os.path.join(LB_CONFIG_DIR, 'includes.chroot')
BUILD_TYPES_DIR = 'data/build-types'
BUILD_ARCHES_DIR = 'data/architectures'
BUILD_FLAVORS_DIR = 'data/build-flavors'
# Relative to the build directory
PBUILDER_CONFIG = 'pbuilderrc'
PBUILDER_DIR = 'pbuilder'
LB_CONFIG_DIR = 'config'
CHROOT_INCLUDES_DIR = 'config/includes.chroot'
ARCHIVES_DIR = 'config/archives/'
VYOS_REPO_FILE = 'config/archives/vyos.list.chroot'
CUSTOM_REPO_FILE = 'config/archives/custom.list.chroot'
CUSTOM_PACKAGE_LIST_FILE = 'config/package-lists/custom.list.chroot'
PACKAGE_LIST_FILE = 'config/package-lists/custom.list.chroot'
DEFAULT_BUILD_FLAVOR = 'data/defaults.json'

View File

@ -21,7 +21,7 @@ import sys
import os
from distutils.spawn import find_executable
import defaults
import vyos_build_defaults as defaults
def check_build_config():
if not os.path.exists(defaults.BUILD_CONFIG):
@ -60,6 +60,18 @@ class DependencyChecker(object):
return self.__missing
return None
def print_missing_deps(self):
print("Missing packages: " + " ".join(self.__missing['packages']))
print("Missing binaries: " + " ".join(self.__missing['binaries']))
def format_missing_dependencies(self):
msg = "E: There are missing system dependencies!\n"
if self.__missing['packages']:
msg += "E: Missing packages: " + " ".join(self.__missing['packages'])
if self.__missing['binaries']:
msg += "E: Missing binaries: " + " ".join(self.__missing['binaries'])
return msg
def check_system_dependencies(deps):
checker = DependencyChecker(deps)
missing = checker.get_missing_dependencies()
if missing:
raise OSError(checker.format_missing_dependencies())
else:
pass