Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
rmohr committed Nov 15, 2018
1 parent b6fdb64 commit a41f0d6
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.idea
/bazel-*
Empty file added BUILD
Empty file.
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,64 @@
# rules_container_rpm
bazel rules to install and manage rpms inside of containers

Bazel rules to install and manage rpms inside of containers.

These rules can be used to install RPM packages into a cointainer and update its included RPM database without the need to run the container.
This allows building small and reproducible images with RPMs. Because the rpm database inside the container is also maintained, it can later be queried by any rpm binary to check what packages are installed.

## Load it into your WORKSPACE

```
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_container_rpm",
sha256 = "XYZ",
strip_prefix = "rules_container_rpm-0.0.1",
urls = ["https://github.com/rmohr/rules_container_rpm/archive/v0.0.1.tar.gz"],
)
http_file(
name = "glibc",
url = "https://dl.fedoraproject.org/pub/fedora/linux/releases/28/Everything/x86_64/os/Packages/g/glibc-2.27-8.fc28.x86_64.rpm",
sha256 = "573ceb6ad74b919b06bddd7684a29ef75bc9f3741e067fac1414e05c0087d0b6"
)
```

## Use it in your BUILD file


```
load(
"@io_bazel_rules_docker//container:container.bzl",
"container_image",
)
load(
"@io_bazel_rules_container_rpm//rpm:rpm.bzl",
"rpm_image",
)
container_image(
name = "files_base",
files = ["foo"],
mode = "0o644",
)
rpm_image(
name = "allinone",
base = ":files_base",
rpms = ["@glibc//file", "@ca_certificates//file"],
)
rpm_image(
name = "image2",
base = ":image1",
rpms = ["@ca_certificates//file"],
)
rpm_image(
name = "image1",
base = ":files_base",
rpms = ["@glibc//file"],
)
```
27 changes: 27 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "io_bazel_rules_docker",
sha256 = "29d109605e0d6f9c892584f07275b8c9260803bf0c6fcb7de2623b2bedc910bd",
strip_prefix = "rules_docker-0.5.1",
urls = ["https://github.com/bazelbuild/rules_docker/archive/v0.5.1.tar.gz"],
)

load(
"@io_bazel_rules_docker//container:container.bzl",
container_repositories = "repositories",
)

container_repositories()

http_file(
name = "glibc",
url = "https://dl.fedoraproject.org/pub/fedora/linux/releases/28/Everything/x86_64/os/Packages/g/glibc-2.27-8.fc28.x86_64.rpm",
sha256 = "573ceb6ad74b919b06bddd7684a29ef75bc9f3741e067fac1414e05c0087d0b6"
)

http_file(
name = "ca_certificates",
url = "https://dl.fedoraproject.org/pub/fedora/linux/releases/28/Everything/x86_64/os/Packages/c/ca-certificates-2018.2.22-3.fc28.noarch.rpm",
sha256 = "dfc3d2bf605fbea7db7f018af53fe0563628f788a40cb1e7f84434606b7b6a12"
)
26 changes: 26 additions & 0 deletions rpm/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# 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.
package(default_visibility = ["//visibility:public"])

licenses(["notice"]) # Apache 2.0

py_binary(
name = "install_rpms",
srcs = [":install_rpms.py"],
srcs_version = "PY2AND3",
visibility = ["//visibility:public"],
deps = [
"@bazel_tools//tools/build_defs/pkg:archive",
],
)
53 changes: 53 additions & 0 deletions rpm/install_rpms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import subprocess
import tempfile
import shutil
import tarfile
import argparse
from os import path


parser = argparse.ArgumentParser(
description='Install RPMs and update the RPM database inside the docker image')

parser.add_argument('--uncompressed_layer', action='append', required=True,
help='The output file, mandatory')

parser.add_argument('--rpm', action='append', required=True,
help=('rpms to add to the database'))

parser.add_argument('--output', action='store', required=True,
help=('target archive'))

def main():
args = parser.parse_args()
dirpath = tempfile.mkdtemp()
rpmdb = path.join(dirpath, "var/lib/rpm")
try:
# Uncompress the latest database state into a temporary directory
for tar in args.uncompressed_layer:
with tarfile.open(tar, "r") as archive:
for member in archive.getmembers():
if member.name.startswith(("/var/lib/rpm", "./var/lib/rpm")):
archive.extract(member, dirpath)

# Add the rpm database if it is not there
subprocess.check_call(["rpm", "--dbpath", rpmdb, "--initdb"])

# Register the RPMs in the database
for rpm in args.rpm:
subprocess.check_call(["rpm", "--nosignature", "--dbpath", rpmdb, "-i", "-v", "--ignoresize", "--nodeps", "--noscripts" ,"--notriggers" ,"--excludepath", "/", rpm])

# Extract the rpms into the shared folder
for rpm in args.rpm:
p1 = subprocess.Popen(["rpm2cpio", rpm], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["cpio", "-i", "-d", "-m", "-v", "-D", dirpath], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
p2.communicate()

with tarfile.open(args.output, "a") as tar:
tar.add(dirpath, arcname="/")
finally:
shutil.rmtree(dirpath)

if __name__ == '__main__':
main()
52 changes: 52 additions & 0 deletions rpm/rpm.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
load(
"@io_bazel_rules_docker//container:container.bzl",
_container = "container",
)
load(
"@io_bazel_rules_docker//container:layer_tools.bzl",
_get_layers = "get_from_target",
)

def rpm_image(**kwargs):
_rpms_layer(**kwargs)

def _rpms_impl(ctx, rpms = None):
rpms = rpms or ctx.files.rpms
rpm_installer = ctx.executable._rpm_installer
parent_parts = _get_layers(ctx, ctx.label.name, ctx.attr.base)
uncompressed_blobs = parent_parts.get("unzipped_layer", [])
uncompressed_layer_args = ["--uncompressed_layer=" + f.path for f in uncompressed_blobs]
rpm_args = ["--rpm=" + f.path for f in rpms]
finaltar = ctx.actions.declare_file(ctx.label.name + "-installed-rpms.tar")
target = "--output=%s" % finaltar.path
ctx.actions.run(
executable = rpm_installer,
arguments = rpm_args + uncompressed_layer_args + [target],
inputs = rpms + uncompressed_blobs,
outputs = [finaltar],
use_default_shell_env = True,
progress_message = "Install RPMs inside a container",
mnemonic = "installrpms",
)
tars = [finaltar]
if ctx.attr.tars:
tars = tars + ctx.attr.tars
return _container.image.implementation(ctx, tars = tars)

_rpms_layer = rule(
attrs = dict(_container.image.attrs.items() + {
# The dependency whose runfiles we're appending.
"rpms": attr.label_list(allow_files = True, mandatory = True),
"_rpm_installer": attr.label(
default = Label("//rpm:install_rpms"),
cfg = "host",
executable = True,
allow_files = True,
),
}.items()),
executable = True,
outputs = _container.image.outputs,
# TODO: rpm toolchain?
#toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _rpms_impl,
)
19 changes: 19 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@io_bazel_rules_docker//contrib:test.bzl", "container_test")

container_test(
name = "image1_test",
configs = ["configs/image1.yaml"],
image = "//testdata:image1",
)

container_test(
name = "image2_test",
configs = ["configs/image2.yaml"],
image = "//testdata:image2",
)

container_test(
name = "allinone_test",
configs = ["configs/allinone.yaml"],
image = "//testdata:allinone",
)
14 changes: 14 additions & 0 deletions test/configs/allinone.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
schemaVersion: '2.0.0'
fileExistenceTests:
- name: 'ldconfig binary from glibc'
path: '/sbin/ldconfig'
shouldExist: true
permissions: '-rwxr-xr-x'
- name: 'rpm database file'
path: '/var/lib/rpm/Packages'
shouldExist: true
permissions: '-rw-r--r--'
- name: 'readme from ca-certificates'
path: '/usr/share/pki/ca-trust-source/README'
shouldExist: true
permissions: '-rw-r--r--'
13 changes: 13 additions & 0 deletions test/configs/image1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
schemaVersion: '2.0.0'
fileExistenceTests:
- name: 'ldconfig binary from glibc'
path: '/sbin/ldconfig'
shouldExist: true
permissions: '-rwxr-xr-x'
- name: 'rpm database file'
path: '/var/lib/rpm/Packages'
shouldExist: true
permissions: '-rw-r--r--'
- name: 'readme from ca-certificates'
path: '/usr/share/pki/ca-trust-source/README'
shouldExist: false
14 changes: 14 additions & 0 deletions test/configs/image2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
schemaVersion: '2.0.0'
fileExistenceTests:
- name: 'ldconfig binary from glibc'
path: '/sbin/ldconfig'
shouldExist: true
permissions: '-rwxr-xr-x'
- name: 'rpm database file'
path: '/var/lib/rpm/Packages'
shouldExist: true
permissions: '-rw-r--r--'
- name: 'readme from ca-certificates'
path: '/usr/share/pki/ca-trust-source/README'
shouldExist: true
permissions: '-rw-r--r--'
35 changes: 35 additions & 0 deletions testdata/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])

load(
"@io_bazel_rules_docker//container:container.bzl",
"container_image",
)

load(
"//rpm:rpm.bzl",
"rpm_image",
)

container_image(
name = "files_base",
files = ["foo"],
mode = "0o644",
)

rpm_image(
name = "allinone",
base = ":files_base",
rpms = ["@glibc//file", "@ca_certificates//file"],
)

rpm_image(
name = "image2",
base = ":image1",
rpms = ["@ca_certificates//file"],
)

rpm_image(
name = "image1",
base = ":files_base",
rpms = ["@glibc//file"],
)
1 change: 1 addition & 0 deletions testdata/foo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
asdf

0 comments on commit a41f0d6

Please sign in to comment.