Skip to content

Instantly share code, notes, and snippets.

@schneems
Last active January 13, 2025 16:33
Show Gist options
  • Save schneems/185f4a64440d832baa0c42631a825a77 to your computer and use it in GitHub Desktop.
Save schneems/185f4a64440d832baa0c42631a825a77 to your computer and use it in GitHub Desktop.
$ cat Cargo.toml
[package]
name = "first-path-buildpack-rust"
version = "0.1.0"
edition = "2021"

[dependencies]
libcnb = "0.26.1"
$ cat buildpack.toml
api = "0.10"

[buildpack]
id = "libcnb/first"
version = "0.1.0"
name = "My Buildpack"
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
use libcnb::data::launch::LaunchBuilder;
use libcnb::data::layer_name;
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
use libcnb::generic::{GenericError, GenericMetadata, GenericPlatform};
use libcnb::layer::UncachedLayerDefinition;
use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope};
use libcnb::{buildpack_main, Buildpack, Platform};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;

pub(crate) struct HelloWorldBuildpack;

impl Buildpack for HelloWorldBuildpack {
    // The CNB platform this buildpack targets, usually `GenericPlatform`. See the CNB spec for
    // more information about platforms:
    // https://github.com/buildpacks/spec/blob/main/buildpack.md
    type Platform = GenericPlatform;

    // The type for the metadata of the buildpack itself. This is the data found in the
    // `[metadata]` section of your buildpack's `buildpack.toml`. The framework will automatically
    // try to parse it into the specified type. This example buildpack uses GenericMetadata which
    // provides low-level access to the TOML table.
    type Metadata = GenericMetadata;

    // The error type for this buildpack. Buildpack authors usually implement an enum with
    // specific errors that can happen during buildpack execution. This error type should
    // only contain error specific to this buildpack, such as `CouldNotExecuteMaven` or
    // `InvalidGemfileLock`. This example buildpack uses `GenericError` which means this buildpack
    // does not specify any errors.
    //
    // Common errors that happen during buildpack execution such as I/O errors while
    // writing CNB TOML files are handled by libcnb.rs itself.
    type Error = GenericError;

    // This method will be called when the CNB lifecycle executes the detect phase (`bin/detect`).
    // Use the `DetectContext` to access CNB data such as the operating system this buildpack is currently
    // executed on, the app directory and similar things. When using libcnb.rs, you never have
    // to read environment variables or read/write files to disk to interact with the CNB lifecycle.
    //
    // One example of this is the return type of this method. `DetectResult` encapsulates the
    // required exit code as well as the data written to the build plan. libcnb.rs will,
    // according to the returned value, handle both writing the build plan and exiting with
    // the correct status code for you.
    fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
        DetectResultBuilder::pass().build()
    }

    // Similar to detect, this method will be called when the CNB lifecycle executes the
    // build phase (`bin/build`).
    fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
        println!("Hello World!");
        println!(
            "The build is running on: {} ({})!",
            context.target.os, context.target.arch
        );

        let layer_ref = context.uncached_layer(
            layer_name!("0001_first"),
            UncachedLayerDefinition {
                build: true,
                launch: true,
            },
        )?;
        std::fs::create_dir_all(layer_ref.path().join("bin")).unwrap();
        std::fs::write(layer_ref.path().join("bin").join("lol"), "").unwrap();
        chmod_plus_x(&layer_ref.path().join("bin").join("lol")).unwrap();

        layer_ref.write_env(
            LayerEnv::new()
                .chainable_insert(Scope::All, ModificationBehavior::Delimiter, "PATH", ":")
                .chainable_insert(
                    Scope::All,
                    ModificationBehavior::Prepend,
                    "PATH",
                    "/manually_set_path_prepend/bin",
                ),
        )?;

        let env = layer_ref
            .read_env()?
            .apply(Scope::Build, context.platform.env());
        println!("PATH={:?}", env.get("PATH").unwrap());

        BuildResultBuilder::new()
            .launch(LaunchBuilder::new().build())
            .build()
    }
}

fn chmod_plus_x(path: &Path) -> Result<(), std::io::Error> {
    let mut perms = std::fs::metadata(path)?.permissions();
    let mut mode = perms.mode();
    mode |= 0o700;
    perms.set_mode(mode);

    std::fs::set_permissions(path, perms)
}

// Implements the main function and wires up the framework for the given buildpack.
buildpack_main!(HelloWorldBuildpack);
$ cargo libcnb package
🚚 Preparing package directory...
🖥️ Gathering Cargo configuration (for x86_64-unknown-linux-musl)
🏗️ Building buildpack dependency graph...
🔀 Determining build order...
🚚 Building 1 buildpacks...
📦 [1/1] Building libcnb/first (./)
    Finished `dev` profile [unoptimized] target(s) in 0.02s
Successfully wrote buildpack directory: packaged/x86_64-unknown-linux-musl/debug/libcnb_first (4.89 MiB)
✨ Packaging successfully finished!

💡 To test your buildpack locally with pack, run:
pack build my-image-name \
  --buildpack packaged/x86_64-unknown-linux-musl/debug/libcnb_first \
  --trust-extra-buildpacks \
  --path /path/to/application

/private/tmp/ba6c8b19618d86f52e85c8c8896d5a9e/first-path-buildpack-rust/packaged/x86_64-unknown-linux-musl/debug/libcnb_first
$ pack build debugging-app --path . --buildpack /private/tmp/ba6c8b19618d86f52e85c8c8896d5a9e/first-path-buildpack-rust/packaged/x86_64-unknown-linux-musl/debug/libcnb_first --buildpack ./echo-path-buildpack --platform linux/amd64
24: Pulling from heroku/builder
Digest: sha256:770cbacea87ae3e06c3773723015b03706f98f78b31be4fd1b963c9ba861eb04
Status: Image is up to date for heroku/builder:24
24: Pulling from heroku/heroku
Digest: sha256:9483dc324ebd0615f936808da8da14c401c790f96b13a331208c4513a57c2051
Status: Image is up to date for heroku/heroku:24
Warning: Builder is trusted but additional modules were added; using the untrusted (5 phases) build flow
0.20.5: Pulling from buildpacksio/lifecycle
Digest: sha256:3274fe28594f484240fc3f17e46b7b45e304830e544dcdd82ce19472d159ef71
Status: Image is up to date for buildpacksio/lifecycle:0.20.5
===> ANALYZING
[analyzer] Restoring data for SBOM from previous image
===> DETECTING
[detector] libcnb/first       0.1.0
[detector] examples/echo-path 0.0.1
===> RESTORING
===> BUILDING
[builder] Hello World!
[builder] The build is running on: linux (amd64)!
[builder] read_env: PATH="/layers/libcnb_first/0001_first/bin:/manually_set_path_prepend/bin"
[builder] PATH=/manually_set_path_prepend/bin:/layers/libcnb_first/0001_first/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
===> EXPORTING
[exporter] Reusing layer 'libcnb/first:0001_first'
[exporter] Reusing layer 'buildpacksio/lifecycle:launch.sbom'
[exporter] Reused 1/1 app layer(s)
[exporter] Reusing layer 'buildpacksio/lifecycle:launcher'
[exporter] Reusing layer 'buildpacksio/lifecycle:config'
[exporter] Adding label 'io.buildpacks.lifecycle.metadata'
[exporter] Adding label 'io.buildpacks.build.metadata'
[exporter] Adding label 'io.buildpacks.project.metadata'
[exporter] no default process type
[exporter] Saving debugging-app...
[exporter] *** Images (9270e5dbb797):
[exporter]       debugging-app
Successfully built image debugging-app
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment