cliconfig/containersconfig: Container runtime auto-detection

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
Martin Atkins
2025-09-24 17:24:01 -07:00
parent 45d3ed26ce
commit f6b10b1d25
5 changed files with 192 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package containersconfig
import (
"context"
"iter"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentofu/opentofu/internal/containers"
)
func FindTypicalContainerRuntimes(ctx context.Context, env RuntimeDiscoveryEnvironment) iter.Seq2[ociv1.Platform, containers.Runtime] {
// we'll delegate directly to OS-specific implementations in other
// conditionally-compiled files.
return findTypicalContainerRuntimes(ctx, env)
}
type RuntimeDiscoveryEnvironment interface {
// FindCommandExe attempts to find the path to an executable that matches
// the given command name. It returns an empty string if no such executable
// file is available.
//
// [FindTypicalContainerRuntimes] uses this to try to automatically discover
// various different OCI-compatible language runtimes by testing whether
// their usual command names correspond to programs installed on the system.
FindCommandExe(ctx context.Context, name string) string
// ContainerIDBase returns a string to use as part of a container ID for
// a container built from an image obtained from the given location and
// with the given manifest.
ContainerIDBase(registryName, repositoryName string, manifest *ociv1.Manifest) string
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build linux
package containersconfig
import (
"context"
"iter"
"runtime"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentofu/opentofu/internal/containers"
)
func findTypicalContainerRuntimes(ctx context.Context, env RuntimeDiscoveryEnvironment) iter.Seq2[ociv1.Platform, containers.Runtime] {
platform := ociv1.Platform{
OS: "linux",
Architecture: runtime.GOARCH,
}
return func(yield func(ociv1.Platform, containers.Runtime) bool) {
if exePath := env.FindCommandExe(ctx, "runc"); exePath != "" {
yield(platform, makeExecRuntime(env, exePath, func(containerID, bundleDir string) []string {
// no bundleDir included because runc uses the current working directory
return []string{"create", containerID}
}))
return
}
if exePath := env.FindCommandExe(ctx, "systemd-nspawn"); exePath != "" {
yield(platform, makeExecRuntime(env, exePath, func(containerID, bundleDir string) []string {
return []string{"--machine=" + containerID, "--oci-layout=" + bundleDir, "--no-pager", "--no-ask-password"}
}))
return
}
}
}
func makeExecRuntime(env RuntimeDiscoveryEnvironment, exePath string, argsFunc func(containerID, bundleDir string) []string) containers.Runtime {
return &containers.ExecRuntime{
ContainerIDBaseFunc: env.ContainerIDBase,
RunCommandFunc: func(containerID, bundleDir string) (string, []string) {
args := argsFunc(containerID, bundleDir)
return exePath, args
},
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build !linux && !windows
package containersconfig
import (
"context"
"iter"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentofu/opentofu/internal/containers"
)
func findTypicalContainerRuntimes(_ context.Context, _ RuntimeDiscoveryEnvironment) iter.Seq2[ociv1.Platform, containers.Runtime] {
// On unsupported platforms we just return nothing at all. Container
// runtimes must be explicitly configured.
return func(yield func(ociv1.Platform, containers.Runtime) bool) {}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build windows
package containersconfig
import (
"context"
"iter"
"runtime"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentofu/opentofu/internal/containers"
)
func findTypicalContainerRuntimes(ctx context.Context, env RuntimeDiscoveryEnvironment) iter.Seq2[ociv1.Platform, containers.Runtime] {
// Windows has support for both Windows-native containers and for
// Linux-based containers run in a Hyper-V based virtual machine.
//
// We'll therefore report runtimes for both of these.
// This means that when we find an index manifest that has both a
// Windows and a Linux descriptor we'll prefer the Windows one,
// but we can still use Linux-only images when that's all we have.
windowsPlatform := ociv1.Platform{
OS: "windows",
Architecture: runtime.GOARCH,
// OpenTofu only suppports hosts that have the "win32k" feature,
// which is the Windows API subsystem, so we'll include that so we
// can match descriptors which require it.
// (Only very cut-down environments, such as Windows Nano Server
// used as a base image for Windows-native containers, leave out
// this subsystem. But this feature is talking about the capabilities
// of the host, rather than what's installed in the container, so
// that would be relevant only if OpenTofu were run inside such a
// container, which we don't support.)
OSFeatures: []string{"win32k"},
}
linuxPlatform := ociv1.Platform{
OS: "linux",
Architecture: runtime.GOARCH,
}
return func(yield func(ociv1.Platform, containers.Runtime) bool) {
if exePath := env.FindCommandExe(ctx, "runhcs"); exePath != "" {
// "runhcs" was forked from "runc", so its command line usage
// patterns are similar.
runtime := makeExecRuntime(env, exePath, func(containerID, bundleDir string) []string {
// no bundleDir included because runc uses the current working directory
return []string{"create", containerID}
})
if !yield(windowsPlatform, runtime) {
return
}
if !yield(linuxPlatform, runtime) {
return
}
}
}
}
func makeExecRuntime(env RuntimeDiscoveryEnvironment, exePath string, argsFunc func(containerID, bundleDir string) []string) containers.Runtime {
return &containers.ExecRuntime{
ContainerIDBaseFunc: env.ContainerIDBase,
RunCommandFunc: func(containerID, bundleDir string) (string, []string) {
args := argsFunc(containerID, bundleDir)
return exePath, args
},
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package containersconfig contains types used for describing (or automatically
// discovering) container-execution-related settings.
//
// The rest of OpenTofu should make use of this package only indirectly through
// package cliconfig, which is responsible for making the policy decisions.
package containersconfig