Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plumb through container config as sandbox #17

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
101 changes: 55 additions & 46 deletions internal/provider/harness_container_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,52 +63,9 @@ 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(),
),
}
}

Expand Down Expand Up @@ -255,3 +212,55 @@ 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() map[string]schema.Attribute {
return 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,
},
},
},
},
}
}
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(),
},
}),
}
}
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
Loading