Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buildsys: add feature to vendor go modules #2053

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/hotdog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/src/
21 changes: 4 additions & 17 deletions packages/hotdog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,10 @@ path = "pkg.rs"
url = "https://github.com/bottlerocket-os/hotdog/archive/3f2ca9275fae8db87409c3a0999aa2c8a4bd44d1/hotdog-3f2ca92.tar.gz"
sha512 = "ebf8f2f8f2c7b12443eae5eccdf0d37724637ac972874b9a88eb8d21d88cbe661b0b53680015ebb8c06e17bde708acf90f39fac82002a4c72266ecdac87c3fb5"

[[package.metadata.build-package.external-files]]
url = "https://github.com/opencontainers/runtime-spec/archive/v1.0.2/runtime-spec-1.0.2.tar.gz"
sha512 = "96676b702d02409d33a5c81886c4db4bf45283c628933c6f0f4c2ed0d7cc44fafe95249151d7dc2d1fc5225944d172cdb45fc2f2f5f9bb87531e93421933b664"

[[package.metadata.build-package.external-files]]
url = "https://github.com/golang/sys/archive/4abf325e0275e4ef0bdd441dcf497570f1419ab9/sys-4abf325.tar.gz"
sha512 = "74c51b1eb48a0a31443f9cb7ee4e2d7550f2477cbc89fad3909f973f042c8bc2bfc378582847c498ce157c3c28d14a3b22d69e9220539dc5dd87a77bb7b407e3"

[[package.metadata.build-package.external-files]]
path = "go-selinux-v1.10.0.tar.gz"
url = "https://github.com/opencontainers/selinux/archive/refs/tags/v1.10.0.tar.gz"
sha512 = "88c2591416aaea346bf438021c5f5095212ab6eec765c4b9ba149bd229cdc92a6baddc11f4aef7cfa7f0a5df6eb453e5670689679fd831848860eb28aca034a2"

[[package.metadata.build-package.external-files]]
path = "libcap-v1.2.62.tar.gz"
url = "https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/libcap-cap/v1.2.62.tar.gz"
sha512 = "d533563e4a2b4b9f910730286180d160c00e436e7f6970e124a67991ab2dcdeca4a6b07cb230d812cc6b4771935accc49ba0ed0465a581a571f0ddb4752284f7"
[[package.metadata.build-package.go-mods]]
input = "hotdog-3f2ca92.tar.gz"
mod-dir = "hotdog-3f2ca9275fae8db87409c3a0999aa2c8a4bd44d1"
output-dir = "src"
Comment on lines +15 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd tend to prefer coming up with some standard contract and automating that in a way that's easy for spec files to consume, so there's less risk of each spec doing things in slightly different ways, which tends to obscure packaging mistakes.

Specifically, if instead of an output dir we created a tarball, then we could reference that archive in the spec file in the usual way - listing it as a Source, and using the %setup or %autosetup macro. The latter would be valuable for any patches we'd carry. There's also a modest performance benefit from not having lots of small files to copy into the build context.

Then we could specify the behavior into the external-files declaration like:

[[package.metadata.build-package.external-files]]
url = "https://github.com/bottlerocket-os/hotdog/archive/3f2ca9275fae8db87409c3a0999aa2c8a4bd44d1/hotdog-3f2ca92.tar.gz"
path = "override-upstream.tar.gz"
sha512 = "ebf8f2f8f2c7b12443eae5eccdf0d37724637ac972874b9a88eb8d21d88cbe661b0b53680015ebb8c06e17bde708acf90f39fac82002a4c72266ecdac87c3fb5"
bundle-modules = [ "go" ]
bundle-path = "override-upstream-bundled.tar.gz"

(Not entirely sure about what to name the output file but there's probably an automatic convention like x.tar.* -> x-bundled.tar.* that we could use.)

What do you think?


[build-dependencies]
glibc = { path = "../glibc" }
Expand Down
49 changes: 9 additions & 40 deletions packages/hotdog/hotdog.spec
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,12 @@
%global gitrev 3f2ca9275fae8db87409c3a0999aa2c8a4bd44d1
%global shortrev %(c=%{gitrev}; echo ${c:0:7})

%global gosysrev 4abf325e0275e4ef0bdd441dcf497570f1419ab9
%global gosysrevshort %(c=%{gosysrev}; echo ${c:0:7})

%global runtimespec 1.0.2

%global goselinux 1.10.0

%global libcap 1.2.62

Name: %{_cross_os}hotdog
Version: 1.0.1
Release: 1%{?dist}
Summary: Tool with OCI hooks to run the Log4j Hot Patch in containers
License: Apache-2.0
URL: https://github.com/awslabs/oci-add-hooks
jpmcb marked this conversation as resolved.
Show resolved Hide resolved
Source0: https://%{goimport}/archive/%{gorev}/%{gorepo}-%{shortrev}.tar.gz
Source1: https://github.com/opencontainers/runtime-spec/archive/v%{runtimespec}/runtime-spec-%{runtimespec}.tar.gz
Source2: https://github.com/golang/sys/archive/%{gosysrev}/sys-%{gosysrevshort}.tar.gz
Source3: https://github.com/opencontainers/selinux/archive/refs/tags/v%{goselinux}.tar.gz#/go-selinux-v%{goselinux}.tar.gz
Source4: https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/libcap-cap/v%{libcap}.tar.gz#/libcap-v%{libcap}.tar.gz

BuildRequires: %{_cross_os}glibc-devel
Requires: %{_cross_os}log4j2-hotpatch
Expand All @@ -38,39 +24,19 @@ Requires: %{_cross_os}log4j2-hotpatch
%{summary}.

%prep
%autosetup -Sgit -n %{gorepo}-%{gitrev} -p1
%cross_go_setup %{gorepo}-%{gitrev} %{goproject} %{goimport}

# We need to manage these third-party dependencies because the hotdog
# "release" that we use doesn't include the `vendor` directory, unlike our other
# go third party dependencies
mkdir -p GOPATH/src/github.com/opencontainers/runtime-spec
tar -C GOPATH/src/github.com/opencontainers/runtime-spec -xzf %{SOURCE1} --strip 1
cp GOPATH/src/github.com/opencontainers/runtime-spec/LICENSE LICENSE.runtime-spec

mkdir -p GOPATH/src/golang.org/x/sys
tar -C GOPATH/src/golang.org/x/sys -xzf %{SOURCE2} --strip 1
cp GOPATH/src/golang.org/x/sys/LICENSE LICENSE.golang-sys

mkdir -p GOPATH/src/github.com/opencontainers/selinux
tar -C GOPATH/src/github.com/opencontainers/selinux -xzf %{SOURCE3} --strip 1
cp GOPATH/src/github.com/opencontainers/selinux/LICENSE LICENSE.go-selinux

mkdir -p GOPATH/src/kernel.org/pub/linux/libs/security/libcap
tar -C GOPATH/src/kernel.org/pub/linux/libs/security/libcap -xzf %{SOURCE4} --strip 2
cp GOPATH/src/kernel.org/pub/linux/libs/security/libcap/License LICENSE.libcap
%setup -T -c
cp -r /home/builder/src/%{gorepo}-%{gitrev}/* .

%build
%cross_go_configure %{goimport}
%set_cross_go_flags

# Set CGO_ENABLED=0 to statically link hotdog-hotpath, since it runs inside containers that
# may not have the glibc version used to compile it
# Set `GO111MODULE=off` to force golang to look for the dependencies in the GOPATH
CGO_ENABLED=0 GO111MODULE=off go build -installsuffix cgo -a -ldflags "-s" -o hotdog-hotpatch ./cmd/hotdog-hotpatch
CGO_ENABLED=0 go build ${GOFLAGS} -installsuffix cgo -a -ldflags "-s" -o hotdog-hotpatch ./cmd/hotdog-hotpatch

# The oci hooks commands can be compiled as we usually compile golang packages
for cmd in hotdog-cc-hook hotdog-poststart-hook; do
GO111MODULE=off go build -buildmode=pie -ldflags "${GOLDFLAGS}" -o $cmd ./cmd/$cmd
go build ${GOFLAGS} -buildmode=pie -ldflags "${GOLDFLAGS}" -o $cmd ./cmd/$cmd
done

%install
Expand All @@ -83,9 +49,12 @@ for cmd in hotdog-cc-hook hotdog-poststart-hook; do
install -p -m 0755 $cmd %{buildroot}%{_cross_libexecdir}/hotdog
done

%cross_scan_attribution go-vendor vendor

%files
%license LICENSE LICENSE.runtime-spec LICENSE.golang-sys LICENSE.go-selinux LICENSE.libcap
%license LICENSE
%{_cross_attribution_file}
%{_cross_attribution_vendor_dir}
%dir %{_cross_libexecdir}/hotdog
%dir %{_cross_datadir}/hotdog
%{_cross_libexecdir}/hotdog/hotdog-cc-hook
Expand Down
1 change: 1 addition & 0 deletions packages/oci-add-hooks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/src/
11 changes: 4 additions & 7 deletions packages/oci-add-hooks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ path = "pkg.rs"
url = "https://github.com/awslabs/oci-add-hooks/archive/ef29fe312d2e1858d5eb28ab0abe0cbee298a165/oci-add-hooks-ef29fe3.tar.gz"
sha512 = "018b561f838172e768a70acdeb2c27939f931391ced019a23c5193eee6b8970bc02a3e5fa05917010ca2064d1876649ef139d7657700c42a3ddd6e2c174f27dc"

[[package.metadata.build-package.external-files]]
url = "https://github.com/bitly/go-simplejson/archive/v0.5.0/go-simplejson-0.5.0.tar.gz"
sha512 = "39c0d85d6ee06a8a795c1e344f0669f5ae8371d1122f09a1b13e5ff7629dd7faf633f9fcb449e19aadab9ad3e42e93143205781a822a29f27758872cf7e09e18"

[[package.metadata.build-package.external-files]]
url = "https://github.com/joeshaw/json-lossless/archive/e0cd1ca6349bf167e33d44f28c14c728a277205f/json-lossless-e0cd1ca.tar.gz"
sha512 = "b9eb6170f662a396370ae1e170d89e15efc0a96fee6046fbd749c7a65f09f808e08bc2cf91962db65fd86a2aac4dddf428412b568fe1d03a77a7de22ad0690aa"
[[package.metadata.build-package.go-mods]]
input = "oci-add-hooks-ef29fe3.tar.gz"
mod-dir = "oci-add-hooks-ef29fe312d2e1858d5eb28ab0abe0cbee298a165"
output-dir = "src"

[build-dependencies]
glibc = { path = "../glibc" }
30 changes: 9 additions & 21 deletions packages/oci-add-hooks/oci-add-hooks.spec
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,36 @@

%global gitrev ef29fe312d2e1858d5eb28ab0abe0cbee298a165
%global shortrev %(c=%{gitrev}; echo ${c:0:7})
%global gosimplejson 0.5.0
%global jsonlosslessrev e0cd1ca6349bf167e33d44f28c14c728a277205f
%global jsonlosslessshort %(c=%{jsonlosslessrev}; echo ${c:0:7})

Name: %{_cross_os}oci-add-hooks
Version: 1.0.0
Release: 1%{?dist}
Summary: OCI runtime wrapper that injects OCI hooks
License: Apache-2.0 and MIT
URL: https://github.com/awslabs/oci-add-hooks
Source0: https://%{goimport}/archive/%{gorev}/%{gorepo}-%{shortrev}.tar.gz
Source1: https://github.com/bitly/go-simplejson/archive/v%{gosimplejson}/go-simplejson-%{gosimplejson}.tar.gz
Source2: https://github.com/joeshaw/json-lossless/archive/%{jsonlosslessrev}/json-lossless-%{jsonlosslessshort}.tar.gz

BuildRequires: %{_cross_os}glibc-devel

%description
%{summary}.

%prep
%autosetup -n %{gorepo}-%{gitrev}
%cross_go_setup %{gorepo}-%{gitrev} %{goproject} %{goimport}

# We need to manage these third-party dependencies because the oci-add-hooks
# "release" that we use doesn't include the `vendor` directory, unlike our other
# go third party dependencies
mkdir -p GOPATH/src/github.com/bitly/go-simplejson GOPATH/src/github.com/joeshaw/json-lossless
tar -C GOPATH/src/github.com/bitly/go-simplejson -xzf %{SOURCE1} --strip 1
cp GOPATH/src/github.com/bitly/go-simplejson/LICENSE LICENSE.go-simplejson
tar -C GOPATH/src/github.com/joeshaw/json-lossless -xzf %{SOURCE2} --strip 1
cp GOPATH/src/github.com/joeshaw/json-lossless/LICENSE LICENSE.json-lossless
%setup -T -c
cp -r /home/builder/src/%{gorepo}-%{gitrev}/* .

%build
%cross_go_configure %{goimport}
# We use `GO111MODULE=off` to force golang to look for the dependencies in the GOPATH
GO111MODULE=off go build -v -x -buildmode=pie -ldflags="${GOLDFLAGS}" -o oci-add-hooks
%set_cross_go_flags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! It'll be really nice to get rid of cross_go_setup and cross_go_configure at some point.

export LD_VERSION="-X main.commit=oci-add-hooks-%{gitrev}"
go build ${GOFLAGS} -v -x -buildmode=pie -ldflags="${GOLDFLAGS} ${LD_VERSION}" -o oci-add-hooks

%install
install -d %{buildroot}%{_cross_bindir}
install -p -m 0755 oci-add-hooks %{buildroot}%{_cross_bindir}

%cross_scan_attribution go-vendor vendor

%files
%license LICENSE NOTICE LICENSE.go-simplejson LICENSE.json-lossless
%license LICENSE NOTICE
%{_cross_attribution_file}
%{_cross_attribution_vendor_dir}
%{_cross_bindir}/oci-add-hooks
160 changes: 160 additions & 0 deletions tools/buildsys/src/gomod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*!
Packages using the Go programming language may have upstream tar archives that
include only the source code of the project, but not the source code of any
dependencies. The Go programming language promotes the use of "modules" for
dependencies and projects adopting modules will provide go.mod and go.sum
files.

This module provides the ability to retrieve and validate the dependencies
declared using Go modules given a tar archive containing a go.mod and go.sum.

The location where dependencies are retrieved from are controlled by the
standard environment variables employed by the Go tool: GOPROXY, GOSUMDB, and
GOPRIVATE.

*/

pub(crate) mod error;
use error::Result;

use super::manifest;
use duct::cmd;
use snafu::{OptionExt, ResultExt};
use std::env;
use std::path::PathBuf;
use std::process::Output;

pub(crate) struct GoMod;

impl GoMod {
pub(crate) fn vendor(
root_dir: &PathBuf,
package_dir: &PathBuf,
gomods: &[manifest::GoModule],
) -> Result<Self> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you can just return nothing in the happy case:

Suggested change
) -> Result<Self> {
) -> Result<()> {

for g in gomods {
let input_path_arg = g.input.as_ref().context(error::InputFileSnafu)?;
let input_path = package_dir.join(input_path_arg);
if !input_path.is_file() {
return Err(error::Error::InputFileBad);
}
Comment on lines +38 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about the lack of the "Snafu" extension - should use ensure! instead. It's also conventional to provide the actual path as context, so it's easier to reason about the error.

Suggested change
if !input_path.is_file() {
return Err(error::Error::InputFileBad);
}
ensure!(input_path.is_file(), error::InputFileBadSnafu { path: input_path });

let mod_dir = g.mod_dir.as_ref().context(error::ModDirSnafu)?;
let output_dir_arg = g.output_dir.as_ref().context(error::OutputDirSnafu)?;
let output_dir = package_dir.join(output_dir_arg);
if output_dir.exists() && !output_dir.is_dir() {
return Err(error::Error::OutputDirBad);
}

// Our SDK and toolchain are picked by the external `cargo make` invocation.
let sdk = getenv("BUILDSYS_SDK_IMAGE")?;

// Several Go variables control proxying
let goproxy = go_env("GOPROXY").unwrap_or("".to_string());
let gosumdb = go_env("GOSUMDB").unwrap_or("".to_string());
let goprivate = go_env("GOPRIVATE").unwrap_or("".to_string());

let args = DockerGoArgs {
module_path: package_dir.clone(),
sdk_image: sdk,
go_mod_cache: root_dir.join(".gomodcache".to_string()),
command: format!(
"mkdir -p {outdir}
tar zxf {input} -C {outdir} &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just have tar figure out what kind of decompression to use:

Suggested change
tar zxf {input} -C {outdir} &&
tar xf {input} -C {outdir} &&

cd {outdir}/{moddir} &&
export GOPROXY={goproxy} &&
export GOSUMDB={gosumdb} &&
export GOPRIVATE={goprivate} &&
go list -mod=readonly ./... >/dev/null && go mod vendor",
input = input_path_arg.to_string_lossy(),
outdir = output_dir_arg.to_string_lossy(),
moddir = mod_dir.to_string_lossy(),
goproxy = goproxy,
gosumdb = gosumdb,
goprivate = goprivate,
),
};
match docker_go(root_dir, &args) {
Ok(_) => continue,
Err(e) => return Err(e),
}
Comment on lines +76 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be written more concisely as:

Suggested change
match docker_go(root_dir, &args) {
Ok(_) => continue,
Err(e) => return Err(e),
}
let _ = docker_go(root_dir, &args)?;

(Might be able to get rid of the let _ = part also.)

}

return Ok(Self);
}
}

struct DockerGoArgs {
module_path: PathBuf,
sdk_image: String,
go_mod_cache: PathBuf,
command: String,
}

/// Run `docker-go` with the specified arguments.
fn docker_go(root_dir: &PathBuf, dg_args: &DockerGoArgs) -> Result<Output> {
let args = vec![
"--module-path",
dg_args
.module_path
.to_str()
.context(error::InputFileSnafu)
.unwrap(),
Comment on lines +97 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm why do we still need to unwrap() here?

Generally that would mean that we would add another .context(error::WhateverSnafu) to the chain.

"--sdk-image",
&dg_args.sdk_image,
"--go-mod-cache",
dg_args
.go_mod_cache
.to_str()
.context(error::InputFileSnafu)
.unwrap(),
"--command",
&dg_args.command,
];
let program = root_dir.join("tools/docker-go");
println!("program: {}", program.to_string_lossy());
let output = cmd(program, args)
.stderr_to_stdout()
.stdout_capture()
.unchecked()
.run()
.context(error::CommandStartSnafu)?;

let stdout = String::from_utf8_lossy(&output.stdout);
println!("{}", &stdout);
return if output.status.success() {
Ok(output)
} else {
Err(error::Error::DockerExecution {
args: "".to_string(),
})
};
Comment on lines +124 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return if output.status.success() {
Ok(output)
} else {
Err(error::Error::DockerExecution {
args: "".to_string(),
})
};
ensure!(output.status.success(), error::DockerExecution { args });
Ok(output)

}

/// Run `go env` with the specified argument.
fn go_env(var: &str) -> Option<String> {
let args = vec!["env", var];
let output = match cmd("go", args)
.stderr_to_stdout()
.stdout_capture()
.unchecked()
.run()
{
Ok(v) => v,
Err(_) => return None,
};
let stdout = String::from_utf8_lossy(&output.stdout);
println!("{}", &stdout);
return if output.status.success() {
Some(stdout.to_string())
} else {
None
};
Comment on lines +147 to +151
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

Suggested change
return if output.status.success() {
Some(stdout.to_string())
} else {
None
};
output.status.success().then(|| stdout.to_string())

}

/// Retrieve a BUILDSYS_* variable that we expect to be set in the environment,
/// and ensure that we track it for changes, since it will directly affect the
/// output.
fn getenv(var: &str) -> Result<String> {
println!("cargo:rerun-if-env-changed={}", var);
env::var(var).context(error::EnvironmentSnafu { var })
}
34 changes: 34 additions & 0 deletions tools/buildsys/src/gomod/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use snafu::Snafu;

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub(crate) enum Error {
#[snafu(display("Failed to start command: {}", source))]
CommandStart { source: std::io::Error },

#[snafu(display("Failed to execute command: 'docker {}'", args))]
DockerExecution { args: String },

#[snafu(display("input is required"))]
InputFile,

#[snafu(display("input must be a file and end with .tar.gz"))]
InputFileBad,
Comment on lines +15 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't seem to check for the "ends with .tar.gz" part (and might not want to, since other compression formats might be used)


#[snafu(display("mod-dir is required"))]
ModDir,

#[snafu(display("output-dir is required"))]
OutputDir,

#[snafu(display("output_dir must not exist or be a directory"))]
OutputDirBad,

#[snafu(display("Missing environment variable '{}'", var))]
Environment {
var: String,
source: std::env::VarError,
},
}

pub(super) type Result<T> = std::result::Result<T, Error>;
Loading