Skip to content

Commit c83ca13

Browse files
Add config command to ocitool (#81)
Provides a way for consumers of ocitool to fetch an image's config. Also adds a rule that demos and runs the new cmd.
1 parent 524f6aa commit c83ca13

File tree

10 files changed

+268
-2
lines changed

10 files changed

+268
-2
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ bazel-rules_oci
44
bazel-testlogs
55

66
bin/ocitool-*
7+
8+
.ijwb

BUILD.bazel

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ oci_local_toolchain(
1313

1414
buildifier(
1515
name = "buildifier",
16-
exclude_patterns = ["./.git/*"],
16+
exclude_patterns = [
17+
"./.git/*",
18+
"./.ijwb/*",
19+
],
1720
lint_mode = "warn",
1821
mode = "fix",
1922
)
2023

2124
buildifier_test(
2225
name = "buildifier_test",
23-
exclude_patterns = ["./.git/*"],
26+
exclude_patterns = [
27+
"./.git/*",
28+
"./.ijwb/*",
29+
],
2430
lint_mode = "warn",
2531
no_sandbox = True,
2632
workspace = "//:WORKSPACE",

docs/docs.md

+53
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ be used to extract the image manifest.
3030
| <a id="oci_image-os"></a>os | Used to extract a manifest from base if base is an index | String | optional | `""` |
3131

3232

33+
<a id="oci_image_config"></a>
34+
35+
## oci_image_config
36+
37+
<pre>
38+
oci_image_config(<a href="#oci_image_config-name">name</a>, <a href="#oci_image_config-arch">arch</a>, <a href="#oci_image_config-image">image</a>, <a href="#oci_image_config-os">os</a>)
39+
</pre>
40+
41+
42+
43+
**ATTRIBUTES**
44+
45+
46+
| Name | Description | Type | Mandatory | Default |
47+
| :------------- | :------------- | :------------- | :------------- | :------------- |
48+
| <a id="oci_image_config-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
49+
| <a id="oci_image_config-arch"></a>arch | Used to extract config from image if image is an index | String | optional | `""` |
50+
| <a id="oci_image_config-image"></a>image | - | <a href="https://bazel.build/concepts/labels">Label</a> | required | |
51+
| <a id="oci_image_config-os"></a>os | Used to extract config from image if image is an index | String | optional | `""` |
52+
53+
3354
<a id="oci_image_index"></a>
3455

3556
## oci_image_index
@@ -100,6 +121,38 @@ Pushes a manifest or a list of manifests to an OCI registry.
100121
| <a id="oci_push-x_meta_headers"></a>x_meta_headers | (optional) A list of key/values to to be sent to the registry as headers with an X-Meta- prefix. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | `{}` |
101122

102123

124+
<a id="generate_config_file_action"></a>
125+
126+
## generate_config_file_action
127+
128+
<pre>
129+
generate_config_file_action(<a href="#generate_config_file_action-ctx">ctx</a>, <a href="#generate_config_file_action-config_file">config_file</a>, <a href="#generate_config_file_action-image">image</a>, <a href="#generate_config_file_action-os">os</a>, <a href="#generate_config_file_action-arch">arch</a>)
130+
</pre>
131+
132+
Generates a run action with that extracts an image's config file.
133+
134+
In order to use this action, the calling rule _must_ register
135+
`@com_github_datadog_rules_oci//oci:toolchain` and the image
136+
must provide the `OCIDescriptor` and `OCILayout` (this should
137+
not be an issue when using the `oci_image` rule).
138+
139+
140+
**PARAMETERS**
141+
142+
143+
| Name | Description | Default Value |
144+
| :------------- | :------------- | :------------- |
145+
| <a id="generate_config_file_action-ctx"></a>ctx | The current rules context | none |
146+
| <a id="generate_config_file_action-config_file"></a>config_file | The file to write the config to | none |
147+
| <a id="generate_config_file_action-image"></a>image | The image to extract the config from. | none |
148+
| <a id="generate_config_file_action-os"></a>os | The os to extract the config for | none |
149+
| <a id="generate_config_file_action-arch"></a>arch | The arch to extract the config for | none |
150+
151+
**RETURNS**
152+
153+
The config file named after the rule, os, and arch
154+
155+
103156
<a id="oci_image_layer"></a>
104157

105158
## oci_image_layer

go/cmd/ocitool/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go_library(
44
name = "go_default_library",
55
srcs = [
66
"appendlayer_cmd.go",
7+
"config_cmd.go",
78
"createlayer_cmd.go",
89
"desc_helpers.go",
910
"digest_cmd.go",

go/cmd/ocitool/config_cmd.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/DataDog/rules_oci/go/pkg/ociutil"
7+
"github.com/containerd/containerd/images"
8+
"github.com/containerd/containerd/platforms"
9+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
10+
"github.com/urfave/cli/v2"
11+
"os"
12+
)
13+
14+
// ConfigCmd writes a given layouts config.
15+
func ConfigCmd(c *cli.Context) error {
16+
localProviders, err := LoadLocalProviders(c.StringSlice("layout"), "")
17+
if err != nil {
18+
return err
19+
}
20+
21+
allLocalProviders := ociutil.MultiProvider(localProviders...)
22+
23+
// Read the base descriptor. Its unknown since we don't know if it's an image or index.
24+
baseUnknownDesc, err := ociutil.ReadDescriptorFromFile(c.String("base"))
25+
if err != nil {
26+
return err
27+
}
28+
29+
targetPlatform := ocispec.Platform{
30+
OS: c.String("os"),
31+
Architecture: c.String("arch"),
32+
}
33+
targetPlatformMatch := platforms.Only(targetPlatform)
34+
35+
// Resolve the unknown descriptor into an image manifest
36+
// If the descriptor is an index, match the requested platform.
37+
var baseManifestDesc ocispec.Descriptor
38+
if images.IsIndexType(baseUnknownDesc.MediaType) {
39+
index, err := ociutil.ImageIndexFromProvider(c.Context, allLocalProviders, baseUnknownDesc)
40+
if err != nil {
41+
return err
42+
}
43+
44+
baseManifestDesc, err = ociutil.ManifestFromIndex(index, targetPlatformMatch)
45+
if err != nil {
46+
return err
47+
}
48+
49+
if !targetPlatformMatch.Match(*baseManifestDesc.Platform) {
50+
return fmt.Errorf("invalid platform, expected %v, recieved %v", targetPlatform, *baseManifestDesc.Platform)
51+
}
52+
} else if images.IsManifestType(baseUnknownDesc.MediaType) {
53+
baseManifestDesc = baseUnknownDesc
54+
55+
if ociutil.IsEmptyPlatform(baseManifestDesc.Platform) {
56+
platform, err := ociutil.ResolvePlatformFromDescriptor(c.Context, allLocalProviders, baseManifestDesc)
57+
if err != nil {
58+
return fmt.Errorf("no platform for base: %w", err)
59+
}
60+
baseManifestDesc.Platform = &platform
61+
}
62+
} else {
63+
return fmt.Errorf("unknown base image type %q", baseUnknownDesc.MediaType)
64+
}
65+
66+
manifest, err := ociutil.ImageManifestFromProvider(c.Context, allLocalProviders, baseManifestDesc)
67+
if err != nil {
68+
return fmt.Errorf("no image manifest (%v) in store: %w", baseManifestDesc, err)
69+
}
70+
71+
imageConfig, err := ociutil.ImageConfigFromProvider(c.Context, allLocalProviders, manifest.Config)
72+
if err != nil {
73+
return fmt.Errorf("no image config (%v) in store: %w", manifest.Config, err)
74+
}
75+
76+
// Write the image config to the output file.
77+
outConfig, err := os.Create(c.String("out-config"))
78+
if err != nil {
79+
return err
80+
}
81+
defer func() { _ = outConfig.Close() }()
82+
83+
err = json.NewEncoder(outConfig).Encode(imageConfig)
84+
if err != nil {
85+
return err
86+
}
87+
88+
return nil
89+
}

go/cmd/ocitool/main.go

+21
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,27 @@ as described in https://github.com/opencontainers/image-spec/blob/main/image-lay
232232
},
233233
},
234234
},
235+
{
236+
Name: "config",
237+
Description: "Fetch the config of an image based on the given input layout.",
238+
Action: ConfigCmd,
239+
Flags: []cli.Flag{
240+
&cli.StringFlag{
241+
Name: "base",
242+
Required: true,
243+
},
244+
&cli.StringFlag{
245+
Name: "os",
246+
},
247+
&cli.StringFlag{
248+
Name: "arch",
249+
},
250+
&cli.StringFlag{
251+
Name: "out-config",
252+
Required: true,
253+
},
254+
},
255+
},
235256
{
236257
Name: "push-blob",
237258
Hidden: true,

oci/BUILD.bazel

+11
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ bzl_library(
3737
srcs = ["defs.bzl"],
3838
visibility = ["//visibility:public"],
3939
deps = [
40+
":config",
4041
":image",
4142
":layer",
4243
":oci_image_layout",
@@ -45,6 +46,16 @@ bzl_library(
4546
],
4647
)
4748

49+
bzl_library(
50+
name = "config",
51+
srcs = ["config.bzl"],
52+
visibility = ["//visibility:public"],
53+
deps = [
54+
"@com_github_datadog_rules_oci//oci:image",
55+
"@com_github_datadog_rules_oci//oci:providers",
56+
],
57+
)
58+
4859
bzl_library(
4960
name = "image",
5061
srcs = ["image.bzl"],

oci/config.bzl

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
""" config """
2+
3+
load("@com_github_datadog_rules_oci//oci:image.bzl", "get_descriptor_file")
4+
load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout")
5+
6+
def generate_config_file_action(ctx, config_file, image, os, arch):
7+
""" Generates a run action with that extracts an image's config file.
8+
9+
In order to use this action, the calling rule _must_ register
10+
`@com_github_datadog_rules_oci//oci:toolchain` and the image
11+
must provide the `OCIDescriptor` and `OCILayout` (this should
12+
not be an issue when using the `oci_image` rule).
13+
14+
Args:
15+
ctx: The current rules context
16+
config_file: The file to write the config to
17+
image: The image to extract the config from.
18+
os: The os to extract the config for
19+
arch: The arch to extract the config for
20+
21+
Returns:
22+
The config file named after the rule, os, and arch
23+
"""
24+
toolchain = ctx.toolchains["@com_github_datadog_rules_oci//oci:toolchain"]
25+
26+
base_desc = get_descriptor_file(ctx, image[OCIDescriptor])
27+
base_layout = image[OCILayout]
28+
29+
ctx.actions.run(
30+
executable = toolchain.sdk.ocitool,
31+
arguments = [
32+
"--layout={}".format(base_layout.blob_index.path),
33+
"config",
34+
"--base={}".format(base_desc.path),
35+
"--os={}".format(os),
36+
"--arch={}".format(arch),
37+
"--out-config={}".format(config_file.path),
38+
],
39+
inputs = [
40+
base_desc,
41+
base_layout.blob_index,
42+
] + base_layout.files.to_list(),
43+
outputs = [
44+
config_file,
45+
],
46+
)
47+
48+
return config_file
49+
50+
def _oci_image_config_impl(ctx):
51+
config_file = ctx.actions.declare_file("{}.config.json".format(ctx.label.name))
52+
53+
return DefaultInfo(files = depset([
54+
generate_config_file_action(ctx, config_file, ctx.attr.image, ctx.attr.os, ctx.attr.arch),
55+
]))
56+
57+
oci_image_config_rule = struct(
58+
implementation = _oci_image_config_impl,
59+
attrs = {
60+
"image": attr.label(
61+
mandatory = True,
62+
providers = [OCIDescriptor, OCILayout],
63+
),
64+
"os": attr.string(
65+
doc = "Used to extract config from image if image is an index",
66+
),
67+
"arch": attr.string(
68+
doc = "Used to extract config from image if image is an index",
69+
),
70+
},
71+
toolchains = ["@com_github_datadog_rules_oci//oci:toolchain"],
72+
)
73+
74+
oci_image_config = rule(
75+
implementation = oci_image_config_rule.implementation,
76+
attrs = oci_image_config_rule.attrs,
77+
toolchains = oci_image_config_rule.toolchains,
78+
)

oci/defs.bzl

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" public API """
22

3+
load(":config.bzl", _generate_config_file_action = "generate_config_file_action", _oci_image_config = "oci_image_config")
34
load(":image.bzl", _oci_image = "oci_image", _oci_image_index = "oci_image_index")
45
load(":layer.bzl", _oci_image_layer = "oci_image_layer")
56
load(":oci_image_layout.bzl", _oci_image_layout = "oci_image_layout")
@@ -10,6 +11,9 @@ oci_pull = _oci_pull
1011
oci_push = _oci_push
1112

1213
oci_image = _oci_image
14+
oci_image_config = _oci_image_config
1315
oci_image_index = _oci_image_index
1416
oci_image_layer = _oci_image_layer
1517
oci_image_layout = _oci_image_layout
18+
19+
generate_config_file_action = _generate_config_file_action

oci/image.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,5 @@ oci_image = rule(
227227
),
228228
},
229229
toolchains = ["@com_github_datadog_rules_oci//oci:toolchain"],
230+
provides = [OCIDescriptor, OCILayout],
230231
)

0 commit comments

Comments
 (0)