Skip to content

Commit 45fa72f

Browse files
committed
feat: add custom image support
1 parent 4bb65c1 commit 45fa72f

File tree

7 files changed

+164
-11
lines changed

7 files changed

+164
-11
lines changed

README.md

+68-5
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ hbox offers the following features:
1717
- **Support for Pipes**: Supports the use of pipes in `hbox run`, enabling efficient command chaining.
1818
- **Convenient Shims**: Creates `shims` (alias shortcuts) for all installed packages, simplifying command entry from `hbox run <package alias> <commands>` to `<package alias> <commands>`.
1919
- **Accessible Internal Binaries**: Provides direct access to internal binaries within images. Users can override the default entrypoint to access essential tools and utilities within containers directly.
20-
- **Customizable Environment Variables**: Allows setting environment variables for each package, enabling finer control over runtime configurations.]()
20+
- **Customizable Environment Variables**: Allows setting environment variables for each package, enabling finer control over runtime configurations.
2121
- **Support for Docker and Podman**: Provides seamless support for both Docker and Podman container engines, offering flexibility in container runtime choices.
22+
- **Custom Images**:
23+
- **Building Custom Images**: Allows users to build custom images on demand, either replacing existing packages or introducing new ones. Users can define build contexts, Dockerfiles, and build arguments within the configuration file.
24+
- **Dynamic Build Arguments**: Supports dynamic and user-defined build arguments, using internal variables like **hbox_package_name** and **hbox_package_version** to tailor the build process to specific needs.
25+
- **Registry and Local Images**: Manages images pulled from registries and locally built images, providing flexibility in how images are sourced and utilized within the hbox environment.
2226

2327
## Installation
2428

@@ -250,7 +254,7 @@ The configuration below is for index (`$HBOX_DIR/index/<shard>/<package>.json`)
250254

251255
| Property | Type | Description |
252256
|-------------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
253-
| `image` | `string` | The Docker image to be used for the package. Example: `"docker.io/busybox"` |
257+
| `image` | `object` | The image configuration, which can include build instructions. Example: `{ "name": "hbox.${hbox_package_name}", "build": { "context": "/path/to/context", "dockerfile": "Dockerfile", "args": { "VERSION": "${hbox_package_version}" }}}` |
254258
| `ports` | `array` | An array of port mappings for the container. Each port mapping has a `host` and `container`. Example: `[{"host": 8090, "container": 8091}, {"host": 8091, "container": 8092}]` |
255259
| `volumes` | `array` | An array of volume mappings for the container. Each volume mapping has a `source` and `target`. Example: `[{"source": ".", "target": "/app"}]` |
256260
| `current_directory` | `string` | The working directory inside the container. Example: `"/app"` |
@@ -260,7 +264,7 @@ The configuration below is for index (`$HBOX_DIR/index/<shard>/<package>.json`)
260264

261265
#### Property Details
262266

263-
- **image**: Specifies the Docker image to be used. This is a required property for defining the container image from which the package will be run.
267+
- **image**: Specifies the image configuration, which can include build instructions. This allows for dynamic and user-defined build arguments, using internal variables like **hbox_package_name** and **hbox_package_version**.
264268

265269
- **ports**: Defines the port mappings between the host and the container. Each port mapping includes:
266270
- `host`: The port on the host machine.
@@ -292,7 +296,9 @@ Example of a `$HBOX_DIR/index/g/golang.json`:
292296

293297
```json
294298
{
295-
"image": "docker.io/golang",
299+
"image": {
300+
"name": "docker.io/golang"
301+
},
296302
"volumes": [
297303
{
298304
"source": ".",
@@ -326,7 +332,9 @@ Example of a `$HBOX_DIR/overrides/busybox.json`:
326332

327333
```json
328334
{
329-
"image": "docker.io/busybox",
335+
"image": {
336+
"name": "docker.io/busybox"
337+
},
330338
"ports": [
331339
{
332340
"host": 8090,
@@ -432,6 +440,61 @@ If you enable logs in your `$HBOX_DIR/config.json` file, they will appear in the
432440

433441
**Note**: Be careful when sharing your logs, as they may contain sensitive information such as API keys and environment variables.
434442

443+
## How To
444+
445+
### Creating packages from custom images
446+
447+
To build custom images, you need to have a Dockerfile and a `.json` hbox config.
448+
449+
Let's take the [pkl](https://pkl-lang.org/) as an example and create a `Dockerfile` for it:
450+
451+
```Dockerfile
452+
FROM alpine:latest
453+
454+
ARG VERSION=0.25.3
455+
456+
RUN apk add --no-cache curl
457+
458+
WORKDIR /usr/local/bin
459+
460+
RUN curl -L -o pkl https://github.com/apple/pkl/releases/download/${VERSION}/pkl-alpine-linux-amd64 && \
461+
chmod +x pkl
462+
463+
ENTRYPOINT ["pkl"]
464+
```
465+
466+
Since this is a custom package, we need to make it visible to hbox, so we will add it into the `$HBOX_DIR/overrides` folder as `pkl.json`.
467+
Let's also make use of hbox internal variables to pass values to the Dockerfile and tag the image accordingly:
468+
469+
```json
470+
{
471+
"image": {
472+
"name": "hbox.${hbox_package_name}",
473+
"build": {
474+
"context": "/home/helton/dockerfiles",
475+
"dockerfile": "/home/helton/dockerfiles/pkl.Dockerfile",
476+
"args": {
477+
"VERSION": "${hbox_package_version}"
478+
}
479+
}
480+
}
481+
}
482+
```
483+
484+
Now we can safely add `pkl` as a package into hbox
485+
486+
```sh
487+
> hbox add pkl 0.25.3
488+
[+] Building 4.8s (8/8) FINISHED
489+
...
490+
=> => naming to docker.io/library/hbox.pkl:0.25.3
491+
Added 'pkl' version '0.25.3'. Current version is '0.25.3'.
492+
> which pkl
493+
/home/helton/.hbox/shims/pkl
494+
> pkl --version
495+
Pkl 0.25.3 (Linux 5.15.0-1053-aws, native)
496+
```
497+
435498
## Next steps
436499

437500
If you want to see my ideas for the future of the project, check out the [ROADMAP](ROADMAP.md).

src/commands.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,14 @@ pub fn configure_setting(path: String, value: Option<String>) -> Result<(), Box<
192192
fn do_add_package(name: &String, version: &String, package: Package) -> Result<(), Box<dyn Error>> {
193193
let mut new_package = package.clone();
194194
new_package.versions.current = version.clone();
195-
if crate::runner::pull(&new_package) {
195+
196+
let should_add_package = if new_package.index.image.is_local() {
197+
crate::runner::build(&new_package)
198+
} else {
199+
crate::runner::pull(&new_package)
200+
};
201+
202+
if should_add_package {
196203
if !&package.index.only_shim_binaries {
197204
add_shim(&name, None)?;
198205
}

src/configs/context.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use crate::packages::Package;
2+
3+
pub struct Context {
4+
package_name: String,
5+
package_version: String,
6+
}
7+
8+
impl Context {
9+
pub fn from(package: &Package) -> Self {
10+
Self {
11+
package_name: package.name.clone(),
12+
package_version: package.versions.current.clone(),
13+
}
14+
}
15+
16+
pub fn apply(&self, mut text: String) -> String {
17+
text = text.replace("${hbox_package_name}", &self.package_name);
18+
text = text.replace("${hbox_package_version}", &self.package_version);
19+
text
20+
}
21+
}

src/configs/index.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::configs::app::AppConfig;
22
use crate::serialization::parse_json;
33
use log::debug;
44
use serde::{Deserialize, Serialize};
5+
use std::collections::HashMap;
56
use std::error::Error;
67
use std::path::Path;
78

@@ -45,7 +46,7 @@ impl IndexConfig {
4546

4647
#[derive(Serialize, Deserialize, Debug, Clone)]
4748
pub struct Package {
48-
pub image: String,
49+
pub image: Image,
4950
pub volumes: Option<Vec<Volume>>,
5051
pub ports: Option<Vec<Port>>,
5152
pub current_directory: Option<String>,
@@ -58,7 +59,10 @@ pub struct Package {
5859
impl Package {
5960
pub fn new(name: &str) -> Self {
6061
Package {
61-
image: format!("docker.io/{}", name),
62+
image: Image {
63+
name: format!("docker.io/{}", name),
64+
build: None,
65+
},
6266
volumes: None,
6367
ports: None,
6468
current_directory: None,
@@ -69,6 +73,25 @@ impl Package {
6973
}
7074
}
7175

76+
#[derive(Serialize, Deserialize, Debug, Clone)]
77+
pub struct Image {
78+
pub name: String,
79+
pub build: Option<Build>,
80+
}
81+
82+
impl Image {
83+
pub fn is_local(&self) -> bool {
84+
self.build.is_some()
85+
}
86+
}
87+
88+
#[derive(Serialize, Deserialize, Debug, Clone)]
89+
pub struct Build {
90+
pub context: String,
91+
pub dockerfile: String,
92+
pub args: Option<HashMap<String, String>>,
93+
}
94+
7295
#[derive(Serialize, Deserialize, Debug, Clone)]
7396
pub struct Volume {
7497
pub source: String,

src/configs/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod app;
2+
pub mod context;
23
pub mod index;
34
pub mod user;
45
pub mod version;

src/packages.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,19 @@ impl Package {
6565
pub fn print(&self, verbose: bool) {
6666
info!("- [{}]", self.name);
6767
if verbose {
68-
info!(" - image: {}", self.index.image);
68+
info!(" - image:");
69+
info!(" - name: {}", &self.index.image.name);
70+
if let Some(build) = &self.index.image.build {
71+
info!(" - build:");
72+
info!(" - dockerfile: {}", build.dockerfile);
73+
info!(" - context: {}", build.context);
74+
info!(" - args:");
75+
if let Some(args) = &build.args {
76+
for (arg_name, arg_value) in args.iter() {
77+
info!(" - {}: {}", arg_name, arg_value);
78+
}
79+
}
80+
}
6981
if let Some(ports) = &self.index.ports {
7082
info!(" - ports:");
7183
for port in ports {

src/runner.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::configs::context::Context;
12
use crate::configs::index::Binary;
23
use crate::configs::user::UserConfig;
34
use crate::packages::Package;
@@ -8,9 +9,32 @@ use std::path::Path;
89
use std::process::{Command, Stdio};
910
use std::thread;
1011

12+
pub fn build(package: &Package) -> bool {
13+
let config = UserConfig::load().unwrap_or_default();
14+
let context = Context::from(package);
15+
let image_name = context.apply(package.index.image.name.clone());
16+
let image = format!("{}:{}", image_name, package.versions.current);
17+
18+
let mut args = vec!["build".to_string(), "-t".to_string(), image];
19+
if let Some(build) = &package.index.image.build {
20+
args.push("-f".to_string());
21+
args.push(build.dockerfile.clone());
22+
if let Some(build_args) = &build.args {
23+
for (key, value) in build_args {
24+
args.push("--build-arg".to_string());
25+
args.push(format!("{}={}", key, context.apply(value.clone())));
26+
}
27+
}
28+
args.push(build.context.clone());
29+
}
30+
31+
run_command_with_args(config.engine.as_str(), &args, None)
32+
}
33+
1134
pub fn pull(package: &Package) -> bool {
1235
let config = UserConfig::load().unwrap_or_default();
13-
let image = format!("{}:{}", package.index.image, package.versions.current);
36+
let image_name = Context::from(package).apply(package.index.image.name.clone());
37+
let image = format!("{}:{}", image_name, package.versions.current);
1438
run_command_with_args(config.engine.as_str(), &["pull".to_string(), image], None)
1539
}
1640

@@ -77,9 +101,11 @@ fn add_default_flags(package: &Package, args: &mut Vec<String>) {
77101
}
78102

79103
fn add_container_image(package: &Package, args: &mut Vec<String>) {
104+
let context = Context::from(&package);
80105
args.push(format!(
81106
"{}:{}",
82-
package.index.image, package.versions.current
107+
context.apply(package.index.image.name.clone()),
108+
package.versions.current
83109
));
84110
}
85111

0 commit comments

Comments
 (0)