feat: add blockd service (#172)

This commit is contained in:
Andrew Rynhard 2018-11-01 22:05:24 -07:00 committed by GitHub
parent c8de882c67
commit aa65101232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2623 additions and 62 deletions

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,283 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Autonomy</title>
<meta name="description" content="">
<meta name="author" content="andrew.rynhard@autonomy.io">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Raleway|Fira+Mono|Roboto:300" rel="stylesheet">
<link rel="icon" type="image/png" href="https://dianemo.autonomy.io/img/favicon.png">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js"></script>
<script src="https://dianemo.autonomy.io/js/search.js"></script>
<link rel="stylesheet" href="https://dianemo.autonomy.io//css/milligram.min.css">
<link rel="stylesheet" href="https://dianemo.autonomy.io/css/main.css">
</head>
<nav class="navbar">
<div class="container">
<div class="row">
<div class="column column-50">
<ul class="navbar-list navbar-left">
<li class="navbar-item">
<a class="navbar-link logo" href="/">
<img src="https://dianemo.autonomy.io//img/logo.svg" class="logo">
</a>
</li>
</ul>
</div>
<div class="column column-50">
<ul class="navbar-list navbar-right">
<li class="navbar-item">
<a class="navbar-link navbar-logo" rel="noopener noreferrer" href="https://github.com/autonomy/dianemo" target="_blank">
<span class="octicon octicon-mark-github"></span>
</a>
</li>
<li class="navbar-item">
<a class="navbar-link navbar-logo" rel="noopener noreferrer" href="https://hub.docker.com/u/autonomy" target="_blank">
<span class="fab fa-docker"></span>
</a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<script id="search-result-template" type="text/x-js-template">
<li class="sidebar-item">
<div id="summary-${key}">
<a class="sidebar-link" href="${link}">${title}</a>
<p class="search-result-item">${preview}</p>
</div>
</li>
</script>
<nav class="sidebar">
<div class="row">
<div class="column">
<span>
<a class="logo" href="https://dianemo.autonomy.io/">
<img src="https://dianemo.autonomy.io//img/logo.svg" class="logo">
</a>
</span>
</div>
</div>
<hr>
<div class="row">
<div class="column">
<div class="button-group button-group-center">
<a class="button" href="https://github.com/autonomy/dianemo/fork">
<span class="octicon octicon-repo-forked"></span>
Fork
</a>
<a class="button" href="https://github.com/autonomy/dianemo/stargazers">
<span class="octicon octicon-star"></span>
Star
</a>
</div>
</div>
</div>
<hr>
<div class="row search-area">
<form class="search-form" action="" onSubmit="return">
<input class="search-box" id="search-query" name="s" type="text" placeholder="search" />
</form>
<ul class="sidebar-list search-results" id="search-results">
</ul>
</div>
<div class="row">
<div class="column">
<ul class="sidebar-list parent">
<li class="sidebar-item">
<a class="sidebar-link sidebar-link-parent active"
href="https://dianemo.autonomy.io/components/" >
Components
</a>
<ul class="sidebar-list active">
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/kernel/" >
kernel
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/init/" >
init
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/kubeadm/" >
kubeadm
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/trustd/" >
trustd
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/proxyd/" >
proxyd
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/osd/" >
osd
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/osctl/" >
osctl
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link active"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>
<li class="sidebar-item">
<a class="sidebar-link sidebar-link-parent"
href="https://dianemo.autonomy.io/configuration/" >
Configuration
</a>
<ul class="sidebar-list">
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/configuration/controlplane/" >
Control Plane
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/configuration/workers/" >
Workers
</a>
</li>
</ul>
</li>
<li class="sidebar-item">
<a class="sidebar-link sidebar-link-parent"
href="https://dianemo.autonomy.io/examples/" >
Examples
</a>
<ul class="sidebar-list">
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/examples/aws/" >
AWS
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/examples/kvm/" >
KVM
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<body>
<div class="container">
<div class="content">
<div class="row ">
<div class="column column-10">
<a class="navigation navigation-previous" href="https://dianemo.autonomy.io/components/osctl/">
<i class="fa fa-chevron-left"></i>
</a>
</div>
<div class="column document">
<section class="document">
<h1 class="title">blockd</h1>
<p><p>Dianemo comes with a reserved block device with three partitions:</p>
<ul>
<li>an EFI System Partition (<code>ESP</code>)</li>
<li>a <code>ROOT</code> partition mounted as read-only that contains the minimal set of binaries to operate system services</li>
<li>and a <code>DATA</code> partion that is mounted as read/write at <code>/var/run</code></li>
</ul>
<p>These partitions are reserved and cannot be modified.
The one expection to this is that the <code>DATA</code> partition will be resized automatically in the <code>init</code> process to the maximum size possible.
Managing any other block device can be done via the <code>blockd</code> service.</p>
</p>
</section>
</div>
<div class="column column-10">
</div>
</div>
</div>
</div>
</body>
<div class="footer">
<aside class="copyright">
&copy; 2018 Released under Mozilla Public License 2.0
</aside>
</div>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -79,5 +79,15 @@ To make this work, we needed an out-of-band tool for managing the nodes. In an i
retrieve container logs restart a service reset a node reboot a node retrieve kernel logs generate pki resources inject data into node configuration files </description>
</item>
<item>
<title>blockd</title>
<link>https://dianemo.autonomy.io/components/blockd/</link>
<pubDate>Tue, 30 Oct 2018 09:16:35 -0700</pubDate>
<guid>https://dianemo.autonomy.io/components/blockd/</guid>
<description>Dianemo comes with a reserved block device with three partitions:
an EFI System Partition (ESP) a ROOT partition mounted as read-only that contains the minimal set of binaries to operate system services and a DATA partion that is mounted as read/write at /var/run These partitions are reserved and cannot be modified. The one expection to this is that the DATA partition will be resized automatically in the init process to the maximum size possible.</description>
</item>
</channel>
</rss>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>
@ -261,6 +268,10 @@ With it you can do things like:</p>
</div>
<div class="column column-10">
<a class="navigation navigation-next" href="https://dianemo.autonomy.io/components/blockd/">
<i class="fa fa-chevron-right"></i>
</a>
</div>
</div>
</div>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -171,6 +171,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

File diff suppressed because one or more lines are too long

View File

@ -79,6 +79,16 @@ To make this work, we needed an out-of-band tool for managing the nodes. In an i
retrieve container logs restart a service reset a node reboot a node retrieve kernel logs generate pki resources inject data into node configuration files </description>
</item>
<item>
<title>blockd</title>
<link>https://dianemo.autonomy.io/components/blockd/</link>
<pubDate>Tue, 30 Oct 2018 09:16:35 -0700</pubDate>
<guid>https://dianemo.autonomy.io/components/blockd/</guid>
<description>Dianemo comes with a reserved block device with three partitions:
an EFI System Partition (ESP) a ROOT partition mounted as read-only that contains the minimal set of binaries to operate system services and a DATA partion that is mounted as read/write at /var/run These partitions are reserved and cannot be modified. The one expection to this is that the DATA partition will be resized automatically in the init process to the maximum size possible.</description>
</item>
<item>
<title>AWS</title>
<link>https://dianemo.autonomy.io/examples/aws/</link>

View File

@ -37,6 +37,11 @@
<lastmod>2018-10-29T19:40:55-07:00</lastmod>
</url>
<url>
<loc>https://dianemo.autonomy.io/components/blockd/</loc>
<lastmod>2018-10-30T09:16:35-07:00</lastmod>
</url>
<url>
<loc>https://dianemo.autonomy.io/examples/aws/</loc>
<lastmod>2018-10-29T19:40:55-07:00</lastmod>

View File

@ -170,6 +170,13 @@
</a>
</li>
<li class="sidebar-item">
<a class="sidebar-link"
href="https://dianemo.autonomy.io/components/blockd/" >
blockd
</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,19 @@
---
title: "blockd"
date: 2018-10-30T09:16:35-07:00
draft: false
menu:
main:
parent: 'components'
weight: 80
---
Dianemo comes with a reserved block device with three partitions:
- an EFI System Partition (`ESP`)
- a `ROOT` partition mounted as read-only that contains the minimal set of binaries to operate system services
- and a `DATA` partion that is mounted as read/write at `/var/run`
These partitions are reserved and cannot be modified.
The one expection to this is that the `DATA` partition will be resized automatically in the `init` process to the maximum size possible.
Managing any other block device can be done via the `blockd` service.

View File

@ -64,7 +64,7 @@ tasks:
COPY src/packer.json /packer.json
COPY src/entrypoint.sh /bin/entrypoint.sh
RUN chmod +x /bin/entrypoint.sh
{{ if .Git.IsTag }}
{{ if and .Git.IsClean .Git.IsTag }}
ENV VERSION {{ .Git.Tag }}
{{ else }}
ENV VERSION {{ .Git.SHA }}

View File

@ -11,33 +11,43 @@ function create_image() {
if [ "$FULL" = true ] ; then
if [ "$RAW" = true ] ; then
parted -s -a optimal ${RAW_IMAGE} mkpart ESP fat32 0 $((${INITRAMFS_SIZE} + 50))M
parted -s -a optimal ${RAW_IMAGE} mkpart ROOT xfs $((${INITRAMFS_SIZE} + 50))M $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M
parted -s -a optimal ${RAW_IMAGE} mkpart DATA xfs $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M 100%
parted -s -a optimal ${RAW_IMAGE} mkpart primary fat32 0 $((${INITRAMFS_SIZE} + 50))M
parted ${RAW_IMAGE} name 1 ESP
parted -s -a optimal ${RAW_IMAGE} mkpart primary xfs $((${INITRAMFS_SIZE} + 50))M $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M
parted ${RAW_IMAGE} name 2 ROOT
parted -s -a optimal ${RAW_IMAGE} mkpart primary xfs $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M 100%
parted ${RAW_IMAGE} name 3 DATA
losetup ${DEVICE} ${RAW_IMAGE}
partx -av ${DEVICE}
extract_boot_partition ${DEVICE}p1
extract_root_partition ${DEVICE}p2
extract_data_partition ${DEVICE}p3
else
parted -s -a optimal ${DEVICE} mkpart ESP fat32 0 $((${INITRAMFS_SIZE} + 50))M
parted -s -a optimal ${DEVICE} mkpart ROOT xfs $((${INITRAMFS_SIZE} + 50))M $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M
parted -s -a optimal ${DEVICE} mkpart DATA xfs $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M 100%
parted -s -a optimal ${DEVICE} mkpart primary fat32 0 $((${INITRAMFS_SIZE} + 50))M
parted ${DEVICE} name 1 ESP
parted -s -a optimal ${DEVICE} mkpart primary xfs $((${INITRAMFS_SIZE} + 50))M $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M
parted ${DEVICE} name 2 ROOT
parted -s -a optimal ${DEVICE} mkpart primary xfs $((${ROOTFS_SIZE} + ${INITRAMFS_SIZE} + 100))M 100%
parted ${DEVICE} name 3 DATA
extract_boot_partition ${DEVICE}1
extract_root_partition ${DEVICE}2
extract_data_partition ${DEVICE}3
fi
else
if [ "$RAW" = true ] ; then
parted -s -a optimal ${RAW_IMAGE} mkpart ROOT xfs 0 $((${ROOTFS_SIZE} + 50))M
parted -s -a optimal ${RAW_IMAGE} mkpart DATA xfs $((${ROOTFS_SIZE} + 50))M 100%
parted -s -a optimal ${RAW_IMAGE} mkpart primary xfs 0 $((${ROOTFS_SIZE} + 50))M
parted ${RAW_IMAGE} name 1 ROOT
parted -s -a optimal ${RAW_IMAGE} mkpart primary xfs $((${ROOTFS_SIZE} + 50))M 100%
parted ${RAW_IMAGE} name 2 DATA
losetup ${DEVICE} ${RAW_IMAGE}
partx -av ${DEVICE}
extract_root_partition ${DEVICE}p1
extract_data_partition ${DEVICE}p2
else
parted -s -a optimal ${DEVICE} mkpart ROOT xfs 0 $((${ROOTFS_SIZE} + 50))M
parted -s -a optimal ${DEVICE} mkpart DATA xfs $((${ROOTFS_SIZE} + 50))M 100%
parted -s -a optimal ${DEVICE} mkpart primary xfs 0 $((${ROOTFS_SIZE} + 50))M
parted ${DEVICE} name 1 ROOT
parted -s -a optimal ${DEVICE} mkpart primary xfs $((${ROOTFS_SIZE} + 50))M 100%
parted ${DEVICE} name 2 DATA
extract_root_partition ${DEVICE}1
extract_data_partition ${DEVICE}2
fi

View File

@ -3,6 +3,7 @@ metadata:
variables:
rootfs: /rootfs
versionPath: github.com/autonomy/dianemo/src/initramfs/pkg/version
srcXfsprogs: https://www.kernel.org/pub/linux/utils/fs/xfs/xfsprogs/xfsprogs-4.18.0.tar.xz
script:
template: |
#!/bin/sh
@ -18,6 +19,9 @@ script:
docker tag {{ .Repository }}:proxyd autonomy/proxyd:{{ .Git.SHA }}
docker save autonomy/proxyd:{{ .Git.SHA }} -o ../../build/proxyd.tar
docker tag {{ .Repository }}:blockd autonomy/blockd:{{ .Git.SHA }}
docker save autonomy/blockd:{{ .Git.SHA }} -o ../../build/blockd.tar
{{ if and (.Git.IsClean) (or (.Git.IsTag) (eq .Git.Branch "master")) }}
docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD
@ -32,21 +36,27 @@ script:
docker tag autonomy/proxyd:{{ .Git.SHA }} autonomy/proxyd:latest
docker push autonomy/proxyd:{{ .Git.SHA }}
docker push autonomy/proxyd:latest
docker tag autonomy/blockd:{{ .Git.SHA }} autonomy/blockd:latest
docker push autonomy/blockd:{{ .Git.SHA }}
docker push autonomy/blockd:latest
{{ end }}
pipeline:
stages:
- generate
- base
- initramfs
- osd
- osctl
- trustd
- proxyd
- initramfs
- blockd
- test
- image
stages:
base:
tasks:
- src
- test
osd:
tasks:
- osd
@ -64,10 +74,16 @@ stages:
proxyd:
tasks:
- proxyd
blockd:
tasks:
- blockd
initramfs:
tasks:
- xfsprogs
- init
- initramfs
image:
tasks:
- image
generate:
artifacts:
@ -75,26 +91,47 @@ stages:
destination: ./cmd/osd
- source: /src/github.com/autonomy/dianemo/src/initramfs/cmd/trustd/proto
destination: ./cmd/trustd
- source: /src/github.com/autonomy/dianemo/src/initramfs/cmd/blockd/proto
destination: ./cmd/blockd
tasks:
- proto
test:
tasks:
- test
tasks:
image:
template: |
FROM scratch
WORKDIR /tmp
COPY --from=init /initramfs/initramfs.xz initramfs.xz
CMD false
init:
blockd:
template: |
FROM {{ .Repository }}:base AS {{ .Docker.CurrentStage }}
WORKDIR /src/github.com/autonomy/dianemo/src/initramfs/cmd/{{ .Docker.CurrentStage }}
{{ if and .Git.IsClean .Git.IsTag }}
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a \
-ldflags "-s -w -X {{ index .Variables "versionPath" }}.Name=Blockd -X {{ index .Variables "versionPath" }}.Tag={{ .Git.Tag }} -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }} -X \"{{ index .Variables "versionPath" }}.Built={{ .Built }}\"" \
-o /{{ .Docker.CurrentStage }}
{{ else }}
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a \
-ldflags "-s -w -X {{ index .Variables "versionPath" }}.Name=Blockd -X {{ index .Variables "versionPath" }}.Tag=none -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }}" \
-o /{{ .Docker.CurrentStage }}
{{ end }}
RUN chmod +x /{{ .Docker.CurrentStage }}
FROM scratch
COPY --from={{ .Docker.CurrentStage }} /{{ .Docker.CurrentStage }} /{{ .Docker.CurrentStage }}
ENTRYPOINT ["/{{ .Docker.CurrentStage }}"]
image:
template: |
FROM scratch
WORKDIR /tmp
COPY --from=dianemo/initramfs:initramfs /initramfs/initramfs.xz initramfs.xz
CMD false
init:
template: |
WORKDIR /src/github.com/autonomy/dianemo/src/initramfs/cmd/{{ .Docker.CurrentStage }}
{{ if and .Git.IsClean .Git.IsTag }}
RUN GOOS=linux GOARCH=amd64 go build \
-ldflags "-s -w -linkmode external -extldflags \"-L/lib -lblkid -luuid\" -X {{ index .Variables "versionPath" }}.Name=Dianemo -X {{ index .Variables "versionPath" }}.Tag={{ .Git.Tag }} -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }} -X \"{{ index .Variables "versionPath" }}.Built={{ .Built }}\"" \
-ldflags "-s -w -X {{ index .Variables "versionPath" }}.Name=Dianemo -X {{ index .Variables "versionPath" }}.Tag={{ .Git.Tag }} -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }} -X \"{{ index .Variables "versionPath" }}.Built={{ .Built }}\"" \
-o /initramfs/init
{{ else }}
RUN GOOS=linux GOARCH=amd64 go build \
-ldflags "-s -w -linkmode external -extldflags \"-L/lib -lblkid -luuid\" -X {{ index .Variables "versionPath" }}.Name=Dianemo -X {{ index .Variables "versionPath" }}.Tag=none -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }}" \
-ldflags "-s -w -X {{ index .Variables "versionPath" }}.Name=Dianemo -X {{ index .Variables "versionPath" }}.Tag=none -X {{ index .Variables "versionPath" }}.SHA={{ .Git.SHA }}" \
-o /initramfs/init
{{ end }}
RUN chmod +x /initramfs/init
@ -110,7 +147,7 @@ tasks:
{{ else }}
RUN find . 2>/dev/null | cpio -H newc -o | xz -v -C crc32 -0 -e -T 0 -z >/tmp/initramfs.xz
{{ end }}
RUN cp /tmp/initramfs.xz /initramfs/initramfs.xz
RUN cp /tmp/initramfs.xz .
osctl:
template: |
FROM {{ .Repository }}:base AS {{ .Docker.CurrentStage }}
@ -166,6 +203,9 @@ tasks:
WORKDIR /src/github.com/autonomy/dianemo/src/initramfs/cmd/trustd
COPY ./cmd/trustd/proto ./proto
RUN protoc -I/usr/local/include -I./proto --go_out=plugins=grpc:proto proto/api.proto
WORKDIR /src/github.com/autonomy/dianemo/src/initramfs/cmd/blockd
COPY ./cmd/blockd/proto ./proto
RUN protoc -I/usr/local/include -I./proto --go_out=plugins=grpc:proto proto/api.proto
proxyd:
template: |
FROM {{ .Repository }}:base AS {{ .Docker.CurrentStage }}
@ -199,6 +239,7 @@ tasks:
RUN go mod verify
test:
template: |
FROM {{ .Repository }}:base AS {{ .Docker.CurrentStage }}
WORKDIR /src/github.com/autonomy/dianemo/src/initramfs
RUN chmod +x ./hack/test.sh
RUN ./hack/test.sh --lint ./hack/golangci-lint.yaml
@ -220,3 +261,14 @@ tasks:
FROM scratch
COPY --from={{ .Docker.CurrentStage }} /{{ .Docker.CurrentStage }} /{{ .Docker.CurrentStage }}
ENTRYPOINT ["/{{ .Docker.CurrentStage }}"]
xfsprogs:
template: |
FROM {{ .Repository }}:base AS {{ .Docker.CurrentStage }}
WORKDIR /tmp/{{ .Docker.CurrentStage }}
RUN curl -L {{index .Variables "srcXfsprogs" }} | tar -xJ --strip-components=1
RUN make \
DEBUG=-DNDEBUG \
INSTALL_USER=0 \
INSTALL_GROUP=0 \
LOCAL_CONFIGURE_OPTIONS="--prefix=/usr"
RUN make install DESTDIR={{ index .Variables "rootfs" }}

View File

@ -0,0 +1,65 @@
package main
import (
"flag"
"log"
"github.com/autonomy/dianemo/src/initramfs/cmd/trustd/pkg/reg"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/factory"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/gen"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/tls"
"github.com/autonomy/dianemo/src/initramfs/pkg/userdata"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
var (
dataPath *string
generate *bool
trustdPort *int
)
func init() {
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds | log.Ltime)
dataPath = flag.String("userdata", "", "the path to the user data")
trustdPort = flag.Int("trustd-port", 50001, "the trustd port")
generate = flag.Bool("generate", false, "generate the TLS certificate using one of the Root of Trusts")
flag.Parse()
}
func main() {
data, err := userdata.Open(*dataPath)
if err != nil {
log.Fatalf("open user data: %v", err)
}
if *generate {
var generator *gen.Generator
generator, err = gen.NewGenerator(data, *trustdPort)
if err != nil {
log.Fatal(err)
}
if err = generator.Identity(data.Security); err != nil {
log.Fatalf("generate identity: %v", err)
}
}
config, err := tls.NewConfig(tls.Mutual, data.Security.OS)
if err != nil {
log.Fatalf("credentials: %v", err)
}
log.Println("Starting blockd")
err = factory.Listen(
&reg.Registrator{Data: data.Security.OS},
factory.Network("unix"),
factory.ServerOptions(
grpc.Creds(
credentials.NewTLS(config),
),
),
)
if err != nil {
log.Fatalf("listen: %v", err)
}
}

View File

@ -0,0 +1,26 @@
package reg
import (
"context"
"github.com/autonomy/dianemo/src/initramfs/cmd/blockd/proto"
"github.com/autonomy/dianemo/src/initramfs/pkg/userdata"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
)
// Registrator is the concrete type that implements the factory.Registrator and
// proto.BlockdServer interfaces.
type Registrator struct {
Data *userdata.OSSecurity
}
// Register implements the factory.Registrator interface.
func (r *Registrator) Register(s *grpc.Server) {
proto.RegisterBlockdServer(s, r)
}
// Resize implements the proto.BlockdServer interface.
func (r *Registrator) Resize(ctx context.Context, in *proto.ResizePartitionRequest) (reply *empty.Empty, err error) {
return nil, nil
}

View File

@ -0,0 +1,165 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: api.proto
package proto
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
empty "github.com/golang/protobuf/ptypes/empty"
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// The request message containing the process name.
type ResizePartitionRequest struct {
Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ResizePartitionRequest) Reset() { *m = ResizePartitionRequest{} }
func (m *ResizePartitionRequest) String() string { return proto.CompactTextString(m) }
func (*ResizePartitionRequest) ProtoMessage() {}
func (*ResizePartitionRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{0}
}
func (m *ResizePartitionRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ResizePartitionRequest.Unmarshal(m, b)
}
func (m *ResizePartitionRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ResizePartitionRequest.Marshal(b, m, deterministic)
}
func (m *ResizePartitionRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ResizePartitionRequest.Merge(m, src)
}
func (m *ResizePartitionRequest) XXX_Size() int {
return xxx_messageInfo_ResizePartitionRequest.Size(m)
}
func (m *ResizePartitionRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ResizePartitionRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ResizePartitionRequest proto.InternalMessageInfo
func (m *ResizePartitionRequest) GetNumber() int32 {
if m != nil {
return m.Number
}
return 0
}
func (m *ResizePartitionRequest) GetSize() int64 {
if m != nil {
return m.Size
}
return 0
}
func init() {
proto.RegisterType((*ResizePartitionRequest)(nil), "proto.ResizePartitionRequest")
}
func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) }
var fileDescriptor_00212fb1f9d3bf1c = []byte{
// 163 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0xc8, 0xd4,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x52, 0xd2, 0xe9, 0xf9, 0xf9, 0xe9, 0x39,
0xa9, 0xfa, 0x60, 0x5e, 0x52, 0x69, 0x9a, 0x7e, 0x6a, 0x6e, 0x41, 0x49, 0x25, 0x44, 0x8d, 0x92,
0x0b, 0x97, 0x58, 0x50, 0x6a, 0x71, 0x66, 0x55, 0x6a, 0x40, 0x62, 0x51, 0x49, 0x66, 0x49, 0x66,
0x7e, 0x5e, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89, 0x90, 0x18, 0x17, 0x5b, 0x5e, 0x69, 0x6e,
0x52, 0x6a, 0x91, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x94, 0x27, 0x24, 0xc4, 0xc5, 0x02,
0x52, 0x2f, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x1c, 0x04, 0x66, 0x1b, 0x79, 0x73, 0xb1, 0x39, 0xe5,
0xe4, 0x27, 0x67, 0xa7, 0x08, 0x39, 0x72, 0xb1, 0x41, 0xcc, 0x13, 0x92, 0x85, 0xd8, 0xa0, 0x87,
0xdd, 0x78, 0x29, 0x31, 0x3d, 0x88, 0xb3, 0xf4, 0x60, 0xce, 0xd2, 0x73, 0x05, 0x39, 0x4b, 0x89,
0x21, 0x89, 0x0d, 0x2c, 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xa6, 0xfc, 0x26, 0x27, 0xca,
0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// BlockdClient is the client API for Blockd service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BlockdClient interface {
Resize(ctx context.Context, in *ResizePartitionRequest, opts ...grpc.CallOption) (*empty.Empty, error)
}
type blockdClient struct {
cc *grpc.ClientConn
}
func NewBlockdClient(cc *grpc.ClientConn) BlockdClient {
return &blockdClient{cc}
}
func (c *blockdClient) Resize(ctx context.Context, in *ResizePartitionRequest, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
err := c.cc.Invoke(ctx, "/proto.Blockd/Resize", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// BlockdServer is the server API for Blockd service.
type BlockdServer interface {
Resize(context.Context, *ResizePartitionRequest) (*empty.Empty, error)
}
func RegisterBlockdServer(s *grpc.Server, srv BlockdServer) {
s.RegisterService(&_Blockd_serviceDesc, srv)
}
func _Blockd_Resize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResizePartitionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BlockdServer).Resize(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.Blockd/Resize",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BlockdServer).Resize(ctx, req.(*ResizePartitionRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Blockd_serviceDesc = grpc.ServiceDesc{
ServiceName: "proto.Blockd",
HandlerType: (*BlockdServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Resize",
Handler: _Blockd_Resize_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api.proto",
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
package proto;
import "google/protobuf/empty.proto";
// The Blockd service definition.
service Blockd {
rpc Resize(ResizePartitionRequest) returns (google.protobuf.Empty) {}
}
// The request message containing the process name.
message ResizePartitionRequest {
int32 number = 1;
int64 size = 2;
}

View File

@ -104,6 +104,7 @@ func root() (err error) {
&services.Containerd{},
&services.CRT{},
&services.OSD{},
&services.Blockd{},
&services.Kubelet{},
&services.Kubeadm{},
)

View File

@ -0,0 +1,27 @@
package util
import "C"
import (
"strings"
)
func PartNo(partname string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
idx := strings.Index(partname, "p")
return partname[idx+1:]
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz")
}
return ""
}
func DevnameFromPartname(partname, partno string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
return strings.TrimRight(partname, "p"+partno)
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimRight(partname, partno)
}
return ""
}

View File

@ -0,0 +1,102 @@
package util
import (
"testing"
)
func Test_PartNo(t *testing.T) {
type args struct {
devname string
}
tests := []struct {
name string
args args
want string
}{
{
name: "hda1",
args: args{
devname: "/dev/hda1",
},
want: "1",
},
{
name: "hda10",
args: args{
devname: "/dev/hda10",
},
want: "10",
},
{
name: "sda1",
args: args{
devname: "/dev/sda1",
},
want: "1",
},
{
name: "sda10",
args: args{
devname: "/dev/sda10",
},
want: "10",
},
{
name: "nvme1n2p2",
args: args{
devname: "/dev/nvme1n2p2",
},
want: "2",
},
{
name: "nvme1n2p11",
args: args{
devname: "/dev/nvme1n2p11",
},
want: "11",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := PartNo(tt.args.devname); got != tt.want {
t.Errorf("PartNo() = %v, want %v", got, tt.want)
}
})
}
}
func Test_DevnameFromPartname(t *testing.T) {
type args struct {
devname string
partno string
}
tests := []struct {
name string
args args
want string
}{
{
name: "hda1",
args: args{
devname: "/dev/hda1",
partno: PartNo("/dev/hda1"),
},
want: "/dev/hda",
},
{
name: "nvme1n2p11",
args: args{
devname: "/dev/nvme1n2p11",
partno: PartNo("/dev/nvme1n2p11"),
},
want: "/dev/nvme1n2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := DevnameFromPartname(tt.args.devname, tt.args.partno); got != tt.want {
t.Errorf("DevnameFromPartname() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,25 @@
// Package xfs provides an interface to xfsprogs.
package xfs
import (
"os"
"os/exec"
)
// GrowFS expands an XFS filesystem to the maximum possible. The partition
// MUST be mounted, or this will fail.
func GrowFS(partname string) error {
return cmd("xfs_growfs", "-d", partname)
}
func cmd(name string, arg ...string) error {
cmd := exec.Command(name, arg...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
err := cmd.Start()
if err != nil {
return err
}
return cmd.Wait()
}

View File

@ -6,10 +6,14 @@ import (
"fmt"
"os"
"path"
"strings"
"sync"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/constants"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/fs/xfs"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/mount/blkid"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice"
gptpartition "github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table/gpt/partition"
"golang.org/x/sys/unix"
)
@ -62,7 +66,11 @@ func Init(s string) (err error) {
if err = mountSpecialDevices(); err != nil {
return
}
if err = mountBlockDevices(s); err != nil {
blockdevices, err := probe()
if err != nil {
return fmt.Errorf("probe block devices: %s", err.Error())
}
if err = mountBlockDevices(blockdevices, s); err != nil {
return
}
@ -168,12 +176,56 @@ func mountSpecialDevices() (err error) {
return nil
}
func mountBlockDevices(s string) (err error) {
probed, err := probe()
if err != nil {
return fmt.Errorf("probe block devices: %s", err.Error())
// nolint: gocyclo
func fixDataPartition(blockdevices []*BlockDevice) error {
for _, b := range blockdevices {
if b.LABEL == constants.DataPartitionLabel {
devname := devnameFromPartname(b.dev)
bd, err := blockdevice.Open(devname)
if err != nil {
return err
}
// nolint: errcheck
defer bd.Close()
pt, err := bd.PartitionTable()
if err != nil {
return err
}
if err := pt.Read(); err != nil {
return err
}
if err := pt.Repair(); err != nil {
return err
}
for _, partition := range pt.Partitions() {
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
if err := pt.Resize(partition); err != nil {
return err
}
}
}
// Rereading the partition table requires that all partitions be unmounted
// or it will fail with EBUSY.
if err := bd.RereadPartitionTable(devname); err != nil {
return err
}
}
}
for _, b := range probed {
return nil
}
func mountBlockDevices(blockdevices []*BlockDevice, s string) (err error) {
if err = fixDataPartition(blockdevices); err != nil {
return err
}
for _, b := range blockdevices {
mountpoint := &Point{
source: b.dev,
fstype: b.TYPE,
@ -196,6 +248,13 @@ func mountBlockDevices(s string) (err error) {
return fmt.Errorf("mount %s: %s", mountpoint.target, err.Error())
}
if b.LABEL == constants.DataPartitionLabel {
// The XFS partition MUST be mounted, or this will fail.
if err = xfs.GrowFS(mountpoint.target); err != nil {
return err
}
}
instance.blockdevices[b.LABEL] = mountpoint
}
@ -258,3 +317,25 @@ func probeDevice(devname string) (*BlockDevice, error) {
LABEL: LABEL,
}, nil
}
func partNo(partname string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
idx := strings.Index(partname, "p")
return partname[idx+1:]
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz")
}
return ""
}
func devnameFromPartname(partname string) string {
partno := partNo(partname)
if strings.HasPrefix(partname, "/dev/nvme") {
return strings.TrimRight(partname, "p"+partno)
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimRight(partname, partno)
}
return ""
}

View File

@ -0,0 +1,78 @@
// nolint: dupl,golint
package services
import (
"os"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/constants"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/system/conditions"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/system/runner"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/system/runner/containerd"
"github.com/autonomy/dianemo/src/initramfs/pkg/userdata"
"github.com/autonomy/dianemo/src/initramfs/pkg/version"
"github.com/containerd/containerd/oci"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// Blockd implements the Service interface. It serves as the concrete type with
// the required methods.
type Blockd struct{}
// ID implements the Service interface.
func (t *Blockd) ID(data *userdata.UserData) string {
return "blockd"
}
// PreFunc implements the Service interface.
func (t *Blockd) PreFunc(data *userdata.UserData) error {
return os.Mkdir("/run/blockd", os.ModeDir)
}
// PostFunc implements the Service interface.
func (t *Blockd) PostFunc(data *userdata.UserData) (err error) {
return nil
}
// ConditionFunc implements the Service interface.
func (t *Blockd) ConditionFunc(data *userdata.UserData) conditions.ConditionFunc {
return conditions.None()
}
func (t *Blockd) Start(data *userdata.UserData) error {
// Set the image.
var image string
if data.Services.Blockd != nil && data.Services.Blockd.Image != "" {
image = data.Services.Blockd.Image
} else {
image = "docker.io/autonomy/blockd:" + version.SHA
}
// Set the process arguments.
args := runner.Args{
ID: t.ID(data),
ProcessArgs: []string{"/blockd", "--userdata=" + constants.UserDataPath},
}
if data.Services.Kubeadm.Init == nil {
args.ProcessArgs = append(args.ProcessArgs, "--generate=true")
}
// Set the mounts.
mounts := []specs.Mount{
{Type: "bind", Destination: "/dev", Source: "/dev", Options: []string{"rbind", "rshared", "rw"}},
{Type: "bind", Destination: constants.UserDataPath, Source: constants.UserDataPath, Options: []string{"rbind", "ro"}},
{Type: "bind", Destination: "/var/etc/kubernetes", Source: "/var/etc/kubernetes", Options: []string{"bind", "rw"}},
{Type: "bind", Destination: "/run/factory", Source: "/run/blockd", Options: []string{"rbind", "rshared", "rw"}},
}
r := containerd.Containerd{}
return r.Run(
data,
args,
runner.WithContainerImage(image),
runner.WithOCISpecOpts(
containerd.WithMemoryLimit(int64(1000000*512)),
oci.WithMounts(mounts),
),
)
}

View File

@ -4,10 +4,9 @@ import (
"flag"
"log"
"github.com/autonomy/dianemo/src/initramfs/cmd/osd/pkg/gen"
"github.com/autonomy/dianemo/src/initramfs/cmd/osd/pkg/reg"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/factory"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/middleware/auth/basic"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/gen"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/tls"
"github.com/autonomy/dianemo/src/initramfs/pkg/userdata"
"google.golang.org/grpc"
@ -15,48 +14,33 @@ import (
)
var (
dataPath *string
generate *bool
port *int
rotPort *int
dataPath *string
generate *bool
port *int
trustdPort *int
)
func init() {
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds | log.Ltime)
dataPath = flag.String("userdata", "", "the path to the user data")
port = flag.Int("port", 50000, "the port to listen on")
rotPort = flag.Int("rot-port", 50001, "the port to listen on")
trustdPort = flag.Int("trustd-port", 50001, "the trustd port")
generate = flag.Bool("generate", false, "generate the TLS certificate using one of the Root of Trusts")
flag.Parse()
}
func main() {
var err error
data, err := userdata.Open(*dataPath)
if err != nil {
log.Fatalf("open user data: %v", err)
}
if *generate {
if len(data.Services.Trustd.Endpoints) == 0 {
log.Fatalf("at least one root of trust endpoint is required")
}
creds := basic.NewCredentials(
data.Security.OS.CA.Crt,
data.Services.Trustd.Username,
data.Services.Trustd.Password,
)
// TODO: In the case of failure, attempt to generate the identity from
// another RoT.
var conn *grpc.ClientConn
conn, err = basic.NewConnection(data.Services.Trustd.Endpoints[0], *rotPort, creds)
var generator *gen.Generator
generator, err = gen.NewGenerator(data, *trustdPort)
if err != nil {
return
log.Fatal(err)
}
generator := gen.NewGenerator(conn)
if err = generator.Identity(data.Security); err != nil {
log.Fatalf("generate identity: %v", err)
}
@ -67,6 +51,7 @@ func main() {
log.Fatalf("credentials: %v", err)
}
log.Println("Starting osd")
err = factory.Listen(
&reg.Registrator{Data: data},
factory.Port(*port),

View File

@ -34,6 +34,7 @@ require (
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
github.com/google/go-cmp v0.2.0 // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/google/uuid v1.0.0 // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
@ -69,7 +70,7 @@ require (
golang.org/x/net v0.0.0-20180724234803-3673e40ba225
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
golang.org/x/sys v0.0.0-20180302081741-dd2ff4accc09
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
google.golang.org/appengine v1.2.0 // indirect
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect

View File

@ -64,6 +64,8 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
@ -136,6 +138,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6Zh
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180302081741-dd2ff4accc09 h1:wNPZbZUOH0tyqngVRXeF2iQm19+ssqyebJTCFBvxsow=
golang.org/x/sys v0.0.0-20180302081741-dd2ff4accc09/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM=
golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=

View File

@ -0,0 +1,69 @@
// Package blockdevice provides a library for working with block devices.
package blockdevice
import (
"fmt"
"os"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table/gpt"
"golang.org/x/sys/unix"
)
// BlockDevice represents a block device.
type BlockDevice struct {
table table.PartitionTable
f *os.File
}
// Open initializes and returns a block device.
func Open(devname string) (*BlockDevice, error) {
f, err := os.OpenFile(devname, os.O_RDWR, os.ModeDevice)
if err != nil {
return nil, err
}
// TODO: Dynamically detect MBR/GPT.
// TODO: Use BLKGETSIZE ioctl to get the size.
// TODO: Use BLKPBSZGET ioctl to get the physical sector size.
// TODO: Use BLKSSZGET ioctl to get the logical sector size.
// and pass them into gpt as options.
bd := &BlockDevice{
table: gpt.NewGPT(devname, f),
}
return bd, nil
}
// Close closes the block devices's open file.
func (bd *BlockDevice) Close() error {
return bd.f.Close()
}
// PartitionTable returns the block device partition table.
func (bd *BlockDevice) PartitionTable() (table.PartitionTable, error) {
if bd.table == nil {
return nil, fmt.Errorf("missing partition table")
}
return bd.table, nil
}
// RereadPartitionTable invokes the BLKRRPART ioctl have the kernel read the
// partition table.
func (bd *BlockDevice) RereadPartitionTable(devname string) error {
f, err := os.Open(devname)
if err != nil {
return err
}
unix.Sync()
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKRRPART, 0); ret != 0 {
return fmt.Errorf("re-read partition table: %v", ret)
}
if err := f.Sync(); err != nil {
return err
}
unix.Sync()
return nil
}

View File

@ -0,0 +1,44 @@
// Package lba provides a library for working with Logical Block Addresses.
package lba
import (
"fmt"
)
// Range represents a range of Logical Block Addresses.
type Range struct {
Start uint64
End uint64
}
// LogicalBlockAddresser represents Logical Block Addressing.
type LogicalBlockAddresser struct {
PhysicalBlockSize int
LogicalBlockSize int
}
// Make returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) Make(size int) []byte {
return make([]byte, lba.PhysicalBlockSize*size)
}
// Copy copies from src to dst in the specified range.
func (lba *LogicalBlockAddresser) Copy(dst []byte, src []byte, rng Range) (int, error) {
size := uint64(lba.PhysicalBlockSize)
n := copy(dst[size*rng.Start:size*rng.End], src)
if n != len(src) {
return -1, fmt.Errorf("expected to write %d elements, wrote %d", len(src), n)
}
return n, nil
}
// From returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) From(src []byte, rng Range) ([]byte, error) {
size := uint64(lba.PhysicalBlockSize)
if uint64(len(src)) < size+size*rng.End {
return nil, fmt.Errorf("cannot read LBA range (start: %d, end %d), source too small", rng.Start, rng.End)
}
return src[size*rng.Start : size+size*rng.End], nil
}

View File

@ -0,0 +1,68 @@
package serde
import (
"fmt"
)
// Serde describes a serializer/deserializer.
type Serde interface {
Fields() []*Field
}
// FieldSerializerFunc is the func signature for serialization.
type FieldSerializerFunc = func([]byte, interface{}) error
// FieldDeserializerFunc is the func signature for deserialization.
type FieldDeserializerFunc = func(uint32, uint32, []byte, interface{}) ([]byte, error)
// Field represents a field in a datastructure.
type Field struct {
Offset uint32
Length uint32
Contents *[]byte
SerializerFunc FieldSerializerFunc
DeserializerFunc FieldDeserializerFunc
}
// Ser serializes a field.
func Ser(t Serde, data []byte, offset uint32, opts interface{}) error {
for _, field := range t.Fields() {
if field.SerializerFunc == nil {
return fmt.Errorf("the field is missing the serializer function")
}
if err := field.SerializerFunc(data[field.start(offset):field.end(offset)], opts); err != nil {
return err
}
}
return nil
}
// De deserializes a field.
func De(t Serde, data []byte, offset uint32, opts interface{}) error {
for _, field := range t.Fields() {
if field.DeserializerFunc == nil {
return fmt.Errorf("the field is missing the deserializer function")
}
contents, err := field.DeserializerFunc(field.Offset, field.Length, data, opts)
if err != nil {
return err
}
if n := copy(data[field.start(offset):field.end(offset)], contents); uint32(n) != field.Length {
return fmt.Errorf("expected to write %d elements, wrote %d", field.Length, n)
}
}
return nil
}
func (fld *Field) start(offset uint32) uint32 {
return fld.Offset + offset
}
func (fld *Field) end(offset uint32) uint32 {
return fld.Offset + fld.Length + offset
}

View File

@ -0,0 +1,416 @@
// Package gpt provides a library for working with GPT partitions.
package gpt
import (
"fmt"
"os"
"syscall"
"unsafe"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/lba"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/serde"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table/gpt/header"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/table/gpt/partition"
"github.com/google/uuid"
"golang.org/x/sys/unix"
)
// GPT represents the GUID partition table.
type GPT struct {
table table.Table
header *header.Header
partitions []table.Partition
lba *lba.LogicalBlockAddresser
devname string
f *os.File
}
// NewGPT initializes and returns a GUID partition table.
func NewGPT(devname string, f *os.File, setters ...interface{}) *GPT {
opts := NewDefaultOptions(setters...)
lba := &lba.LogicalBlockAddresser{
PhysicalBlockSize: opts.PhysicalBlockSize,
LogicalBlockSize: opts.LogicalBlockSize,
}
return &GPT{
lba: lba,
devname: devname,
f: f,
}
}
// Bytes returns the partition table as a byte slice.
func (gpt *GPT) Bytes() []byte {
return gpt.table
}
// Type returns the partition type.
func (gpt *GPT) Type() table.Type {
return table.GPT
}
// Header returns the header.
func (gpt *GPT) Header() table.Header {
return gpt.header
}
// Partitions returns the partitions.
func (gpt *GPT) Partitions() []table.Partition {
return gpt.partitions
}
// Read performs reads the partition table.
func (gpt *GPT) Read() error {
primaryTable, err := gpt.readPrimary()
if err != nil {
return err
}
serializedHeader, err := gpt.serializeHeader(primaryTable)
if err != nil {
return err
}
serializedPartitions, err := gpt.serializePartitions(serializedHeader)
if err != nil {
return err
}
gpt.table = primaryTable
gpt.header = serializedHeader
gpt.partitions = serializedPartitions
return nil
}
// Write writes the partition table to disk.
func (gpt *GPT) Write() error {
partitions, err := gpt.deserializePartitions()
if err != nil {
return err
}
if err := gpt.writePrimary(partitions); err != nil {
return fmt.Errorf("failed to write primary table: %v", err)
}
if err := gpt.writeSecondary(partitions); err != nil {
return fmt.Errorf("failed to write primary table: %v", err)
}
return gpt.Read()
}
// Write the primary table.
func (gpt *GPT) writePrimary(partitions []byte) error {
header, err := gpt.deserializeHeader(partitions)
if err != nil {
return err
}
table, err := gpt.newTable(header, partitions, lba.Range{Start: 0, End: 1}, lba.Range{Start: 1, End: 33})
if err != nil {
return err
}
written, err := gpt.f.WriteAt(table, int64(gpt.PhysicalBlockSize()))
if err != nil {
return err
}
if written != len(table) {
return fmt.Errorf("expected a primary table write of %d bytes, got %d", len(table), written)
}
return nil
}
// Write the secondary table.
func (gpt *GPT) writeSecondary(partitions []byte) error {
header, err := gpt.deserializeHeader(partitions, header.WithHeaderPrimary(false))
if err != nil {
return err
}
table, err := gpt.newTable(header, partitions, lba.Range{Start: 32, End: 33}, lba.Range{Start: 0, End: 32})
if err != nil {
return err
}
offset := int64((gpt.header.LastUsableLBA + 1))
written, err := gpt.f.WriteAt(table, offset*int64(gpt.PhysicalBlockSize()))
if err != nil {
return err
}
if written != len(table) {
return fmt.Errorf("expected a secondary table write of %d bytes, got %d", len(table), written)
}
return nil
}
// Repair repairs the partition table.
func (gpt *GPT) Repair() error {
// Seek to the end to get the size.
size, err := gpt.f.Seek(0, 2)
if err != nil {
return err
}
// Reset and seek to the beginning.
_, err = gpt.f.Seek(0, 0)
if err != nil {
return err
}
gpt.header.BackupLBA = uint64(size/int64(gpt.lba.PhysicalBlockSize) - 1)
gpt.header.LastUsableLBA = gpt.header.BackupLBA - 33
return gpt.Write()
}
// Add adds a partition.
func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error) {
opts := partition.NewDefaultOptions(setters...)
var start, end uint64
if len(gpt.partitions) == 0 {
start = gpt.header.FirstUsableLBA
} else {
previous := gpt.partitions[len(gpt.partitions)-1]
start = previous.(*partition.Partition).LastLBA + 1
}
end = start + size/uint64(gpt.PhysicalBlockSize())
if end > gpt.header.LastUsableLBA {
// TODO: This calculation is wrong, fix it.
available := (gpt.header.LastUsableLBA - start) * uint64(gpt.PhysicalBlockSize())
return nil, fmt.Errorf("requested partition size %d is too big, largest available is %d", size, available)
}
uuid, err := uuid.NewUUID()
if err != nil {
return nil, err
}
partition := &partition.Partition{
Type: opts.Type,
ID: uuid,
FirstLBA: start,
LastLBA: end,
// TODO: Flags should be an option.
Flags: 0,
Name: opts.Name,
Number: int32(len(gpt.partitions) + 1),
}
gpt.partitions = append(gpt.partitions, partition)
if err := gpt.Write(); err != nil {
return nil, fmt.Errorf("failed to add partition: %v", err)
}
if err := gpt.InformKernelOfAdd(gpt.devname, partition); err != nil {
return nil, err
}
return partition, nil
}
// Resize resizes a partition.
// TODO: Verify that we can indeed grow this partition safely.
func (gpt *GPT) Resize(p table.Partition) error {
partition, ok := p.(*partition.Partition)
if !ok {
return fmt.Errorf("partition is not a GUID partition table partition")
}
// TODO: This should be a parameter.
partition.LastLBA = gpt.header.LastUsableLBA
index := partition.Number - 1
if len(gpt.partitions) < int(index) {
return fmt.Errorf("unknown partition %d, only %d available", partition.Number, len(gpt.partitions))
}
gpt.partitions[index] = partition
if err := gpt.Write(); err != nil {
return fmt.Errorf("failed to grow partitioin: %v", err)
}
return gpt.InformKernelOfResize(gpt.devname, p)
}
// Delete deletes a partition.
func (gpt *GPT) Delete(partition table.Partition) error {
return nil
}
// PhysicalBlockSize returns the physical block size.
func (gpt *GPT) PhysicalBlockSize() int {
return gpt.lba.PhysicalBlockSize
}
// TODO: Rename this func, it doesn't deserialize anything.
func (gpt *GPT) readPrimary() ([]byte, error) {
// LBA 34 is the first usable sector on the disk.
table := gpt.lba.Make(34)
read, err := gpt.f.ReadAt(table, 0)
if err != nil {
return nil, err
}
if read != len(table) {
return nil, fmt.Errorf("expected a read of %d bytes, got %d", len(table), read)
}
return table, nil
}
func (gpt *GPT) newTable(header, partitions []byte, headerRange, paritionsRange lba.Range) ([]byte, error) {
table := gpt.lba.Make(33)
if _, err := gpt.lba.Copy(table, header, headerRange); err != nil {
return nil, fmt.Errorf("failed to copy header data: %v", err)
}
if _, err := gpt.lba.Copy(table, partitions, paritionsRange); err != nil {
return nil, fmt.Errorf("failed to copy partition data: %v", err)
}
return table, nil
}
func (gpt *GPT) serializeHeader(table []byte) (*header.Header, error) {
// GPT header is in LBA 1.
data, err := gpt.lba.From(table, lba.Range{Start: 1, End: 1})
if err != nil {
return nil, err
}
hdr := header.NewHeader(data, gpt.lba)
opts := header.NewDefaultOptions(header.WithHeaderTable(table))
if err := serde.Ser(hdr, hdr.Bytes(), 0, opts); err != nil {
return nil, fmt.Errorf("failed to serialize the header: %v", err)
}
return hdr, nil
}
func (gpt *GPT) deserializeHeader(partitions []byte, setters ...interface{}) ([]byte, error) {
data := gpt.lba.Make(1)
setters = append(setters, header.WithHeaderArrayBytes(partitions))
opts := header.NewDefaultOptions(setters...)
if err := serde.De(gpt.header, data, 0, opts); err != nil {
return nil, fmt.Errorf("failed to deserialize the header: %v", err)
}
return data, nil
}
func (gpt *GPT) serializePartitions(header *header.Header) ([]table.Partition, error) {
partitions := make([]table.Partition, 0, header.NumberOfPartitionEntries)
for i := uint32(0); i < header.NumberOfPartitionEntries; i++ {
offset := i * header.PartitionEntrySize
data := header.ArrayBytes()[offset : offset+header.PartitionEntrySize]
prt := partition.NewPartition(data)
if err := serde.Ser(prt, header.ArrayBytes(), offset, nil); err != nil {
return nil, fmt.Errorf("failed to serialize the partitions: %v", err)
}
// The first LBA of the partition cannot start before the first usable
// LBA specified in the header.
if prt.FirstLBA >= header.FirstUsableLBA {
prt.Number = int32(i) + 1
partitions = append(partitions, prt)
}
}
return partitions, nil
}
func (gpt *GPT) deserializePartitions() ([]byte, error) {
// TODO: Should this be a method on the Header struct?
data := make([]byte, gpt.header.NumberOfPartitionEntries*gpt.header.PartitionEntrySize)
for j, p := range gpt.partitions {
i := uint32(j)
partition, ok := p.(*partition.Partition)
if !ok {
return nil, fmt.Errorf("partition is not a GUID partition table partition")
}
if err := serde.De(partition, data, i*gpt.header.PartitionEntrySize, nil); err != nil {
return nil, fmt.Errorf("failed to deserialize the partitions: %v", err)
}
}
return data, nil
}
// InformKernelOfAdd invokes the BLKPG_ADD_PARTITION ioctl.
func (gpt *GPT) InformKernelOfAdd(devname string, partition table.Partition) error {
f, err := os.Open(devname)
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
return inform(f.Fd(), partition, unix.BLKPG_ADD_PARTITION, int64(gpt.lba.PhysicalBlockSize))
}
// InformKernelOfResize invokes the BLKPG_RESIZE_PARTITION ioctl.
func (gpt *GPT) InformKernelOfResize(devname string, partition table.Partition) error {
f, err := os.Open(devname)
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
return inform(f.Fd(), partition, unix.BLKPG_RESIZE_PARTITION, int64(gpt.lba.PhysicalBlockSize))
}
// InformKernelOfDelete invokes the BLKPG_DEL_PARTITION ioctl.
func (gpt *GPT) InformKernelOfDelete(devname string, partition table.Partition) error {
f, err := os.Open(devname)
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
return inform(f.Fd(), partition, unix.BLKPG_DEL_PARTITION, int64(gpt.lba.PhysicalBlockSize))
}
func inform(fd uintptr, partition table.Partition, op int32, blocksize int64) error {
arg := &unix.BlkpgIoctlArg{
Op: op,
Data: (*byte)(unsafe.Pointer(&unix.BlkpgPartition{
Start: partition.Start() * blocksize,
Length: partition.Length() * blocksize,
Pno: partition.No(),
})),
}
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
unix.BLKPG,
uintptr(unsafe.Pointer(arg)),
)
if errno != 0 {
return errno
}
return nil
}

View File

@ -0,0 +1,49 @@
package gpt
// Options is the functional options struct.
type Options struct {
PrimaryGPT bool
PhysicalBlockSize int
LogicalBlockSize int
}
// Option is the functional option func.
type Option func(*Options)
// WithPrimaryGPT sets the contents of offset 24 in the GPT header to the location of the primary header.
func WithPrimaryGPT(o bool) Option {
return func(args *Options) {
args.PrimaryGPT = o
}
}
// WithPhysicalBlockSize sets the physical block size.
func WithPhysicalBlockSize(o int) Option {
return func(args *Options) {
args.PhysicalBlockSize = o
}
}
// WithLogicalBlockSize sets the logical block size.
func WithLogicalBlockSize(o int) Option {
return func(args *Options) {
args.LogicalBlockSize = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
opts := &Options{
PrimaryGPT: true,
PhysicalBlockSize: 512,
LogicalBlockSize: 512,
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -0,0 +1,418 @@
// Package header provides a library for working with GPT headers.
package header
import (
"bytes"
"encoding/binary"
"fmt"
"hash/crc32"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/lba"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/serde"
"github.com/google/uuid"
)
const (
// HeaderSize is the GUID partition table header size in bytes.
HeaderSize = 92
)
// Header represents a GUID partition table.
type Header struct {
data []byte
array []byte
Signature string // 0
Revision uint32 // 8
Size uint32 // 12
CRC uint32 // 16
Reserved uint32 // 20
CurrentLBA uint64 // 24
BackupLBA uint64 // 32
FirstUsableLBA uint64 // 40
LastUsableLBA uint64 // 48
GUUID uuid.UUID // 56
PartitionEntriesStartLBA uint64 // 72
NumberOfPartitionEntries uint32 // 80
PartitionEntrySize uint32 // 84
PartitionsArrayCRC uint32 // 88
TrailingBytes []byte // 92
*lba.LogicalBlockAddresser
}
// NewHeader inializes and returns a GUID partition table header.
func NewHeader(data []byte, lba *lba.LogicalBlockAddresser) *Header {
return &Header{
data: data,
LogicalBlockAddresser: lba,
}
}
// Bytes implements the table.Header interface.
func (hdr *Header) Bytes() []byte {
return hdr.data
}
// ArrayBytes returns the GUID partition table partitions entries array as a byte slice.
func (hdr *Header) ArrayBytes() []byte {
return hdr.array
}
// Fields impements the serde.Serde interface.
// nolint: gocyclo
func (hdr *Header) Fields() []*serde.Field {
return []*serde.Field{
// 8 bytes Signature ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h or 0x5452415020494645ULL on little-endian machines)
{
Offset: 0,
Length: 8,
// Contents: []byte{0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return []byte{0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54}, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
signature := string(contents)
if signature != "EFI PART" {
return fmt.Errorf("expected signature of \"EFI PART\", got %q", signature)
}
hdr.Signature = string(contents)
return nil
},
},
// 4 bytes Revision (for GPT version 1.0 (through at least UEFI version 2.7 (May 2017)), the value is 00h 00h 01h 00h)
{
Offset: 8,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x01, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.Revision)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
expected := []byte{0x00, 0x00, 0x01, 0x00}
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected revision of %v, got %v", expected, contents)
}
hdr.Revision = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes Header size in little endian (in bytes, usually 5Ch 00h 00h 00h or 92 bytes)
{
Offset: 12,
Length: 4,
// Contents: []byte{0x5c, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.Size)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.Size = binary.LittleEndian.Uint32(contents)
if hdr.Size != HeaderSize {
return fmt.Errorf("expected GPT header size of %d, got %d", HeaderSize, hdr.Size)
}
return nil
},
},
// 4 bytes Reserved; must be zero
{
Offset: 20,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return []byte{0x00, 0x00, 0x00, 0x00}, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
expected := []byte{0x00, 0x00, 0x00, 0x00}
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected reserved field to be %v, got %v", expected, contents)
}
hdr.Reserved = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 8 bytes Current LBA (location of this header copy)
// nolint: dupl
{
Offset: 24,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
if o.Primary {
binary.LittleEndian.PutUint64(data, hdr.CurrentLBA)
} else {
binary.LittleEndian.PutUint64(data, hdr.BackupLBA)
}
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.CurrentLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Backup LBA (location of the other header copy)
// nolint: dupl
{
Offset: 32,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
if o.Primary {
binary.LittleEndian.PutUint64(data, hdr.BackupLBA)
} else {
binary.LittleEndian.PutUint64(data, hdr.CurrentLBA)
}
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.BackupLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes First usable LBA for partitions (primary partition table last LBA + 1)
{
Offset: 40,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.FirstUsableLBA)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.FirstUsableLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Last usable LBA (secondary partition table first LBA - 1)
{
Offset: 48,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.LastUsableLBA)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.LastUsableLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 16 bytes Disk GUID (also referred as UUID on UNIXes)
{
Offset: 56,
Length: 16,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return hdr.GUUID.MarshalBinary()
},
SerializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
if err != nil {
return fmt.Errorf("invalid GUUID: %v", err)
}
hdr.GUUID = guid
return nil
},
},
// 8 bytes Starting LBA of array of partition entries (always 2 in primary copy)
{
Offset: 72,
Length: 8,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.PartitionEntriesStartLBA)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
// TODO: Should we verify it is 2 in the case of primary?
o, ok := opts.(*Options)
if !ok {
return fmt.Errorf("option is not a GPT header option")
}
hdr.PartitionEntriesStartLBA = binary.LittleEndian.Uint64(contents)
array, err := hdr.From(o.Table, lba.Range{Start: hdr.PartitionEntriesStartLBA, End: uint64(33)})
if err != nil {
return fmt.Errorf("failed to read starting LBA from header: %v", err)
}
hdr.array = array
return nil
},
},
// 4 bytes Number of partition entries in array
{
Offset: 80,
Length: 4,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.NumberOfPartitionEntries)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
hdr.NumberOfPartitionEntries = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes Size of a single partition entry (usually 80h or 128)
{
Offset: 84,
Length: 4,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.PartitionEntrySize)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
length := binary.LittleEndian.Uint32(contents)
// This field should be set to a value of: 128 x 2n where n is an integer greater than or equal to zero.
if length%128 != 0 {
return fmt.Errorf("expected partition entry size to be a multiple of %d, got %d", 128, length)
}
hdr.PartitionEntrySize = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes CRC32/zlib of partition array in little endian
{
Offset: 88,
Length: 4,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
expected := hdr.NumberOfPartitionEntries * hdr.PartitionEntrySize
if len(o.Array) != int(expected) {
return nil, fmt.Errorf("expected array length of %d, got %d", expected, len(o.Array))
}
crc := crc32.ChecksumIEEE(o.Array)
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, crc)
// We should update this here and now to ensure that the field is still valid.
hdr.PartitionsArrayCRC = crc
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
crc := binary.LittleEndian.Uint32(contents)
// Note that hdr.array is expected to be set in offset 72 DeserializerFunc.
checksum := crc32.ChecksumIEEE(hdr.array)
if crc != checksum {
return fmt.Errorf("expected partition checksum of %v, got %v", checksum, crc)
}
hdr.PartitionsArrayCRC = crc
return nil
},
},
// Reserved; must be zeroes for the rest of the block (420 bytes for a sector size of 512 bytes; but can be more with larger sector sizes)
{
Offset: HeaderSize,
Length: 420,
// Contents: []byte{0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, 420)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
expected := make([]byte, 420)
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected %d trailing bytes of zeroes", 420)
}
hdr.TrailingBytes = contents
return nil
},
},
// 4 bytes CRC32/zlib of header (offset +0 up to header size) in little endian, with this field zeroed during calculation
{
Offset: 16,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
// Copy the header into a temporary slice and to avoid modifying the original.
header := make([]byte, HeaderSize)
copy(header, new)
// Zero the CRC field during the calculation.
copy(header[16:20], []byte{0x00, 0x00, 0x00, 0x00})
crc := crc32.ChecksumIEEE(header)
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, crc)
// We should update this here and now to ensure that the field is still valid.
hdr.CRC = crc
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
crc := binary.LittleEndian.Uint32(contents)
// Copy the header into a temporary slice and to avoid modifying the original.
header := make([]byte, HeaderSize)
copy(header, hdr.data)
// Zero the CRC field during the calculation.
copy(header[16:20], []byte{0x00, 0x00, 0x00, 0x00})
checksum := crc32.ChecksumIEEE(header)
if crc != checksum {
return fmt.Errorf("expected header checksum of %d, got %d", crc, checksum)
}
hdr.CRC = crc
return nil
},
},
}
}

View File

@ -0,0 +1,49 @@
package header
// Options is the functional options struct.
type Options struct {
Primary bool
Table []byte
Array []byte
}
// Option is the functional option func.
type Option func(*Options)
// WithHeaderPrimary sets the primary option.
func WithHeaderPrimary(o bool) Option {
return func(args *Options) {
args.Primary = o
}
}
// WithHeaderTable sets the partition type.
func WithHeaderTable(o []byte) Option {
return func(args *Options) {
args.Table = o
}
}
// WithHeaderArrayBytes sets the partition type.
func WithHeaderArrayBytes(o []byte) Option {
return func(args *Options) {
args.Array = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
opts := &Options{
Primary: true,
Table: []byte{},
Array: []byte{},
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -0,0 +1,189 @@
// Package partition provides a library for working with GPT partitions.
package partition
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/serde"
"github.com/google/uuid"
"golang.org/x/text/encoding/unicode"
)
// Partition represents a partition entry in a GUID partition table.
type Partition struct {
data []byte
Type uuid.UUID // 0
ID uuid.UUID // 16
FirstLBA uint64 // 32
LastLBA uint64 // 40
Flags uint64 // 48
Name string // 56
TrailingBytes []byte // 128
Number int32
}
// NewPartition initializes and returns a new partition.
func NewPartition(data []byte) *Partition {
return &Partition{
data: data,
}
}
// Bytes returns the partition as a byte slice.
func (prt *Partition) Bytes() []byte {
return prt.data
}
// Start returns the partition's starting LBA..
func (prt *Partition) Start() int64 {
return int64(prt.FirstLBA)
}
// Length returns the partition's length in LBA.
func (prt *Partition) Length() int64 {
return int64(prt.LastLBA)
}
// No returns the partition's number.
func (prt *Partition) No() int32 {
return prt.Number
}
// Fields implements the serder.Serde interface.
func (prt *Partition) Fields() []*serde.Field {
return []*serde.Field{
// 16 bytes Partition type GUID
{
Offset: 0,
Length: 16,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return prt.Type.MarshalBinary()
},
SerializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
if err != nil {
return fmt.Errorf("invalid GUUID: %v", err)
}
// TODO: Provide a method for getting the human readable name of the type.
// See https://en.wikipedia.org/wiki/GUID_Partition_Table.
prt.Type = guid
return nil
},
},
// 16 bytes Unique partition GUID
{
Offset: 16,
Length: 16,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return prt.ID.MarshalBinary()
},
SerializerFunc: func(contents []byte, opts interface{}) error {
guid, err := uuid.FromBytes(contents)
if err != nil {
return fmt.Errorf("invalid GUUID: %v", err)
}
prt.ID = guid
return nil
},
},
// 8 bytes First LBA (little endian)
{
Offset: 32,
Length: 8,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.FirstLBA)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
prt.FirstLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Last LBA (inclusive, usually odd)
{
Offset: 40,
Length: 8,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.LastLBA)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
prt.LastLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Attribute flags (e.g. bit 60 denotes read-only)
{
Offset: 48,
Length: 8,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.Flags)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
prt.Flags = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 72 bytes Partition name (36 UTF-16LE code units)
{
Offset: 56,
Length: 72,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
name, err := utf16.NewEncoder().Bytes([]byte(prt.Name))
if err != nil {
return nil, err
}
// TODO: Should we error if the name exceeds 72 bytes?
data := make([]byte, 72)
copy(data, name)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
decoded, err := utf16.NewDecoder().Bytes(contents)
if err != nil {
return err
}
prt.Name = string(bytes.Trim(decoded, "\x00"))
return nil
},
},
{
Offset: 72,
Length: 56,
DeserializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
copy(data, prt.TrailingBytes)
return data, nil
},
SerializerFunc: func(contents []byte, opts interface{}) error {
prt.TrailingBytes = contents
return nil
},
},
}
}

View File

@ -0,0 +1,52 @@
package partition
import (
"github.com/google/uuid"
)
// Options is the functional options struct.
type Options struct {
Type uuid.UUID
Name string
}
// Option is the functional option func.
type Option func(*Options)
// WithPartitionType sets the partition type.
func WithPartitionType(o [16]byte) Option {
return func(args *Options) {
// TODO: An Option should return an error.
// nolint: errcheck
guuid, _ := uuid.FromBytes(o[:])
args.Type = guuid
}
}
// WithPartitionName sets the partition name.
func WithPartitionName(o string) Option {
return func(args *Options) {
args.Name = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
// Default to data type "af3dc60f-8384-7247-8e79-3d69d8477de4"
// TODO: An Option should return an error.
// nolint: errcheck
guuid, _ := uuid.FromBytes([]byte{0Xaf, 0X3d, 0Xc6, 0X0f, 0X83, 0X84, 0X72, 0X47, 0X8e, 0X79, 0X3d, 0X69, 0Xd8, 0X47, 0X7d, 0Xe4})
opts := &Options{
Type: guuid,
Name: "",
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -0,0 +1,67 @@
// Package table provides a library for working with block device partition tables.
package table
import "github.com/autonomy/dianemo/src/initramfs/pkg/blockdevice/pkg/serde"
// Table represents a partition table.
type Table = []byte
// PartitionTable describes a partition table.
type PartitionTable interface {
// Bytes returns the partition table as a byte slice.
Bytes() Table
// Read reades the partition table.
Read() error
// Write writes the partition table/.
Write() error
// Type returns the partition table type.
Type() Type
// Header returns the partition table header.
Header() Header
// Partitions returns a slice o partition table partitions.
Partitions() []Partition
// Repair repairs a partition table.
Repair() error
// Partitioner must be implemented by a partition table.
Partitioner
}
// Type represents a partition table type.
type Type int
const (
// MBR is the Master Boot Record artition table.
MBR Type = iota
// GPT is the GUID partition table.
GPT
)
// Header describes a partition table header.
type Header interface {
// Bytes returns the partition table header as a byte slice.
Bytes() []byte
serde.Serde
}
// Partition describes a partition.
type Partition interface {
// Bytes returns the partition table partitions as a byte slice.
Bytes() []byte
// Start returns the partition's starting LBA.
Start() int64
// Length returns the partition's length in LBA.
Length() int64
// No returns the partition's number.
No() int32
serde.Serde
}
// Partitioner describes actions that can be taken on a partition.
type Partitioner interface {
// Add adds a partition to the partition table.
Add(uint64, ...interface{}) (Partition, error)
// Resize resizes a partition table.
Resize(Partition) error
// Delete deletes a partition table.
Delete(Partition) error
}

View File

@ -2,6 +2,7 @@ package factory
import (
"crypto/tls"
"fmt"
"net"
"strconv"
@ -17,6 +18,7 @@ type Registrator interface {
// Options is the functional options struct.
type Options struct {
Port int
Network string
Config *tls.Config
ServerOptions []grpc.ServerOption
}
@ -31,6 +33,13 @@ func Port(o int) Option {
}
}
// Network sets the network type of the listener.
func Network(o string) Option {
return func(args *Options) {
args.Network = o
}
}
// Config sets the listen port of the server.
func Config(o *tls.Config) Option {
return func(args *Options) {
@ -48,7 +57,8 @@ func ServerOptions(o ...grpc.ServerOption) Option {
// NewDefaultOptions initializes the Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{
Port: 50000,
Port: 50000,
Network: "tcp",
}
for _, setter := range setters {
@ -69,11 +79,19 @@ func Listen(r Registrator, setters ...Option) (err error) {
server := grpc.NewServer(opts.ServerOptions...)
r.Register(server)
listener, err := net.Listen("tcp", ":"+strconv.Itoa(opts.Port))
var address string
switch opts.Network {
case "unix":
address = "/run/factory/factory.sock"
case "tcp":
address = ":" + strconv.Itoa(opts.Port)
default:
return fmt.Errorf("unknown network: %s", opts.Network)
}
listener, err := net.Listen(opts.Network, address)
if err != nil {
return
}
err = server.Serve(listener)
if err != nil {
return

View File

@ -11,6 +11,7 @@ import (
"github.com/autonomy/dianemo/src/initramfs/cmd/trustd/proto"
"github.com/autonomy/dianemo/src/initramfs/pkg/crypto/x509"
"github.com/autonomy/dianemo/src/initramfs/pkg/grpc/middleware/auth/basic"
"github.com/autonomy/dianemo/src/initramfs/pkg/net"
"github.com/autonomy/dianemo/src/initramfs/pkg/userdata"
"google.golang.org/grpc"
@ -22,12 +23,29 @@ type Generator struct {
}
// NewGenerator initializes a Generator with a preconfigured grpc.ClientConn.
func NewGenerator(conn *grpc.ClientConn) (g *Generator) {
func NewGenerator(data *userdata.UserData, port int) (g *Generator, err error) {
if len(data.Services.Trustd.Endpoints) == 0 {
return nil, fmt.Errorf("at least one root of trust endpoint is required")
}
creds := basic.NewCredentials(
data.Security.OS.CA.Crt,
data.Services.Trustd.Username,
data.Services.Trustd.Password,
)
// TODO: In the case of failure, attempt to generate the identity from
// another RoT.
var conn *grpc.ClientConn
conn, err = basic.NewConnection(data.Services.Trustd.Endpoints[0], port, creds)
if err != nil {
return nil, err
}
client := proto.NewTrustdClient(conn)
return &Generator{
client: client,
}
}, nil
}
// Certificate implements the proto.TrustdClient interface.

View File

@ -51,6 +51,7 @@ type Services struct {
Kubeadm *Kubeadm `yaml:"kubeadm"`
Trustd *Trustd `yaml:"trustd"`
Proxyd *Proxyd `yaml:"proxyd"`
Blockd *Blockd `yaml:"blockd"`
OSD *OSD `yaml:"osd"`
CRT *CRT `yaml:"crt"`
}
@ -106,6 +107,11 @@ type Proxyd struct {
Image string `yaml:"image,omitempty"`
}
// Blockd describes the configuration of the blockd service.
type Blockd struct {
Image string `yaml:"image,omitempty"`
}
// CRT describes the configuration of the container runtime service.
type CRT struct {
Image string `yaml:"image,omitempty"`

View File

@ -522,7 +522,7 @@ tasks:
template: |
WORKDIR $SRC/{{ .Docker.CurrentStage }}
WORKDIR /tools/usr/local
RUN curl -L {{ index .Variables "srcGo" }} | tar -C /tools/usr/local -xz
RUN curl -L {{ index .Variables "srcGo" }} | tar -xz
gperf:
template: |
WORKDIR $SRC/{{ .Docker.CurrentStage }}
@ -840,6 +840,7 @@ tasks:
COPY src/fsh.sh /tools/bin
RUN chmod +x /tools/bin/fsh.sh
RUN fsh.sh {{ index .Variables "rootfs" }}
# WORKDIR $PREFIX/$SRC/{{ .Docker.CurrentStage }}
WORKDIR /tools/usr/local/src/{{ .Docker.CurrentStage }}
RUN curl -L {{index .Variables "srcGlibc" }} | tar --strip-components=1 -xJ
RUN ln -sfv /tools/lib/gcc /usr/lib
@ -894,6 +895,7 @@ tasks:
--without-ncurses
RUN make -j $(($(nproc) / 2))
RUN make install
RUN cp -R ../include $PREFIX/include/util-linux
xml-parser:
template: |
WORKDIR $SRC/{{ .Docker.CurrentStage }}