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

Application Mixins (only) #112

Closed
wants to merge 6 commits into from
Closed
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
130 changes: 130 additions & 0 deletions text/0000-app-mixins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Meta
[meta]: #meta
- Name: Application Mixins
- Start Date: 2020-06-22
- Author(s): [Joe Kutner](https://github.com/jkutner/)
- RFC Pull Request: (leave blank)
- CNB Pull Request: (leave blank)
- CNB Issue: (leave blank)
- Supersedes: N/A

# Summary
[summary]: #summary

This is a proposal for allowing application developers (buildpack users) to specify mixins that will be dynamically included with their build and run images. In this way, users can include custom system packages with their applications.

# Motivation
[motivation]: #motivation

Mixins already allow buildpack authors to create buildpacks that depend on an extended set of OS packages without affecting build time, but it's common for application code to depend on OS packages too. However, allowing buildpacks to arbitrarily install OS packages during build would drastically increase build time, especially when CNB tooling is used to build or rebase many apps with similar package requirements.

For that reason, this proposal defines a mechanism to implement dynamic installation of mixins at build time while minimizing the performance impact. This is accomplished by allowing stack images to satisfy required mixin statically and/or reject dynamic mixin installation early in the build process.

# What it is
[what-it-is]: #what-it-is

- *mixin* - a named set of additions to a stack that can be used to make additive changes to the contract.
- *application developer* - a person who is using buildpacks to transform their application code into an OCI image
- *stack buildpack* - a type of buildpack that runs against the stack image(s) instead of an app. Among other things, it can provide mixins.

An application developer may specify a list of mixins in their application's `project.toml` file like this:

```toml
[build]
mixins = [ "<mixin name>" ]
```

When a command like `pack build` is run, the list of mixins will be processed before buildpacks are run. For each mixin name, the following will happen:

* If the mixin name is prefixed with `build:` (as per [RFC-0006](https://github.com/buildpacks/rfcs/blob/main/text/0006-stage-specific-mixins.md)) the mixin will be dynamically added to the build image.
* If the mixin name is prefixed with `run:` (as per [RFC-0006](https://github.com/buildpacks/rfcs/blob/main/text/0006-stage-specific-mixins.md)) the mixin will be dynamically added to the run image.
* If the mixin name does not have a prefix it will be dynamically added to both the build and run stack images.

# How it Works
[how-it-works]: #how-it-works

When a list of mixins are required by buildpacks via the build plan and the build phase starts:

1. The lifecycle will compare the list of mixins to those provided by the stack using the `io.buildpacks.stack.mixins` label.
1. If all mixin names are provided by the stack, no further action is required.
1. If any requested mixin is not provided by the stack, the lifecycle will run the detect phase for all stackpacks defined in the builder.
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit confused - does this mean that we run detect twice? I thought stack packs were supposed to run before userspace buildpacks...

1. The lifecycle will compare the mixins added to the build plan to see if they match the required mixins.
1. If at least one stackpack passes detect and provides the required mixin(s), the lifecycle will execute the stackpack build phase for all passing stackpack(s). If no stackpacks pass detect, or no stackpacks provide the required mixin, the build will fail with a message that describes the mixins that could not be provided.
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me how detect opt-in and buildplan interact. I'm guessing that stackpack_a opts in and provides mixin_a/mixin_b.

But what if stackpack_b opts in and provides mixin_b? Will it also be used, or skipped?

What if a stackpack provides more than what the user wants?

1. During the lifecycle's build phase, the stackpacks that passed detection will run against the build and run images accordingly (see details below). All stackpacks will be run before the user's buildpacks.
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this answers it. So everyone that opts-in will be run, regardless of if they overlap, or provide more mixins than the user needs.

Copy link
Member

Choose a reason for hiding this comment

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

I suggest that we resolve this more cleanly. Maybe we could implement a push-forward mechanism, like we have with build plan entries?

If we implemented the protocol for this entirely on top of the build plan mechanism (perhaps with a mixin = true option in each entry), it might reduce some of this complexity.


## Stack Buildpacks

The complete details of a Stack buildpack are described in [RFC-0069: Stack buildpacks](https://github.com/buildpacks/rfcs/blob/main/text/0069-stack-buildpacks.md).

## Rebasing an App

Before a launch image is rebased, the platform must re-run the any stackpacks that were used to build the launch image against the new run-image. Then, the rebase operation can be performed as normal, while including the stackpack layers as part of the stack. This will be made possible by including the stackpack in the run-image, but because the stackpack detect phase is not run, the operation does not need access to the application source.
Copy link
Member

Choose a reason for hiding this comment

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

How would the list of mixins used be persisted from pack build to pack rebase? I believe that paketobuildpacks make sure that the application code (if it can be compiled into a binary) isn't on the final image - does this require that the project.toml file be kept? Or will the list of used mixins/stackpacks be persisted through another mechanism?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is addressed in RFC-0069: Stack buildpacks. They will be stored in a LABEL.


## Example: ffmpeg

The `project.toml` for an app that requires the `ffmpeg` package might look like this:

```toml
[project]
id = "example/image-app"
name = "My Image App"
version = "1.0.0"

[build]
mixins = [ "ffmpeg" ]
```

## Example: libpq

The `project.toml` for an app that requires the `libpq` package might look like this:

```toml
[project]
id = "example/database-app"
name = "My Database App"
version = "1.0.0"

[build]
mixins = [ "build:libpq-dev", "run:libpq" ]
```

# Drawbacks
[drawbacks]: #drawbacks

- Installing a mixin at build-time means that the `rebase` must also update the provided mixin. In this way, `rebase` becomes an operation that _may_ do more than edit JSON on a registry. It must also re-run a stack buildpack.

# Alternatives
[alternatives]: #alternatives

- [Add root buildpack as interface for app image extension RFC](https://github.com/buildpacks/rfcs/pull/77)
- [App Image Extensions (OS packages)](https://github.com/buildpacks/rfcs/pull/23)
Comment on lines +99 to +100
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind clarifying why these aren't valid alternatives?

Copy link
Member Author

Choose a reason for hiding this comment

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

i wouldn't say they aren't valid approaches. We just don't have support for them.

- [Root Buildpacks](https://github.com/buildpacks/rfcs/blob/root-buildpacks/text/0000-root-buildpacks.md): Allow application developers to use root buildpacks in `project.toml`. This would have significant performance implications, and creates a "foot gun" in which end users could build images they are not able to maintain. For this reason, we are holding off on a generic root buildpack feature.

# Prior Art
[prior-art]: #prior-art

- [RFC-0006: Stage specific mixins](https://github.com/buildpacks/rfcs/blob/main/text/0006-stage-specific-mixins.md)
- [Heroku Apt Buildpack](https://github.com/heroku/heroku-buildpack-apt/)

# Unresolved Questions
[unresolved-questions]: #unresolved-questions

- Should stackpacks be able to define per-stack mixins?
- We could support a top-level `mixins` array, and allow refinements under `[[stacks]] mixins`. If we do this, we need to distinguish between provided and required mixins (at least under `[[stacks]]`).
- If buildpacks can require mixins from `bin/detect`, the stackpack could use this mechanism to require per-stack mixins.
- Should we use regex to match mixins?

# Spec. Changes (OPTIONAL)
[spec-changes]: #spec-changes


## project.toml (TOML)

```
[[build]]
mixins = [ "<mixin name>" ]
```

Where:

* `mixins` - a named set of additions to a stack that can be used to make additive changes to the contract.