Skip to content

Commit

Permalink
feat: Add ability to specify modes and owners in create-layer
Browse files Browse the repository at this point in the history
  • Loading branch information
bcmyers committed Jan 31, 2025
1 parent 045a79e commit 41499db
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 93 deletions.
47 changes: 25 additions & 22 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,6 @@ oci_image_index(<a href="#oci_image_index-name">name</a>, <a href="#oci_image_in
| <a id="oci_image_index-manifests"></a>manifests | - | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |


<a id="oci_image_layer"></a>

## oci_image_layer

<pre>
oci_image_layer(<a href="#oci_image_layer-name">name</a>, <a href="#oci_image_layer-directory">directory</a>, <a href="#oci_image_layer-file_map">file_map</a>, <a href="#oci_image_layer-files">files</a>, <a href="#oci_image_layer-symlinks">symlinks</a>)
</pre>

Create a tarball and an OCI descriptor for it

**ATTRIBUTES**


| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="oci_image_layer-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
| <a id="oci_image_layer-directory"></a>directory | Directory in the tarball to place the `files` | String | optional | `""` |
| <a id="oci_image_layer-file_map"></a>file_map | Dictionary of file -> file location in tarball | <a href="https://bazel.build/rules/lib/dict">Dictionary: Label -> String</a> | optional | `{}` |
| <a id="oci_image_layer-files"></a>files | List of files to include under `directory` | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="oci_image_layer-symlinks"></a>symlinks | Dictionary of symlink -> target entries to place in the tarball | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | `{}` |


<a id="oci_image_layout"></a>

## oci_image_layout
Expand Down Expand Up @@ -122,6 +100,31 @@ Pushes a manifest or a list of manifests to an OCI registry.
| <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 | `{}` |


<a id="oci_image_layer"></a>

## oci_image_layer

<pre>
oci_image_layer(<a href="#oci_image_layer-name">name</a>, <a href="#oci_image_layer-directory">directory</a>, <a href="#oci_image_layer-files">files</a>, <a href="#oci_image_layer-file_map">file_map</a>, <a href="#oci_image_layer-mode_map">mode_map</a>, <a href="#oci_image_layer-owner_map">owner_map</a>, <a href="#oci_image_layer-symlinks">symlinks</a>, <a href="#oci_image_layer-kwargs">kwargs</a>)
</pre>

Creates a tarball and an OCI descriptor for it

**PARAMETERS**


| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="oci_image_layer-name"></a>name | A unique name for this rule | none |
| <a id="oci_image_layer-directory"></a>directory | Directory in the tarball to place the `files` | `None` |
| <a id="oci_image_layer-files"></a>files | List of files to include under `directory` | `None` |
| <a id="oci_image_layer-file_map"></a>file_map | Dictionary of file -> file location in tarball | `None` |
| <a id="oci_image_layer-mode_map"></a>mode_map | Dictionary of file location in tarball -> mode int (e.g. 0x755) | `None` |
| <a id="oci_image_layer-owner_map"></a>owner_map | Dictionary of file location in tarball -> ownership string (e.g. 'root:root') | `None` |
| <a id="oci_image_layer-symlinks"></a>symlinks | Dictionary of symlink -> target entries to place in the tarball | `None` |
| <a id="oci_image_layer-kwargs"></a>kwargs | Additional arguments to pass to the rule, e.g. tags or visibility | none |


<a id="oci_pull"></a>

## oci_pull
Expand Down
8 changes: 7 additions & 1 deletion go/cmd/ocitool/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@rules_go//go:def.bzl", "go_binary", "go_library")
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand Down Expand Up @@ -46,3 +46,9 @@ go_binary(
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)

go_test(
name = "go_default_test",
srcs = ["createlayer_cmd_test.go"],
embed = [":go_default_library"],
)
88 changes: 77 additions & 11 deletions go/cmd/ocitool/createlayer_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"

"github.com/DataDog/rules_oci/go/internal/flagutil"
"github.com/DataDog/rules_oci/go/internal/tarutil"
"github.com/DataDog/rules_oci/go/pkg/ociutil"
"github.com/DataDog/rules_oci/go/pkg/layer"
"github.com/DataDog/rules_oci/go/pkg/ociutil"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -43,25 +45,47 @@ func CreateLayerCmd(c *cli.Context) error {
defer tw.Close()

for _, filePath := range files {
err = tarutil.AppendFileToTarWriter(filePath, filepath.Join(dir, filepath.Base(filePath)), tw)
storePath := filepath.Join(dir, filepath.Base(filePath))

err = tarutil.AppendFileToTarWriter(
/* filePath */ filePath,
/* loc */ storePath,
/* mode */ config.mode(storePath),
/* uname */ config.uname(storePath),
/* gname */ config.gname(storePath),
/* tw */ tw,
)

if err != nil {
return err
}
}

for filePath, storePath := range config.FileMapping {
err = tarutil.AppendFileToTarWriter(filePath, storePath, tw)
err = tarutil.AppendFileToTarWriter(
/* filePath */ filePath,
/* loc */ storePath,
/* mode */ config.mode(storePath),
/* uname */ config.uname(storePath),
/* gname */ config.gname(storePath),
/* tw */ tw,
)

if err != nil {
return err
}
}

for k, v := range config.SymlinkMapping {
err = tw.WriteHeader(&tar.Header{
header := &tar.Header{
Typeflag: tar.TypeSymlink,
Name: k,
Linkname: v,
})
Mode: config.mode(k),
Uname: config.uname(k),
Gname: config.gname(k),
}
err = tw.WriteHeader(header)
if err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
Expand Down Expand Up @@ -98,28 +122,44 @@ type createLayerConfig struct {
BazelLabel string `json:"bazel-label" toml:"bazel-label" yaml:"bazel-label"`
Descriptor string `json:"outd" toml:"outd" yaml:"outd"`
Directory string `json:"dir" toml:"dir" yaml:"dir"`
Files []string `json:"file" toml:"file" yaml:"file"`
FileMapping map[string]string `json:"file-map" toml:"file-map" yaml:"file-map"`
Files []string `json:"file" toml:"file" yaml:"file"`
ModeMapping map[string]int64 `json:"mode-map" toml:"mode-map" yaml:"mode-map"`
OutputLayer string `json:"out" toml:"out" yaml:"out"`
OwnerMapping map[string]string `json:"owner-map" toml:"owner-map" yaml:"owner-map"`
SymlinkMapping map[string]string `json:"symlink" toml:"symlink" yaml:"symlink"`
}

func newCreateLayerConfig(c *cli.Context) *createLayerConfig {
func newCreateLayerConfig(c *cli.Context) (*createLayerConfig, error) {
modeMapping := make(map[string]int64)
for path, modeStr := range c.Generic("mode-map").(*flagutil.KeyValueFlag).Map {
mode, err := strconv.ParseInt(modeStr, 0, 64)
if err != nil {
return nil, fmt.Errorf("error parsing mode-map value %s: %w", modeStr, err)
}
modeMapping[path] = mode
}
return &createLayerConfig{
BazelLabel: c.String("bazel-label"),
Descriptor: c.String("outd"),
Directory: c.String("dir"),
Files: c.StringSlice("file"),
FileMapping: c.Generic("file-map").(*flagutil.KeyValueFlag).Map,
Files: c.StringSlice("file"),
ModeMapping: modeMapping,
OutputLayer: c.String("out"),
Descriptor: c.String("outd"),
OwnerMapping: c.Generic("owner-map").(*flagutil.KeyValueFlag).Map,
SymlinkMapping: c.Generic("symlink").(*flagutil.KeyValueFlag).Map,
}
}, nil
}

func parseConfig(c *cli.Context) (*createLayerConfig, error) {
configFile := c.Path("configuration-file")
if configFile == "" {
return newCreateLayerConfig(c), nil
config, err := newCreateLayerConfig(c)
if err != nil {
return nil, err
}
return config, nil
}

file, err := os.ReadFile(configFile)
Expand All @@ -135,3 +175,29 @@ func parseConfig(c *cli.Context) (*createLayerConfig, error) {

return &config, nil
}

func (c *createLayerConfig) mode(path string) int64 {
if i, exists := c.ModeMapping[path]; exists {
return i
}
return 0
}

func (c *createLayerConfig) uname(path string) string {
if s, exists := c.OwnerMapping[path]; exists {
uname := strings.SplitN(s, ":", 2)[0]
return uname
}
return ""
}

func (c *createLayerConfig) gname(path string) string {
if s, exists := c.OwnerMapping[path]; exists {
parts := strings.SplitN(s, ":", 2)
if len(parts) > 1 {
gname := parts[1]
return gname
}
}
return ""
}
30 changes: 30 additions & 0 deletions go/cmd/ocitool/createlayer_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"strconv"
"testing"
)

func TestParseIntWorksAsExpected(t *testing.T) {
for _, tc := range []struct {
input string
expected int64
}{
{input: "0x755", expected: 0x755},
{input: "1877", expected: 0x755},
{input: "0b11101010101", expected: 0x755},
} {
actual, err := strconv.ParseInt(tc.input, 0, 64)
if err != nil {
t.Errorf("ParseInt(%q, 0, 64) unexpectedly returned an error. Error: %w", tc.input, err)
}
if actual != tc.expected {
t.Errorf(
"ParseInt(%q, 0, 64) = %d, but expected %d",
tc.input,
actual,
tc.expected,
)
}
}
}
8 changes: 8 additions & 0 deletions go/cmd/ocitool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ var app = &cli.App{
Name: "file-map",
Value: &flagutil.KeyValueFlag{},
},
&cli.GenericFlag{
Name: "owner-map",
Value: &flagutil.KeyValueFlag{},
},
&cli.GenericFlag{
Name: "mode-map",
Value: &flagutil.KeyValueFlag{},
},
},
},
{
Expand Down
14 changes: 12 additions & 2 deletions go/internal/tarutil/tarappend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import (

// AppendFileToTarWriter appends a file (given as a filepath) to a tarfile
// through the tarfile interface.
func AppendFileToTarWriter(filePath string, loc string, tw *tar.Writer) error {
func AppendFileToTarWriter(
filePath string,
loc string,
mode int64,
uname,
gname string,
tw *tar.Writer,
) error {
f, err := os.Open(filePath)
if err != nil {
return err
Expand All @@ -26,9 +33,12 @@ func AppendFileToTarWriter(filePath string, loc string, tw *tar.Writer) error {
return err
}

hdr.AccessTime = time.Time{}
hdr.ChangeTime = time.Time{}
hdr.Gname = gname
hdr.ModTime = time.Time{}
hdr.AccessTime = time.Time{}
hdr.Mode = mode
hdr.Uname = uname

hdr.Name = loc

Expand Down
8 changes: 8 additions & 0 deletions oci/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ bzl_library(
visibility = ["//visibility:public"],
deps = [
":image",
":layer",
":oci_image_layout",
":pull",
":push",
Expand Down Expand Up @@ -92,6 +93,13 @@ bzl_library(
visibility = ["//visibility:public"],
)

bzl_library(
name = "layer",
srcs = ["layer.bzl"],
visibility = ["//visibility:public"],
deps = ["@com_github_datadog_rules_oci//oci:providers"],
)

bzl_library(
name = "providers",
srcs = ["providers.bzl"],
Expand Down
3 changes: 2 additions & 1 deletion oci/defs.bzl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" public API """

load(":image.bzl", _oci_image = "oci_image", _oci_image_index = "oci_image_index", _oci_image_layer = "oci_image_layer")
load(":image.bzl", _oci_image = "oci_image", _oci_image_index = "oci_image_index")
load(":layer.bzl", _oci_image_layer = "oci_image_layer")
load(":oci_image_layout.bzl", _oci_image_layout = "oci_image_layout")
load(":pull.bzl", _oci_pull = "oci_pull")
load(":push.bzl", _oci_push = "oci_push")
Expand Down
56 changes: 0 additions & 56 deletions oci/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,62 +21,6 @@ def get_descriptor_file(ctx, desc):

return out

def _oci_image_layer_impl(ctx):
toolchain = ctx.toolchains["@com_github_datadog_rules_oci//oci:toolchain"]

descriptor_file = ctx.actions.declare_file("{}.descriptor.json".format(ctx.label.name))

ctx.actions.run(
executable = toolchain.sdk.ocitool,
arguments = [
"create-layer",
"--out={}".format(ctx.outputs.layer.path),
"--outd={}".format(descriptor_file.path),
"--dir={}".format(ctx.attr.directory),
"--bazel-label={}".format(ctx.label),
] +
["--file={}".format(f.path) for f in ctx.files.files] +
["--symlink={}={}".format(k, v) for k, v in ctx.attr.symlinks.items()] +
["--file-map={}={}".format(k.files.to_list()[0].path, v) for k, v in ctx.attr.file_map.items()],
inputs = ctx.files.files + ctx.files.file_map,
outputs = [
descriptor_file,
ctx.outputs.layer,
],
)

return [
OCIDescriptor(
descriptor_file = descriptor_file,
file = ctx.outputs.layer,
),
]

oci_image_layer = rule(
implementation = _oci_image_layer_impl,
doc = "Create a tarball and an OCI descriptor for it",
attrs = {
"files": attr.label_list(
doc = "List of files to include under `directory`",
allow_files = True,
),
"directory": attr.string(
doc = "Directory in the tarball to place the `files`",
),
"symlinks": attr.string_dict(
doc = "Dictionary of symlink -> target entries to place in the tarball",
),
"file_map": attr.label_keyed_string_dict(
doc = "Dictionary of file -> file location in tarball",
allow_files = True,
),
},
toolchains = ["@com_github_datadog_rules_oci//oci:toolchain"],
outputs = {
"layer": "%{name}-layer.tar.gz",
},
)

def _oci_image_index_impl(ctx):
toolchain = ctx.toolchains["@com_github_datadog_rules_oci//oci:toolchain"]

Expand Down
Loading

0 comments on commit 41499db

Please sign in to comment.