Skip to content

Commit

Permalink
Improve README.md > move how to use up and add link to gradle-oci-jun…
Browse files Browse the repository at this point in the history
…it-jupiter
  • Loading branch information
SgtSilvio committed Jan 7, 2024
1 parent 4875c94 commit 3aebe6b
Showing 1 changed file with 64 additions and 64 deletions.
128 changes: 64 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,9 @@ This plugin allows:
- Building OCI images according to the [OCI Image Format Specification](https://github.com/opencontainers/image-spec)
- Pulling and pushing images from/to OCI registries according to the [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec)

## Why Another Image Builder?

This plugin does not aim to only be "yet another image builder", but instead tries to solve usability issues of other image builders.

### OCI Images as Plain Files

OCI images are a form of distribution of your application.
You may know other distribution formats (deb, rpm, some zip/tar); all of them are just plain files with some metadata.
The same applies to OCI images: they consist of filesystem layers (tar files) and metadata (json files).

The problem here is that most image builders hide these files from the user.
This creates multiple usability issues.
First, it makes it harder to grasp what an image actually is.
While the previous point might be subjective, the software and services needed for managing the files definitely complicate the development environment.
Examples for the software in question here are the Docker daemon and registries.

This plugin simply outputs all artifacts of an OCI image as files; no Docker daemon or registry required.
Having access to the plain files of an OCI image is beneficial.
Copying files and consuming files in other tools is easy.

### Dependency Management for OCI Images

The following paragraph defines build system terminology before talking about dependency management:
Gradle projects are composed of _tasks_.
Each task can have _inputs_ and _outputs_ - these are just files.
Some task outputs are defined as outputs of the project and are then called _artifacts_.
Artifacts (usually from outside the project) can be consumed by declaring _dependencies_.
When a dependency is resolved, _module metadata_ is used to locate the artifacts and optionally _transitive dependencies_.
The dependency and all its transitive dependencies in the end resolve to a set of artifacts - files that can be used as task inputs.

Now that OCI images are just files they can be modelled as artifacts.
The OCI image artifacts can then be consumed by declaring dependencies.
Furthermore, an OCI image can then also have transitive dependencies.
Parent images (for example declared by the FROM statement in Dockerfiles) should actually be a transitive dependency.
Unfortunately, OCI images lose the metadata about their parent images.
The following diagram shows an example comparison of the representation of OCI images (top) and the representation of _OCI components_ by this plugin (bottom).

![parent-image-dependencies-high-level.drawio.svg](docs/images/parent-image-dependencies-high-level.drawio.svg)

In the above diagram, image 3 has image 2 as parent image that in turn has image 1 as parent image.
Image 3 has no pointer to their parent images anymore.
It is also not possible to split the metadata of image 3 because it is a mix of its own and all parent images' metadata.
This representation is good for using the image, but not ideal for building.
The bottom part visualizes how it looks, when all three images would be built with this plugin.
Each component would only contain its own layers and its own metadata with explicit dependencies on the parent images.
When image 3 needs to be used, it needs to be assembled in the OCI image format.

While this might just seem like an extra step, it enables a lot of flexibility.
For example, it is easy to upgrade the version of a parent image independently, as can be seen in the following example diagram.

![version-upgrade.drawio.svg](docs/images/version-upgrade.drawio.svg)

More sophisticated dependency management can be applied, such as substituting a parent image with one from a different vendor as long as it provides the same capabilities.
Also, the OCI components can be published without the need to republish all artifacts of parent images.

Furthermore, we are used to an image having at maximum one parent image, but this plugin allows to declare multiple parent image dependencies.
The dependencies than form a directed graph which will be traversed when assembling the OCI image.
The following diagram shows an example, where the "application" and "java" OCI components have two parent image dependencies.

![multiple-parent-image-dependencies.drawio.svg](docs/images/multiple-parent-image-dependencies.drawio.svg)

### WIP

## How to Use

The following is an example of a basic hello world Java application that is bundled as an OCI image and then executed as a Testcontainer in a JUnit test.
The following is an example of a basic hello world Java application that is bundled as an OCI image and then executed as a Testcontainer in a JUnit test (using an [integration of gradle-oci and junit-jupiter](https://github.com/SgtSilvio/gradle-oci-junit-jupiter)).

`settings.gradle.kts`

Expand Down Expand Up @@ -187,3 +124,66 @@ public class ImageTest {
## Requirements

- Gradle 7.4 or higher

## Why Another Image Builder?

This plugin does not aim to only be "yet another image builder", but instead tries to solve usability issues of other image builders.

### OCI Images as Plain Files

OCI images are a form of distribution of your application.
You may know other distribution formats (deb, rpm, some zip/tar); all of them are just plain files with some metadata.
The same applies to OCI images: they consist of filesystem layers (tar files) and metadata (json files).

The problem here is that most image builders hide these files from the user.
This creates multiple usability issues.
First, it makes it harder to grasp what an image actually is.
While the previous point might be subjective, the software and services needed for managing the files definitely complicate the development environment.
Examples for the software in question here are the Docker daemon and registries.

This plugin simply outputs all artifacts of an OCI image as files; no Docker daemon or registry required.
Having access to the plain files of an OCI image is beneficial.
Copying files and consuming files in other tools is easy.

### Dependency Management for OCI Images

The following paragraph defines build system terminology before talking about dependency management:
Gradle projects are composed of _tasks_.
Each task can have _inputs_ and _outputs_ - these are just files.
Some task outputs are defined as outputs of the project and are then called _artifacts_.
Artifacts (usually from outside the project) can be consumed by declaring _dependencies_.
When a dependency is resolved, _module metadata_ is used to locate the artifacts and optionally _transitive dependencies_.
The dependency and all its transitive dependencies in the end resolve to a set of artifacts - files that can be used as task inputs.

Now that OCI images are just files they can be modelled as artifacts.
The OCI image artifacts can then be consumed by declaring dependencies.
Furthermore, an OCI image can then also have transitive dependencies.
Parent images (for example declared by the FROM statement in Dockerfiles) should actually be a transitive dependency.
Unfortunately, OCI images lose the metadata about their parent images.
The following diagram shows an example comparison of the representation of OCI images (top) and the representation of _OCI components_ by this plugin (bottom).

![parent-image-dependencies-high-level.drawio.svg](docs/images/parent-image-dependencies-high-level.drawio.svg)

In the above diagram, image 3 has image 2 as parent image that in turn has image 1 as parent image.
Image 3 has no pointer to their parent images anymore.
It is also not possible to split the metadata of image 3 because it is a mix of its own and all parent images' metadata.
This representation is good for using the image, but not ideal for building.
The bottom part visualizes how it looks, when all three images would be built with this plugin.
Each component would only contain its own layers and its own metadata with explicit dependencies on the parent images.
When image 3 needs to be used, it needs to be assembled in the OCI image format.

While this might just seem like an extra step, it enables a lot of flexibility.
For example, it is easy to upgrade the version of a parent image independently, as can be seen in the following example diagram.

![version-upgrade.drawio.svg](docs/images/version-upgrade.drawio.svg)

More sophisticated dependency management can be applied, such as substituting a parent image with one from a different vendor as long as it provides the same capabilities.
Also, the OCI components can be published without the need to republish all artifacts of parent images.

Furthermore, we are used to an image having at maximum one parent image, but this plugin allows to declare multiple parent image dependencies.
The dependencies than form a directed graph which will be traversed when assembling the OCI image.
The following diagram shows an example, where the "application" and "java" OCI components have two parent image dependencies.

![multiple-parent-image-dependencies.drawio.svg](docs/images/multiple-parent-image-dependencies.drawio.svg)

### WIP

0 comments on commit 3aebe6b

Please sign in to comment.