Skip to content

Commit

Permalink
Add build-arg and build-context options
Browse files Browse the repository at this point in the history
  • Loading branch information
khizunov committed Dec 26, 2023
1 parent 64341d0 commit 5373527
Show file tree
Hide file tree
Showing 12 changed files with 871 additions and 339 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,27 @@ Skipper can serve as your primary tool for your daily development tasks:
--build-container-tag Tag of the build container
--build-container-net Network to connect the build container (default: net=host)
--env-file Environment variables file/s to pass to the container
--build-arg Set build-time variables for the container
--build-context Additional build contexts when running the build command, give them a name, and then access them inside a Dockerfile
--help Show this message and exit.
```

### [Build context explained](https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/)
Skipper allows you to add additional build contexts when running the build command, give them a name, and then access them inside a Dockerfile.
The build context can be one of the following:
* Local directory – e.g. `--build-context project2=../path/to/project2/src`
* Git repository – e.g. `--build-context qemu-src=https://github.com/qemu/qemu.git`
* HTTP URL to a tarball – e.g. `--build-context src=https://example.org/releases/src.tar`
* Docker image – Define with a `docker-image://` prefix, e.g. `--build-context alpine=docker-image://alpine:3.15`

On the Dockerfile side, you can reference the build context on all commands that accept the “from” parameter. Here’s how that might look:

```dockerfile
FROM [name]
COPY --from=[name] ...
RUN --mount=from=[name] …
```

### Build
As a convention, skipper infers the docker images from the Dockerfiles in the top directory of your repository. For example, assuming that there are 3 Dockerfile in the top directory of the repository:
```
Expand Down Expand Up @@ -154,6 +172,16 @@ build-container-image: development
build-container-tag: latest
container-context: /path/to/context/dir

build-arg:
- VAR1=value1
- VAR2=value2

build-context:
- context1=/path/to/context/dir # Local directory
- qemu-src=https://github.com/qemu/qemu.git # Remote git repository
- src=https://example.org/releases/src.tar # Remote tar file
- alpine=docker-image://alpine:3.15 # Remote docker image

make:
makefile: Makefile.arm32
containers:
Expand All @@ -164,6 +192,14 @@ env:
env_file: path/to/env_file.env
```
```yaml
# Use the git revision as the build container tag
# Allows to use the same build container unless the git revision changes
# This is useful when using a CI system that caches the build container
# Remember to commit if you changing the build container
build-container-tag: 'git:revision'
```
Using the above configuration file, we now can run a simplified version of the make command described above:
```bash
skipper make tests
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PyYAML>=3.11
click==6.7
click>=6.7
requests>=2.6.0
tabulate>=0.7.5
six>=1.10.0
Expand Down
10 changes: 5 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[metadata]
name = strato-skipper
author = Adir Gabai
author-email = [email protected]
home-page = http://github.com/Stratoscale/skipper
author_email = [email protected]
home_page = http://github.com/Stratoscale/skipper
summary = Easily dockerize your Git repository
license = Apache-2
long_description = file: README.md
long_description_content_type = text/markdown

requires-dist = setuptools
requires-python = >=3
requires_dist = setuptools
requires_python = >=3


[files]
Expand All @@ -21,4 +21,4 @@ console_scripts =
skipper = skipper.main:main

[pep8]
max-line-length=145
max_line_length=145
2 changes: 1 addition & 1 deletion skipper.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
build-container-image: skipper-build
build-container-image: skipper-build
192 changes: 192 additions & 0 deletions skipper/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
from dataclasses import dataclass
from logging import Logger
from typing import Callable

from skipper import utils

DOCKER_TAG_FOR_CACHE = "cache"


class Image:
"""
A class to represent an image with attributes like registry, name, tag, namespace.
"""

def __init__(self, name, tag, dockerfile=None, registry=None, namespace=None):
"""
Constructs all the necessary attributes for the Image object.
:param registry: Registry for the image
:param name: Name of the image
:param tag: Tag for the image
:param namespace: Namespace for the registry
"""
if not name:
raise ValueError("Image name is required")

self.name = name
self.tag = tag
self.registry = registry
self.namespace = namespace
self.__dockerfile = dockerfile
self.__cache_fqdn = None
self.__fqdn = None

def __str__(self):
return self.fqdn

@property
def local(self):
"""
Creates a string that represents the local image.
:return: Image in name:tag format
"""
if self.tag:
return self.name + ":" + self.tag
return self.name

@property
def cache_fqdn(self):
"""
Generates a Fully Qualified Domain Name for the cached image.
:return: Cached image Fully Qualified Domain Name
"""
if not self.__cache_fqdn:
self.__cache_fqdn = utils.generate_fqdn_image(
self.registry, self.namespace, self.name, DOCKER_TAG_FOR_CACHE
)
return self.__cache_fqdn

@property
def fqdn(self):
"""
Generates a Fully Qualified Domain Name for the image.
:return: image Fully Qualified Domain Name
"""
if not self.__fqdn:
self.__fqdn = utils.generate_fqdn_image(
self.registry, self.namespace, self.name, self.tag
)
return self.__fqdn

@property
def dockerfile(self):
"""
Returns the Dockerfile for the image.
:return: Dockerfile for the image
"""
if not self.__dockerfile:
self.__dockerfile = utils.image_to_dockerfile(self.name)
return self.__dockerfile

@classmethod
def from_context_obj(cls, ctx_obj):
"""
Creates an instance of Image from a given context.
:param ctx_obj: Click context object
:return: An instance of Image
"""
if ctx_obj is None:
return None

return cls(
name=ctx_obj.get("build_container_image"),
tag=ctx_obj.get("build_container_tag"),
registry=ctx_obj.get("registry"),
)


@dataclass
class BuildOptions:
"""
A class to encapsulate all the build options needed to create Docker image.
"""

def __init__(
self,
image: Image,
container_context,
build_contexts=None,
build_args=None,
use_cache=False,
):
"""
Constructs all the necessary attributes for the build options.
:param image: image details as an instance of Image
:param container_context: Context for the build
:param build_contexts: Build contexts to add to build
:param build_args: Arguments to pass to build
:param use_cache: Boolean indicating if cache should be used
"""
self.image = image
self.container_context = container_context
self.build_contexts = [ctx for ctx in build_contexts if ctx] if build_contexts else []
self.build_args = [arg for arg in build_args if arg] if build_args else []
self.use_cache = use_cache

@classmethod
def from_context_obj(cls, ctx_obj):
"""
Creates an instance of BuildOptions from a given context.
:param ctx_obj: Click context object
:return: An instance of BuildOptions
"""
if ctx_obj is None:
return None

return cls(
image=Image.from_context_obj(ctx_obj),
container_context=ctx_obj.get("container_context"),
build_contexts=ctx_obj.get("build_contexts"),
build_args=ctx_obj.get("build_args"),
use_cache=ctx_obj.get("use_cache"),
)


def build(options: BuildOptions, runner: Callable, logger: Logger) -> int:
"""
Builds a image based on given build options and runner function.
:param options: Build options as an instance of BuildOptions
:param runner: Callable that runs the Docker commands
:param logger: Logger instance
:return: A return code representing the success or failure of the build
"""
cmd = ["build", "--network=host"]

for arg in options.build_args:
cmd += ["--build-arg", arg]

for build_ctx in options.build_contexts:
cmd += ["--build-context", build_ctx]

cmd += [
"-f",
options.image.dockerfile,
"-t",
options.image.local,
options.container_context or ".",
]

if options.use_cache:
runner(["pull", options.image.cache_fqdn])
cmd.extend(["--cache-from", options.image.cache_fqdn])

ret = runner(cmd)

if ret != 0:
logger.error("Failed to build image: %s", options.image)
return ret

if options.use_cache:
runner(["tag", options.image.name, options.image.cache_fqdn])
runner(["push", options.image.cache_fqdn])

return 0
Loading

0 comments on commit 5373527

Please sign in to comment.