From e82cf929f1732cf314c169631f7fa06ca5a9cce0 Mon Sep 17 00:00:00 2001 From: Maya Ozer Date: Sat, 12 Jul 2025 09:02:19 +0300 Subject: [PATCH 1/3] feat: Implement docker init in docker service --- docs/resources/service.md | 2 ++ internal/provider/resource_docker_service.go | 6 ++++++ internal/provider/resource_docker_service_structures.go | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/docs/resources/service.md b/docs/resources/service.md index 7005156a..3c2b86d8 100644 --- a/docs/resources/service.md +++ b/docs/resources/service.md @@ -372,6 +372,8 @@ Optional: - `healthcheck` (Block List, Max: 1) A test to perform to check that the container is healthy (see [below for nested schema](#nestedblock--task_spec--container_spec--healthcheck)) - `hostname` (String) The hostname to use for the container, as a valid RFC 1123 hostname - `hosts` (Block Set) A list of hostname/IP mappings to add to the container's hosts file (see [below for nested schema](#nestedblock--task_spec--container_spec--hosts)) +- `init` (Boolean) Configured whether an init process should be injected for the container. If unset this will default to the +`dockerd` defaults. - `isolation` (String) Isolation technology of the containers running the service. (Windows only). Defaults to `default`. - `labels` (Block Set) User-defined key/value metadata (see [below for nested schema](#nestedblock--task_spec--container_spec--labels)) - `mounts` (Block Set) Specification for mounts to be added to containers created as part of the service (see [below for nested schema](#nestedblock--task_spec--container_spec--mounts)) diff --git a/internal/provider/resource_docker_service.go b/internal/provider/resource_docker_service.go index 8b80e320..eb1d91dc 100644 --- a/internal/provider/resource_docker_service.go +++ b/internal/provider/resource_docker_service.go @@ -517,6 +517,12 @@ func resourceDockerService() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Description: "List of Linux capabilities to drop from the container", }, + "init": { + Type: schema.TypeBool, + Description: "Configured whether an init process should be injected for this container. If unset this will default to the `dockerd` defaults.", + Optional: true, + Computed: true, + }, }, }, }, diff --git a/internal/provider/resource_docker_service_structures.go b/internal/provider/resource_docker_service_structures.go index f842d4cb..a97b2dfd 100644 --- a/internal/provider/resource_docker_service_structures.go +++ b/internal/provider/resource_docker_service_structures.go @@ -183,6 +183,9 @@ func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { if len(in.CapabilityDrop) > 0 { m["cap_drop"] = in.CapabilityDrop } + if in.Init { + m["init"] = in.Init + } out = append(out, m) return out } @@ -965,6 +968,9 @@ func createContainerSpec(v interface{}) (*swarm.ContainerSpec, error) { containerSpec.CapabilityDrop = append(containerSpec.CapabilityDrop, cap.(string)) } } + if value, ok := rawContainerSpec["init"]; ok { + containerSpec.Init = value.(bool) + } } } From ba24e4e98b76567c6a031558ae8e7e1efcee8e84 Mon Sep 17 00:00:00 2001 From: Maya Ozer Date: Sat, 12 Jul 2025 09:48:22 +0300 Subject: [PATCH 2/3] fix: Handle null input --- internal/provider/resource_docker_service_structures.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/provider/resource_docker_service_structures.go b/internal/provider/resource_docker_service_structures.go index a97b2dfd..de877f74 100644 --- a/internal/provider/resource_docker_service_structures.go +++ b/internal/provider/resource_docker_service_structures.go @@ -183,8 +183,8 @@ func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { if len(in.CapabilityDrop) > 0 { m["cap_drop"] = in.CapabilityDrop } - if in.Init { - m["init"] = in.Init + if in.Init != nil { + m["init"] = *in.Init } out = append(out, m) return out @@ -969,7 +969,8 @@ func createContainerSpec(v interface{}) (*swarm.ContainerSpec, error) { } } if value, ok := rawContainerSpec["init"]; ok { - containerSpec.Init = value.(bool) + v := value.(bool) + containerSpec.Init = &v } } From c6cb1ddd0b90500ad4e5dc29df903d670201d8ea Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Thu, 18 Sep 2025 12:16:24 +1000 Subject: [PATCH 3/3] Check Docker client version before setting `ContainerSpec.Init` flag --- .../provider/resource_docker_service_funcs.go | 6 ++-- .../resource_docker_service_structures.go | 32 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/internal/provider/resource_docker_service_funcs.go b/internal/provider/resource_docker_service_funcs.go index 31f3b989..8c700ab1 100644 --- a/internal/provider/resource_docker_service_funcs.go +++ b/internal/provider/resource_docker_service_funcs.go @@ -33,7 +33,7 @@ func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, me var err error client := meta.(*ProviderConfig).DockerClient - serviceSpec, err := createServiceSpec(d) + serviceSpec, err := createServiceSpec(d, meta) if err != nil { return diag.FromErr(err) } @@ -132,7 +132,7 @@ func resourceDockerServiceReadRefreshFunc(ctx context.Context, d.Set("name", service.Spec.Name) d.Set("labels", mapToLabelSet(service.Spec.Labels)) - if err = d.Set("task_spec", flattenTaskSpec(service.Spec.TaskTemplate, d)); err != nil { + if err = d.Set("task_spec", flattenTaskSpec(service.Spec.TaskTemplate, d, meta)); err != nil { log.Printf("[WARN] failed to set task spec from API: %s", err) } if err = d.Set("mode", flattenServiceMode(service.Spec.Mode)); err != nil { @@ -169,7 +169,7 @@ func resourceDockerServiceUpdate(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(err) } - serviceSpec, err := createServiceSpec(d) + serviceSpec, err := createServiceSpec(d, meta) if err != nil { return diag.FromErr(err) } diff --git a/internal/provider/resource_docker_service_structures.go b/internal/provider/resource_docker_service_structures.go index de877f74..e91effd9 100644 --- a/internal/provider/resource_docker_service_structures.go +++ b/internal/provider/resource_docker_service_structures.go @@ -18,10 +18,10 @@ import ( // flatten API objects to the terraform schema // //////////// // see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions -func flattenTaskSpec(in swarm.TaskSpec, d *schema.ResourceData) []interface{} { +func flattenTaskSpec(in swarm.TaskSpec, d *schema.ResourceData, meta interface{}) []interface{} { m := make(map[string]interface{}) if in.ContainerSpec != nil { - m["container_spec"] = flattenContainerSpec(in.ContainerSpec) + m["container_spec"] = flattenContainerSpec(in.ContainerSpec, meta) } if in.Resources != nil { m["resources"] = flattenTaskResources(in.Resources) @@ -111,7 +111,8 @@ func flattenServiceEndpointSpec(in *swarm.EndpointSpec) []interface{} { } // /// start TaskSpec -func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { +func flattenContainerSpec(in *swarm.ContainerSpec, meta interface{}) []interface{} { + client := meta.(*ProviderConfig).DockerClient out := make([]interface{}, 0) m := make(map[string]interface{}) if len(in.Image) > 0 { @@ -183,8 +184,10 @@ func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { if len(in.CapabilityDrop) > 0 { m["cap_drop"] = in.CapabilityDrop } - if in.Init != nil { - m["init"] = *in.Init + if client.ClientVersion() >= "1.37" { + if in.Init != nil { + m["init"] = *in.Init + } } out = append(out, m) return out @@ -577,7 +580,7 @@ func flattenServicePorts(in []swarm.PortConfig) []interface{} { // create API object from the terraform resource schema // //////////// // createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate -func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) { +func createServiceSpec(d *schema.ResourceData, meta interface{}) (swarm.ServiceSpec, error) { serviceSpec := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: d.Get("name").(string), @@ -590,7 +593,7 @@ func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) { } serviceSpec.Labels = labels - taskTemplate, err := createServiceTaskSpec(d) + taskTemplate, err := createServiceTaskSpec(d, meta) if err != nil { return serviceSpec, err } @@ -633,7 +636,7 @@ func createServiceLabels(d *schema.ResourceData) (map[string]string, error) { // == start taskSpec // createServiceTaskSpec creates the task template for the service -func createServiceTaskSpec(d *schema.ResourceData) (swarm.TaskSpec, error) { +func createServiceTaskSpec(d *schema.ResourceData, meta interface{}) (swarm.TaskSpec, error) { taskSpec := swarm.TaskSpec{} if v, ok := d.GetOk("task_spec"); ok { if len(v.([]interface{})) > 0 { @@ -641,7 +644,7 @@ func createServiceTaskSpec(d *schema.ResourceData) (swarm.TaskSpec, error) { rawTaskSpec := rawTaskSpec.(map[string]interface{}) if rawContainerSpec, ok := rawTaskSpec["container_spec"]; ok { - containerSpec, err := createContainerSpec(rawContainerSpec) + containerSpec, err := createContainerSpec(rawContainerSpec, meta) if err != nil { return taskSpec, err } @@ -696,7 +699,8 @@ func createServiceTaskSpec(d *schema.ResourceData) (swarm.TaskSpec, error) { } // createContainerSpec creates the container spec -func createContainerSpec(v interface{}) (*swarm.ContainerSpec, error) { +func createContainerSpec(v interface{}, meta interface{}) (*swarm.ContainerSpec, error) { + client := meta.(*ProviderConfig).DockerClient containerSpec := swarm.ContainerSpec{} if len(v.([]interface{})) > 0 { for _, rawContainerSpec := range v.([]interface{}) { @@ -968,9 +972,11 @@ func createContainerSpec(v interface{}) (*swarm.ContainerSpec, error) { containerSpec.CapabilityDrop = append(containerSpec.CapabilityDrop, cap.(string)) } } - if value, ok := rawContainerSpec["init"]; ok { - v := value.(bool) - containerSpec.Init = &v + if client.ClientVersion() >= "1.37" { + if value, ok := rawContainerSpec["init"]; ok { + v := value.(bool) + containerSpec.Init = &v + } } }