From aa24c6b998c9cbd97953bc28ea0050a5a9a1d6d7 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Mon, 29 Jan 2024 09:59:35 -0500 Subject: [PATCH] plumb through container config as `sandbox` --- docs/resources/harness_container.md | 2 +- docs/resources/harness_k3s.md | 30 ++++++ internal/harnesses/k3s/k3s.go | 43 ++++---- internal/harnesses/k3s/opts.go | 50 +++++++++ .../provider/harness_container_resource.go | 101 ++++++++++-------- internal/provider/harness_k3s_resource.go | 53 +++++++++ 6 files changed, 211 insertions(+), 68 deletions(-) diff --git a/docs/resources/harness_container.md b/docs/resources/harness_container.md index 6e299a1..2c55442 100644 --- a/docs/resources/harness_container.md +++ b/docs/resources/harness_container.md @@ -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) diff --git a/docs/resources/harness_k3s.md b/docs/resources/harness_k3s.md index 796f303..b138517 100644 --- a/docs/resources/harness_k3s.md +++ b/docs/resources/harness_k3s.md @@ -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 @@ -85,3 +86,32 @@ Optional: - `ca_file` (String) - `cert_file` (String) - `key_file` (String) + + + + +### 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) + + +### 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. + + + +### Nested Schema for `sandbox.networks` + +Required: + +- `name` (String) The name of the existing network to attach the container to. diff --git a/internal/harnesses/k3s/k3s.go b/internal/harnesses/k3s/k3s.go index be6d031..cb51031 100644 --- a/internal/harnesses/k3s/k3s.go +++ b/internal/harnesses/k3s/k3s.go @@ -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 { @@ -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 } diff --git a/internal/harnesses/k3s/opts.go b/internal/harnesses/k3s/opts.go index ef7784c..e7afbcd 100644 --- a/internal/harnesses/k3s/opts.go +++ b/internal/harnesses/k3s/opts.go @@ -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" ) @@ -16,6 +18,8 @@ type Opt struct { Registries map[string]*RegistryOpt Mirrors map[string]*RegistryMirrorOpt + + Sandbox provider.DockerRequest } type RegistryOpt struct { @@ -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 } } diff --git a/internal/provider/harness_container_resource.go b/internal/provider/harness_container_resource.go index 803a3ff..4b1de66 100644 --- a/internal/provider/harness_container_resource.go +++ b/internal/provider/harness_container_resource.go @@ -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(), + ), } } @@ -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, + }, + }, + }, + }, + } +} diff --git a/internal/provider/harness_k3s_resource.go b/internal/provider/harness_k3s_resource.go index 1700a90..bef9029 100644 --- a/internal/provider/harness_k3s_resource.go +++ b/internal/provider/harness_k3s_resource.go @@ -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" @@ -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 { @@ -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" } @@ -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(), + }, }), } } @@ -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