feat: Implement support for docker context (#704)

This commit is contained in:
Martin 2025-04-18 23:22:06 +02:00 committed by GitHub
parent e47077458c
commit 54649a1909
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 113 additions and 1 deletions

View file

@ -154,6 +154,7 @@ provider "docker" {
- `ca_material` (String) PEM-encoded content of Docker host CA certificate
- `cert_material` (String) PEM-encoded content of Docker client certificate
- `cert_path` (String) Path to directory with Docker TLS config
- `context` (String) The name of the Docker context to use. Can also be set via `DOCKER_CONTEXT` environment variable. Overrides the `host` if set.
- `disable_docker_daemon_check` (Boolean) If set to `true`, the provider will not check if the Docker daemon is running. This is useful for resources/data_sourcess that do not require a running Docker daemon, such as the data source `docker_registry_image`.
- `host` (String) The Docker daemon address
- `key_material` (String) PEM-encoded content of Docker client private key

View file

@ -2,6 +2,7 @@ package provider
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
@ -53,6 +54,12 @@ func New(version string) func() *schema.Provider {
},
Description: "The Docker daemon address",
},
"context": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DOCKER_CONTEXT", ""),
Description: "The name of the Docker context to use. Can also be set via `DOCKER_CONTEXT` environment variable. Overrides the `host` if set.",
},
"ssh_opts": {
Type: schema.TypeList,
Optional: true,
@ -178,13 +185,29 @@ func New(version string) func() *schema.Provider {
func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) {
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
var host string
if contextName := d.Get("context").(string); contextName != "" {
usr, err := user.Current()
if err != nil {
return nil, diag.Errorf("Could not determine current user. We don't know what the homedir is to look for docker contexts: %v", err)
}
log.Printf("[DEBUG] Homedir %s", usr.HomeDir)
contextHost, err := getContextHost(contextName, usr.HomeDir)
if err != nil {
return nil, diag.Errorf("Error loading Docker context '%s': %s", contextName, err)
}
host = contextHost
} else {
host = d.Get("host").(string)
}
SSHOptsI := d.Get("ssh_opts").([]interface{})
SSHOpts := make([]string, len(SSHOptsI))
for i, s := range SSHOptsI {
SSHOpts[i] = s.(string)
}
config := Config{
Host: d.Get("host").(string),
Host: host,
SSHOpts: SSHOpts,
Ca: d.Get("ca_material").(string),
Cert: d.Get("cert_material").(string),
@ -229,6 +252,45 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema
}
}
func getContextHost(contextName string, homedir string) (string, error) {
contextsDir := fmt.Sprintf("%s/.docker/contexts/meta", homedir)
files, err := os.ReadDir(contextsDir)
if err != nil {
return "", fmt.Errorf("could not read contexts directory: %v", err)
}
for _, file := range files {
metaFilePath := fmt.Sprintf("%s/%s/meta.json", contextsDir, file.Name())
metaFile, err := os.Open(metaFilePath)
if err != nil {
log.Printf("[DEBUG] Skipping file %s due to error: %v", metaFilePath, err)
continue
}
var meta struct {
Name string `json:"Name"`
Endpoints map[string]struct {
Host string `json:"Host"`
} `json:"Endpoints"`
}
err = json.NewDecoder(metaFile).Decode(&meta)
// Ensure the file is closed immediately after reading
metaFile.Close() // nolint:errcheck
if err != nil {
log.Printf("[DEBUG] Skipping file %s due to JSON parsing error: %v", metaFilePath, err)
continue
}
if meta.Name == contextName {
if endpoint, ok := meta.Endpoints["docker"]; ok {
return endpoint.Host, nil
}
}
}
return "", fmt.Errorf("context '%s' not found", contextName)
}
// AuthConfigs represents authentication options to use for the
// PushImage method accommodating the new X-Registry-Config header
type AuthConfigs struct {

View file

@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"testing"
@ -110,6 +111,54 @@ func testAccPreCheck(t *testing.T) {
}
}
func TestGetContextHost_ValidContext(t *testing.T) {
// Create a temporary directory to simulate Docker contexts
tempDir := t.TempDir()
contextName := "test-context"
contextUUID := "1234-5678-91011"
contextFilePath := fmt.Sprintf("%s/.docker/contexts/meta/%s/meta.json", tempDir, contextUUID)
// Simulate a valid Docker context file
contextData := `{
"Name": "test-context",
"Endpoints": {
"docker": {
"Host": "tcp://docker:2375"
}
}
}`
if err := os.MkdirAll(fmt.Sprintf("%s/.docker/contexts/meta/%s", tempDir, contextUUID), 0755); err != nil {
t.Fatalf("Failed to create context directory: %s", err)
}
if err := os.WriteFile(contextFilePath, []byte(contextData), 0644); err != nil {
t.Fatalf("Failed to write context file: %s", err)
}
// Test the function
host, err := getContextHost(contextName, tempDir)
if err != nil {
t.Fatalf("Expected no error, got: %s", err)
}
if host != "tcp://docker:2375" {
t.Fatalf("Expected host 'tcp://docker:2375', got: %s", host)
}
}
func TestGetContextHost_InvalidContext(t *testing.T) {
// Create a temporary directory to simulate Docker contexts
tempDir := t.TempDir()
if err := os.MkdirAll(fmt.Sprintf("%s/.docker/contexts/meta/foobar", tempDir), 0755); err != nil {
t.Fatalf("Failed to create context directory: %s", err)
}
// Test the function with a non-existent context
_, err := getContextHost("non-existent-context", tempDir)
if err == nil || err.Error() != "context 'non-existent-context' not found" {
t.Fatalf("Expected error 'context 'non-existent-context' not found', got: %v", err)
}
}
const testAccDockerProviderWithIncompleteAuthConfig = `
provider "docker" {
alias = "private"