Matthew Rich
dc85c45226
All checks were successful
Declarative Tests / test (push) Successful in 50s
279 lines
7.7 KiB
Go
279 lines
7.7 KiB
Go
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
|
|
|
|
// Container resource
|
|
package resource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
_ "os"
|
|
_ "gopkg.in/yaml.v3"
|
|
_ "os/exec"
|
|
_ "strings"
|
|
"log/slog"
|
|
"github.com/docker/docker/api/types/strslice"
|
|
"github.com/docker/docker/api/types/mount"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/api/types/network"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"gopkg.in/yaml.v3"
|
|
"net/url"
|
|
"path/filepath"
|
|
)
|
|
|
|
type ContainerClient interface {
|
|
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
|
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
|
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
|
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
|
Close() error
|
|
}
|
|
|
|
type Container struct {
|
|
loader YamlLoader
|
|
Id string `yaml:"ID",omitempty`
|
|
Name string `yaml:"name"`
|
|
Path string `yaml:"path"`
|
|
Cmd []string `yaml:"cmd",omitempty`
|
|
Entrypoint strslice.StrSlice `yaml:"entrypoint",omitempty`
|
|
Args []string `yaml:"args",omitempty`
|
|
Environment map[string]string `yaml:"environment"`
|
|
Image string `yaml:"image"`
|
|
ResolvConfPath string `yaml:"resolvconfpath"`
|
|
HostnamePath string `yaml:"hostnamepath"`
|
|
HostsPath string `yaml:"hostspath"`
|
|
LogPath string `yaml:"logpath"`
|
|
Created string `yaml:"created"`
|
|
ContainerState types.ContainerState `yaml:"containerstate"`
|
|
RestartCount int `yaml:"restartcount"`
|
|
Driver string `yaml:"driver"`
|
|
Platform string `yaml:"platform"`
|
|
MountLabel string `yaml:"mountlabel"`
|
|
ProcessLabel string `yaml:"processlabel"`
|
|
AppArmorProfile string `yaml:"apparmorprofile"`
|
|
ExecIDs []string `yaml:"execids"`
|
|
HostConfig container.HostConfig `yaml:"hostconfig"`
|
|
GraphDriver types.GraphDriverData `yaml:"graphdriver"`
|
|
SizeRw *int64 `json:",omitempty"`
|
|
SizeRootFs *int64 `json:",omitempty"`
|
|
/*
|
|
Mounts []MountPoint
|
|
Config *container.Config
|
|
NetworkSettings *NetworkSettings
|
|
*/
|
|
|
|
State string `yaml:"state"`
|
|
|
|
apiClient ContainerClient
|
|
}
|
|
|
|
func init() {
|
|
ResourceTypes.Register("container", func(u *url.URL) Resource {
|
|
c := NewContainer(nil)
|
|
c.Name = filepath.Join(u.Hostname(), u.Path)
|
|
return c
|
|
})
|
|
}
|
|
|
|
func NewContainer(containerClientApi ContainerClient) *Container {
|
|
var apiClient ContainerClient = containerClientApi
|
|
if apiClient == nil {
|
|
var err error
|
|
apiClient, err = client.NewClientWithOpts(client.FromEnv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return &Container{
|
|
loader: YamlLoadDecl,
|
|
apiClient: apiClient,
|
|
}
|
|
}
|
|
|
|
func (c *Container) URI() string {
|
|
return fmt.Sprintf("container://%s", c.Id)
|
|
}
|
|
|
|
func (c *Container) SetURI(uri string) error {
|
|
resourceUri, e := url.Parse(uri)
|
|
if resourceUri.Scheme == c.Type() {
|
|
c.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
|
} else {
|
|
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, c.Type())
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (c *Container) Apply() error {
|
|
ctx := context.Background()
|
|
switch c.State {
|
|
case "absent":
|
|
return c.Delete(ctx)
|
|
case "present":
|
|
return c.Create(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) LoadDecl(yamlFileResourceDeclaration string) error {
|
|
return c.loader(yamlFileResourceDeclaration, c)
|
|
}
|
|
|
|
/*
|
|
apiClient, err := client.NewClientWithOpts(client.FromEnv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer apiClient.Close()
|
|
|
|
containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, ctr := range containers {
|
|
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
|
|
}
|
|
}
|
|
*/
|
|
|
|
func (c *Container) Create(ctx context.Context) error {
|
|
numberOfEnvironmentVariables := len(c.Environment)
|
|
config := &container.Config {
|
|
Image: c.Image,
|
|
Cmd: c.Cmd,
|
|
Entrypoint: c.Entrypoint,
|
|
Tty: false,
|
|
}
|
|
|
|
config.Env = make([]string, numberOfEnvironmentVariables)
|
|
index := 0
|
|
for k,v := range c.Environment {
|
|
config.Env[index] = k + "=" + v
|
|
index++
|
|
}
|
|
for i := range c.HostConfig.Mounts {
|
|
if c.HostConfig.Mounts[i].Type == mount.TypeBind {
|
|
if mountSourceAbsolutePath,e := filepath.Abs(c.HostConfig.Mounts[i].Source); e == nil {
|
|
c.HostConfig.Mounts[i].Source = mountSourceAbsolutePath
|
|
}
|
|
}
|
|
}
|
|
|
|
resp, err := c.apiClient.ContainerCreate(ctx, config, &c.HostConfig, nil, nil, c.Name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c.Id = resp.ID
|
|
|
|
/*
|
|
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
case <-statusCh:
|
|
}
|
|
*/
|
|
|
|
if startErr := c.apiClient.ContainerStart(ctx, c.Id, types.ContainerStartOptions{}); startErr != nil {
|
|
return startErr
|
|
}
|
|
return err
|
|
}
|
|
|
|
// produce yaml representation of any resource
|
|
|
|
func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
|
var containerID string
|
|
filterArgs := filters.NewArgs()
|
|
filterArgs.Add("name", "/" + c.Name)
|
|
containers,err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
|
All: true,
|
|
Filters: filterArgs,
|
|
})
|
|
|
|
if err != nil {
|
|
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
|
}
|
|
|
|
for _, container := range containers {
|
|
for _, containerName := range container.Names {
|
|
if containerName == "/" + c.Name {
|
|
containerID = container.ID
|
|
}
|
|
}
|
|
}
|
|
|
|
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
|
if client.IsErrNotFound(err) {
|
|
c.State = "absent"
|
|
} else {
|
|
c.State = "present"
|
|
c.Id = containerJSON.ID
|
|
if c.Name == "" {
|
|
c.Name = containerJSON.Name
|
|
}
|
|
c.Path = containerJSON.Path
|
|
c.Image = containerJSON.Image
|
|
if containerJSON.State != nil {
|
|
c.ContainerState = *containerJSON.State
|
|
}
|
|
c.Created = containerJSON.Created
|
|
c.ResolvConfPath = containerJSON.ResolvConfPath
|
|
c.HostnamePath = containerJSON.HostnamePath
|
|
c.HostsPath = containerJSON.HostsPath
|
|
c.LogPath = containerJSON.LogPath
|
|
c.RestartCount = containerJSON.RestartCount
|
|
c.Driver = containerJSON.Driver
|
|
}
|
|
slog.Info("Read() ", "type", c.Type(), "name", c.Name, "Id", c.Id)
|
|
return yaml.Marshal(c)
|
|
}
|
|
|
|
|
|
func (c *Container) Delete(ctx context.Context) error {
|
|
err := c.apiClient.ContainerRemove(ctx, c.Id, types.ContainerRemoveOptions{
|
|
RemoveVolumes: true,
|
|
Force: false,
|
|
})
|
|
if err != nil {
|
|
slog.Error("Failed to remove: ", "Id", c.Id)
|
|
panic(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *Container) Type() string { return "container" }
|
|
|
|
func (c *Container) ResolveId(ctx context.Context) string {
|
|
filterArgs := filters.NewArgs()
|
|
filterArgs.Add("name", "/" + c.Name)
|
|
containers,err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
|
All: true,
|
|
Filters: filterArgs,
|
|
})
|
|
if err != nil {
|
|
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
|
}
|
|
|
|
for _, container := range containers {
|
|
for _, containerName := range container.Names {
|
|
if containerName == c.Name {
|
|
if c.Id == "" {
|
|
c.Id = container.ID
|
|
}
|
|
return container.ID
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|