Dagger
Search

tapesctl

Package main provides reproducible lint, test, and cross-platform release
builds locally and in GitHub Actions.

tapesctl is a pure-Rust CLI (no Apple frameworks, no C deps), so the
Linux→macOS cross-compile uses `cargo-zigbuild` — zig bundles the macOS
libSystem stubs, so no Apple SDK and no osxcross build are needed (unlike a
workspace with Apple-framework code, which forces the heavier osxcross path).

To wire up after first checkout:

cd tapesctl
dagger develop # regenerates go.mod + dagger codegen
dagger call lint # cargo fmt --check + clippy
dagger call test # cargo test --workspace
dagger call build-release export --path ./build

Installation

dagger install github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d

Entrypoint

Return Type
Tapesctl !
Arguments
NameTypeDefault ValueDescription
sourceDirectory -Project source directory.
Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
func (m *MyModule) Example() *dagger.Tapesctl  {
	return dag.
			Tapesctl()
}
@function
def example() -> dagger.Tapesctl:
	return (
		dag.tapesctl()
	)
@func()
example(): Tapesctl {
	return dag
		.tapesctl()
}

Types

Tapesctl 🔗

Tapesctl is the main module for the tapesctl CI/CD pipeline.

assertStampedIdentity() 🔗

AssertStampedIdentity runs a built binary and fails unless it reports the identity this pipeline stamped into it.

This is a pipeline step rather than a workflow step because what it protects is publication. The release and nightly functions sync artifacts to the public download prefix that install.sh and the documented URLs read from, and a check that runs after that has already let a mis-stamped binary out — blocking the GitHub release afterwards leaves the wrong binary live on the download host, which is where nearly everyone gets one. Run from in here it is ordered before publication by construction, whatever order a workflow happens to put its steps in.

The linux/amd64 artifact is the one asked. All four come out of one build container holding one set of variables, so an injection either reached that container or reached none of them, and a static musl binary needs nothing but a kernel to answer.

Return Type
Void !
Arguments
NameTypeDefault ValueDescription
artifactsDirectory !-

Artifact tree to check, laid out as //tapesctl.

versionString ""

Release tag or channel name the artifacts were stamped with.

commitString ""

Full commit the artifacts were stamped with.

dateString ""

RFC 3339 build timestamp the artifacts were stamped with.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 assert-stamped-identity --artifacts DIR_PATH
func (m *MyModule) Example(ctx context.Context, artifacts *dagger.Directory)   {
	return dag.
			Tapesctl().
			Assertstampedidentity(ctx, artifacts)
}
@function
async def example(artifacts: dagger.Directory) -> None:
	return await (
		dag.tapesctl()
		.assertstampedidentity(artifacts)
	)
@func()
async example(artifacts: Directory): Promise<void> {
	return dag
		.tapesctl()
		.assertStampedIdentity(artifacts)
}

build() 🔗

Build cross-compiles the tapesctl binary for all supported platforms using cargo-zigbuild. Linux targets are static musl builds (curl-and-run, like the old CGO_ENABLED=0 Go binaries); macOS targets link against zig’s bundled libSystem stubs — no Apple SDK required.

The identity arguments are what make a released binary able to name itself. They are optional because this function also serves plain CI builds, which have no release to name: the source arrives here without a .git directory (see the module’s +ignore), so an unstamped build genuinely knows nothing about its own provenance and says so rather than guessing.

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
versionString ""

Release tag or channel name to stamp into the binaries, for example v1.0.0 or nightly.

commitString ""

Full commit the binaries are built from.

dateString ""

RFC 3339 timestamp of the build.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 build
func (m *MyModule) Example() *dagger.Directory  {
	return dag.
			Tapesctl().
			Build()
}
@function
def example() -> dagger.Directory:
	return (
		dag.tapesctl()
		.build()
	)
@func()
example(): Directory {
	return dag
		.tapesctl()
		.build()
}

buildRelease() 🔗

BuildRelease compiles release binaries and adds SHA256 checksums.

It does not check what the binaries report about themselves: that belongs to whatever is about to publish them, which is the only thing that both knows the identity it asked for and can still withhold the result. See [Tapesctl.AssertStampedIdentity].

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
versionString ""

Release tag or channel name to stamp into the binaries, for example v1.0.0 or nightly.

commitString ""

Full commit the binaries are built from.

dateString ""

RFC 3339 timestamp of the build.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 build-release
func (m *MyModule) Example() *dagger.Directory  {
	return dag.
			Tapesctl().
			Buildrelease()
}
@function
def example() -> dagger.Directory:
	return (
		dag.tapesctl()
		.buildrelease()
	)
@func()
example(): Directory {
	return dag
		.tapesctl()
		.buildRelease()
}

lint() 🔗

Lint runs cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings. Both are the same gates make lint runs locally, so a green Dagger lint matches a green local lint.

Return Type
String !
Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 lint
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Tapesctl().
			Lint(ctx)
}
@function
async def example() -> str:
	return await (
		dag.tapesctl()
		.lint()
	)
@func()
async example(): Promise<string> {
	return dag
		.tapesctl()
		.lint()
}

nightly() 🔗

Nightly builds and uploads nightly artifacts.

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
commitString ""

Full commit the nightly is built from. It is what distinguishes one nightly from the next, since they all carry the same version name.

endpointSecret !-

Bucket endpoint URL.

bucketSecret !-

Bucket name.

accessKeyIdSecret !-

Bucket access key ID.

secretAccessKeySecret !-

Bucket secret access key.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 nightly --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRET
func (m *MyModule) Example(endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret) *dagger.Directory  {
	return dag.
			Tapesctl().
			Nightly(endpoint, bucket, accessKeyId, secretAccessKey)
}
@function
def example(endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> dagger.Directory:
	return (
		dag.tapesctl()
		.nightly(endpoint, bucket, accesskeyid, secretaccesskey)
	)
@func()
example(endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Directory {
	return dag
		.tapesctl()
		.nightly(endpoint, bucket, accessKeyId, secretAccessKey)
}

releaseLatest() 🔗

ReleaseLatest builds and uploads versioned and latest release artifacts.

Return Type
Directory !
Arguments
NameTypeDefault ValueDescription
versionString !-

Version string, for example v1.0.0. Names the upload prefix and is stamped into the binaries, so a released tapesctl reports the release it was downloaded from.

Written without backquotes on purpose: the help renderer takes a backquoted word as the flag’s placeholder, so a quoted command name here would be printed where the argument’s type belongs.

commitString ""

Full commit the release is built from.

endpointSecret !-

Bucket endpoint URL.

bucketSecret !-

Bucket name.

accessKeyIdSecret !-

Bucket access key ID.

secretAccessKeySecret !-

Bucket secret access key.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 release-latest --version string --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRET
func (m *MyModule) Example(version string, endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret) *dagger.Directory  {
	return dag.
			Tapesctl().
			Releaselatest(version, endpoint, bucket, accessKeyId, secretAccessKey)
}
@function
def example(version: str, endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> dagger.Directory:
	return (
		dag.tapesctl()
		.releaselatest(version, endpoint, bucket, accesskeyid, secretaccesskey)
	)
@func()
example(version: string, endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Directory {
	return dag
		.tapesctl()
		.releaseLatest(version, endpoint, bucket, accessKeyId, secretAccessKey)
}

test() 🔗

Test runs the workspace unit tests. --locked fails if Cargo.lock is stale, which subsumes the go.mod-tidy check the Go pipeline had.

Return Type
String !
Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 test
func (m *MyModule) Example(ctx context.Context) string  {
	return dag.
			Tapesctl().
			Test(ctx)
}
@function
async def example() -> str:
	return await (
		dag.tapesctl()
		.test()
	)
@func()
async example(): Promise<string> {
	return dag
		.tapesctl()
		.test()
}

uploadInstallSh() 🔗

UploadInstallSh uploads the install script under the tapesctl namespace.

Releases do not need it: ReleaseLatest publishes the install script itself, so a cut cannot succeed while the served installer is stale. This remains a standalone function for republishing the script outside a release — say, after an installer-only fix that should not wait for the next cut.

Return Type
Void !
Arguments
NameTypeDefault ValueDescription
endpointSecret !-

Bucket endpoint URL.

bucketSecret !-

Bucket name.

accessKeyIdSecret !-

Bucket access key ID.

secretAccessKeySecret !-

Bucket secret access key.

Example
dagger -m github.com/papercomputeco/tapesctl@1bad2ba7a0c1ceff23a54894fc836d1cfd6c310d call \
 upload-install-sh --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRET
func (m *MyModule) Example(ctx context.Context, endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret)   {
	return dag.
			Tapesctl().
			Uploadinstallsh(ctx, endpoint, bucket, accessKeyId, secretAccessKey)
}
@function
async def example(endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> None:
	return await (
		dag.tapesctl()
		.uploadinstallsh(endpoint, bucket, accesskeyid, secretaccesskey)
	)
@func()
async example(endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Promise<void> {
	return dag
		.tapesctl()
		.uploadInstallSh(endpoint, bucket, accessKeyId, secretAccessKey)
}