jx/internal/resource/container.go

258 lines
7.8 KiB
Go
Raw Normal View History

2024-03-20 19:23:31 +00:00
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
2024-03-22 17:39:06 +00:00
// Container resource
2024-03-20 19:23:31 +00:00
package resource
import (
2024-03-25 20:31:06 +00:00
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gopkg.in/yaml.v3"
_ "gopkg.in/yaml.v3"
"log/slog"
"net/url"
_ "os"
_ "os/exec"
"path/filepath"
_ "strings"
2024-03-20 19:23:31 +00:00
)
type ContainerClient interface {
2024-03-25 20:31:06 +00:00
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
2024-03-20 19:23:31 +00:00
}
type Container struct {
2024-03-25 20:31:06 +00:00
loader YamlLoader
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
Name string `json:"name" yaml:"name"`
Path string `json:"path" yaml:"path"`
Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"`
Entrypoint strslice.StrSlice `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
Environment map[string]string `json:"environment" yaml:"environment"`
Image string `json:"image" yaml:"image"`
ResolvConfPath string `json:"resolvconfpath" yaml:"resolvconfpath"`
HostnamePath string `json:"hostnamepath" yaml:"hostnamepath"`
HostsPath string `json:"hostpath" yaml:"hostspath"`
LogPath string `json:"logpath" yaml:"logpath"`
Created string `json:"created" yaml:"created"`
ContainerState types.ContainerState `json:"containerstate" yaml:"containerstate"`
RestartCount int `json:"restartcount" yaml:"restartcount"`
Driver string `json:"driver" yaml:"driver"`
Platform string `json:"platform" yaml:"platform"`
MountLabel string `json:"mountlabel" yaml:"mountlabel"`
ProcessLabel string `json:"processlabel" yaml:"processlabel"`
AppArmorProfile string `json:"apparmorprofile" yaml:"apparmorprofile"`
ExecIDs []string `json:"execids" yaml:"execids"`
HostConfig container.HostConfig `json:"hostconfig" yaml:"hostconfig"`
GraphDriver types.GraphDriverData `json:"graphdriver" yaml:"graphdriver"`
SizeRw *int64 `json:",omitempty" yaml:",omitempty"`
SizeRootFs *int64 `json:",omitempty" yaml:",omitempty"`
2024-03-25 20:31:06 +00:00
/*
Mounts []MountPoint
Config *container.Config
NetworkSettings *NetworkSettings
*/
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
State string `yaml:"state"`
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
apiClient ContainerClient
2024-03-20 19:23:31 +00:00
}
func init() {
2024-03-25 20:31:06 +00:00
ResourceTypes.Register("container", func(u *url.URL) Resource {
c := NewContainer(nil)
c.Name = filepath.Join(u.Hostname(), u.Path)
return c
})
2024-03-20 19:23:31 +00:00
}
func NewContainer(containerClientApi ContainerClient) *Container {
2024-03-25 20:31:06 +00:00
var apiClient ContainerClient = containerClientApi
if apiClient == nil {
var err error
2024-03-27 21:14:13 +00:00
apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2024-03-25 20:31:06 +00:00
if err != nil {
panic(err)
}
}
return &Container{
loader: YamlLoadDecl,
apiClient: apiClient,
}
2024-03-20 19:23:31 +00:00
}
func (c *Container) URI() string {
2024-03-25 20:31:06 +00:00
return fmt.Sprintf("container://%s", c.Id)
2024-03-20 19:23:31 +00:00
}
func (c *Container) SetURI(uri string) error {
2024-03-25 20:31:06 +00:00
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
2024-03-20 19:23:31 +00:00
}
func (c *Container) Apply() error {
2024-03-25 20:31:06 +00:00
ctx := context.Background()
switch c.State {
case "absent":
return c.Delete(ctx)
case "present":
return c.Create(ctx)
}
return nil
2024-03-20 19:23:31 +00:00
}
func (c *Container) LoadDecl(yamlFileResourceDeclaration string) error {
2024-03-25 20:31:06 +00:00
return c.loader(yamlFileResourceDeclaration, c)
2024-03-20 19:23:31 +00:00
}
func (c *Container) Create(ctx context.Context) error {
2024-03-25 20:31:06 +00:00
numberOfEnvironmentVariables := len(c.Environment)
config := &container.Config{
Image: c.Image,
Cmd: c.Cmd,
Entrypoint: c.Entrypoint,
Tty: false,
}
2024-03-21 22:28:29 +00:00
2024-03-25 20:31:06 +00:00
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
}
}
}
2024-03-21 22:28:29 +00:00
2024-03-25 20:31:06 +00:00
resp, err := c.apiClient.ContainerCreate(ctx, config, &c.HostConfig, nil, nil, c.Name)
if err != nil {
panic(err)
}
c.Id = resp.ID
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
/*
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
panic(err)
}
case <-statusCh:
}
*/
2024-03-21 22:28:29 +00:00
2024-03-25 20:31:06 +00:00
if startErr := c.apiClient.ContainerStart(ctx, c.Id, types.ContainerStartOptions{}); startErr != nil {
return startErr
}
return err
2024-03-20 19:23:31 +00:00
}
// produce yaml representation of any resource
func (c *Container) Read(ctx context.Context) ([]byte, error) {
2024-03-25 20:31:06 +00:00
var containerID string
filterArgs := filters.NewArgs()
filterArgs.Add("name", "/"+c.Name)
containers, err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
All: true,
Filters: filterArgs,
})
2024-03-23 06:21:11 +00:00
2024-03-25 20:31:06 +00:00
if err != nil {
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
}
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
for _, container := range containers {
for _, containerName := range container.Names {
if containerName == "/"+c.Name {
containerID = container.ID
}
}
}
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
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)
2024-03-20 19:23:31 +00:00
}
func (c *Container) Delete(ctx context.Context) error {
2024-03-25 20:31:06 +00:00
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
2024-03-20 19:23:31 +00:00
}
func (c *Container) Type() string { return "container" }
func (c *Container) ResolveId(ctx context.Context) string {
2024-03-25 20:31:06 +00:00
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))
}
2024-03-20 19:23:31 +00:00
2024-03-25 20:31:06 +00:00
for _, container := range containers {
for _, containerName := range container.Names {
if containerName == c.Name {
if c.Id == "" {
c.Id = container.ID
}
return container.ID
}
}
}
return ""
2024-03-20 19:23:31 +00:00
}