Skip to content

Commit

Permalink
plumb through container config as sandbox
Browse files Browse the repository at this point in the history
  • Loading branch information
joshrwolf committed Jan 29, 2024
1 parent a9bda91 commit 5a2ccab
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 68 deletions.
2 changes: 1 addition & 1 deletion docs/resources/harness_container.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ A harness that runs steps in a sandbox container.
### Optional

- `envs` (Map of String) Environment variables to set on the container.
- `image` (String) The full image reference to use for the k3s container.
- `image` (String) The full image reference to use for the container.
- `mounts` (Attributes List) The list of mounts to create on the container. (see [below for nested schema](#nestedatt--mounts))
- `networks` (Attributes Map) A map of existing networks to attach the container to. (see [below for nested schema](#nestedatt--networks))
- `privileged` (Boolean)
Expand Down
30 changes: 30 additions & 0 deletions docs/resources/harness_k3s.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ A harness that runs steps in a sandbox container networked to a running k3s clus
- `image` (String) The full image reference to use for the k3s container.
- `networks` (Attributes Map) A map of existing networks to attach the harness containers to. (see [below for nested schema](#nestedatt--networks))
- `registries` (Attributes Map) A map of registries containing configuration for optional auth, tls, and mirror configuration. (see [below for nested schema](#nestedatt--registries))
- `sandbox` (Attributes) A map of configuration for the sandbox container. (see [below for nested schema](#nestedatt--sandbox))

### Read-Only

Expand Down Expand Up @@ -85,3 +86,32 @@ Optional:
- `ca_file` (String)
- `cert_file` (String)
- `key_file` (String)



<a id="nestedatt--sandbox"></a>
### Nested Schema for `sandbox`

Optional:

- `envs` (Map of String) Environment variables to set on the container.
- `image` (String) The full image reference to use for the container.
- `mounts` (Attributes List) The list of mounts to create on the container. (see [below for nested schema](#nestedatt--sandbox--mounts))
- `networks` (Attributes Map) A map of existing networks to attach the container to. (see [below for nested schema](#nestedatt--sandbox--networks))
- `privileged` (Boolean)

<a id="nestedatt--sandbox--mounts"></a>
### Nested Schema for `sandbox.mounts`

Required:

- `destination` (String) The absolute path on the container to mount the source directory.
- `source` (String) The relative or absolute path on the host to the source directory to mount.


<a id="nestedatt--sandbox--networks"></a>
### Nested Schema for `sandbox.networks`

Required:

- `name` (String) The name of the existing network to attach the container to.
43 changes: 22 additions & 21 deletions internal/harnesses/k3s/k3s.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ func New(id string, opts ...Option) (types.Harness, error) {
Cni: true,
MetricsServer: false,
Traefik: false,
Sandbox: provider.DockerRequest{
ContainerRequest: provider.ContainerRequest{
Image: "cgr.dev/chainguard/kubectl:latest-dev",
Entrypoint: []string{"/bin/sh", "-c"},
Cmd: []string{"tail -f /dev/null"},
Env: map[string]string{
"KUBECONFIG": "/k3s-config/k3s.yaml",
},
Networks: []string{"bridge"},
User: "0:0",
},
Mounts: []mount.Mount{
{
Type: mount.TypeVolume,
Source: id + "-config",
Target: "/k3s-config",
},
},
},
}

for _, o := range opts {
Expand Down Expand Up @@ -102,27 +121,9 @@ func New(id string, opts ...Option) (types.Harness, error) {
return nil, err
}

sandbox, err := provider.NewDocker(k3s.sandboxName(), provider.DockerRequest{
ContainerRequest: provider.ContainerRequest{
// TODO: Dynamically build this with predetermined apks
Image: "cgr.dev/chainguard/kubectl:latest-dev",
Entrypoint: []string{"/bin/sh", "-c"},
Cmd: []string{"tail -f /dev/null"},
Networks: append([]string{k3s.serviceName()}, opt.Networks...),
Env: map[string]string{
"KUBECONFIG": "/k3s-config/k3s.yaml",
},
// TODO: Not needed with wolfi-base images
User: "0:0",
},
Mounts: []mount.Mount{
{
Type: mount.TypeVolume,
Source: id + "-config",
Target: "/k3s-config",
},
},
})
opt.Sandbox.Networks = append(opt.Sandbox.Networks, k3s.serviceName())

sandbox, err := provider.NewDocker(k3s.sandboxName(), opt.Sandbox)
if err != nil {
return nil, err
}
Expand Down
50 changes: 50 additions & 0 deletions internal/harnesses/k3s/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package k3s
import (
"fmt"

"github.com/chainguard-dev/terraform-provider-imagetest/internal/containers/provider"
"github.com/docker/docker/api/types/mount"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
)
Expand All @@ -16,6 +18,8 @@ type Opt struct {

Registries map[string]*RegistryOpt
Mirrors map[string]*RegistryMirrorOpt

Sandbox provider.DockerRequest
}

type RegistryOpt struct {
Expand Down Expand Up @@ -119,6 +123,52 @@ func WithNetworks(networks ...string) Option {
opt.Networks = []string{}
}
opt.Networks = append(opt.Networks, networks...)

// also append to sandbox networks
if opt.Sandbox.Networks == nil {
opt.Sandbox.Networks = []string{}
}
opt.Sandbox.Networks = append(opt.Sandbox.Networks, networks...)
return nil
}
}

func WithSandboxImage(image string) Option {
return func(opt *Opt) error {
opt.Sandbox.Image = image
return nil
}
}

func WithSandboxMounts(mounts ...mount.Mount) Option {
return func(opt *Opt) error {
if opt.Sandbox.Mounts == nil {
opt.Sandbox.Mounts = []mount.Mount{}
}
opt.Sandbox.Mounts = append(opt.Sandbox.Mounts, mounts...)
return nil
}
}

func WithSandboxNetworks(networks ...string) Option {
return func(opt *Opt) error {
if opt.Sandbox.Networks == nil {
opt.Sandbox.Networks = []string{}
}
opt.Sandbox.Networks = append(opt.Sandbox.Networks, networks...)
return nil
}
}

func WithSandboxEnv(envs provider.Env) Option {
return func(opt *Opt) error {
if opt.Sandbox.Env == nil {
opt.Sandbox.Env = make(provider.Env)
}

for k, v := range envs {
opt.Sandbox.Env[k] = v
}
return nil
}
}
109 changes: 63 additions & 46 deletions internal/provider/harness_container_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,52 +63,11 @@ func (r *HarnessContainerResource) Schema(ctx context.Context, req resource.Sche
resp.Schema = schema.Schema{
MarkdownDescription: `A harness that runs steps in a sandbox container.`,

Attributes: addHarnessResourceSchemaAttributes(map[string]schema.Attribute{
"image": schema.StringAttribute{
Description: "The full image reference to use for the k3s container.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString("cgr.dev/chainguard/wolfi-base:latest"),
},
"privileged": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"envs": schema.MapAttribute{
Description: "Environment variables to set on the container.",
Optional: true,
ElementType: types.StringType,
},
"networks": schema.MapNestedAttribute{
Description: "A map of existing networks to attach the container to.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "The name of the existing network to attach the container to.",
Required: true,
},
},
},
},
"mounts": schema.ListNestedAttribute{
Description: "The list of mounts to create on the container.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"source": schema.StringAttribute{
Description: "The relative or absolute path on the host to the source directory to mount.",
Required: true,
},
"destination": schema.StringAttribute{
Description: "The absolute path on the container to mount the source directory.",
Required: true,
},
},
},
},
}),
Attributes: addHarnessResourceSchemaAttributes(
addContainerResourceSchemaAttributes(
map[string]schema.Attribute{},
),
),
}
}

Expand Down Expand Up @@ -255,3 +214,61 @@ func (r *HarnessContainerResource) Delete(ctx context.Context, req resource.Dele
func (r *HarnessContainerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

// addContainerResourceSchemaAttributes adds common container resource
// attributes to the given map. this function is provided knowing how common it
// is for other harnesses to require some sort of container configuration.
func addContainerResourceSchemaAttributes(attrs map[string]schema.Attribute) map[string]schema.Attribute {
defaults := map[string]schema.Attribute{
"image": schema.StringAttribute{
Description: "The full image reference to use for the container.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString("cgr.dev/chainguard/wolfi-base:latest"),
},
"privileged": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"envs": schema.MapAttribute{
Description: "Environment variables to set on the container.",
Optional: true,
ElementType: types.StringType,
},
"networks": schema.MapNestedAttribute{
Description: "A map of existing networks to attach the container to.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "The name of the existing network to attach the container to.",
Required: true,
},
},
},
},
"mounts": schema.ListNestedAttribute{
Description: "The list of mounts to create on the container.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"source": schema.StringAttribute{
Description: "The relative or absolute path on the host to the source directory to mount.",
Required: true,
},
"destination": schema.StringAttribute{
Description: "The absolute path on the container to mount the source directory.",
Required: true,
},
},
},
},
}

for k, v := range defaults {
attrs[k] = v
}

return attrs
}
53 changes: 53 additions & 0 deletions internal/provider/harness_k3s_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package provider
import (
"context"
"fmt"
"path/filepath"

"github.com/chainguard-dev/terraform-provider-imagetest/internal/harnesses/k3s"
"github.com/chainguard-dev/terraform-provider-imagetest/internal/log"
"github.com/docker/docker/api/types/mount"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
Expand Down Expand Up @@ -45,6 +47,7 @@ type HarnessK3sResourceModel struct {
DisableMetricsServer types.Bool `tfsdk:"disable_metrics_server"`
Registries map[string]RegistryResourceModel `tfsdk:"registries"`
Networks map[string]ContainerResourceModelNetwork `tfsdk:"networks"`
Sandbox types.Object `tfsdk:"sandbox"`
}

type RegistryResourceModel struct {
Expand All @@ -69,6 +72,14 @@ type RegistryResourceMirrorModel struct {
Endpoints types.List `tfsdk:"endpoints"`
}

type HarnessK3sSandboxResourceModel struct {
Image types.String `tfsdk:"image"`
Privileged types.Bool `tfsdk:"privileged"`
Envs types.Map `tfsdk:"envs"`
Mounts []ContainerResourceMountModel `tfsdk:"mounts"`
Networks map[string]ContainerResourceModelNetwork `tfsdk:"networks"`
}

func (r *HarnessK3sResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_harness_k3s"
}
Expand Down Expand Up @@ -160,6 +171,11 @@ func (r *HarnessK3sResource) Schema(ctx context.Context, req resource.SchemaRequ
},
},
},
"sandbox": schema.SingleNestedAttribute{
Description: "A map of configuration for the sandbox container.",
Optional: true,
Attributes: addContainerResourceSchemaAttributes(map[string]schema.Attribute{}),
},
}),
}
}
Expand Down Expand Up @@ -188,6 +204,43 @@ func (r *HarnessK3sResource) Create(ctx context.Context, req resource.CreateRequ
k3s.WithImage(data.Image.ValueString()),
}

if !data.Sandbox.IsNull() {
sandbox := &HarnessK3sSandboxResourceModel{}
resp.Diagnostics.Append(data.Sandbox.As(ctx, &sandbox, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}

if !sandbox.Image.IsNull() {
kopts = append(kopts, k3s.WithSandboxImage(sandbox.Image.ValueString()))
}

for _, m := range sandbox.Mounts {
src, err := filepath.Abs(m.Source.ValueString())
if err != nil {
resp.Diagnostics.AddError("invalid resource input", fmt.Sprintf("invalid mount source: %s", err))
return
}

kopts = append(kopts, k3s.WithSandboxMounts(mount.Mount{
Type: mount.TypeBind,
Source: src,
Target: m.Destination.ValueString(),
}))
}

for _, n := range sandbox.Networks {
kopts = append(kopts, k3s.WithSandboxNetworks(n.Name.ValueString()))
}

envs := make(map[string]string)
if diags := sandbox.Envs.ElementsAs(ctx, &envs, false); diags.HasError() {
resp.Diagnostics.AddError("invalid resource input", fmt.Sprintf("invalid envs input: %s", diags.Errors()))
return
}
kopts = append(kopts, k3s.WithSandboxEnv(envs))
}

registries := make(map[string]RegistryResourceModel)
if data.Registries != nil {
registries = data.Registries
Expand Down

0 comments on commit 5a2ccab

Please sign in to comment.