2024-05-06 00:48:54 +00:00
|
|
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
|
|
|
|
// Container resource
|
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"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"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
_ "log/slog"
|
|
|
|
"net/url"
|
|
|
|
_ "os"
|
|
|
|
_ "os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
_ "strings"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ContainerNetworkClient interface {
|
|
|
|
ContainerClient
|
|
|
|
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type ContainerNetwork struct {
|
2024-05-09 07:39:45 +00:00
|
|
|
stater machine.Stater `json:"-" yaml:"-"`
|
2024-05-06 00:48:54 +00:00
|
|
|
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
|
|
|
Name string `json:"name" yaml:"name"`
|
|
|
|
|
|
|
|
State string `yaml:"state"`
|
|
|
|
|
|
|
|
apiClient ContainerNetworkClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
ResourceTypes.Register("container_network", func(u *url.URL) Resource {
|
|
|
|
n := NewContainerNetwork(nil)
|
|
|
|
n.Name = filepath.Join(u.Hostname(), u.Path)
|
|
|
|
return n
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNetwork {
|
|
|
|
var apiClient ContainerNetworkClient = containerClientApi
|
|
|
|
if apiClient == nil {
|
|
|
|
var err error
|
|
|
|
apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &ContainerNetwork{
|
|
|
|
apiClient: apiClient,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Clone() Resource {
|
|
|
|
return &ContainerNetwork {
|
|
|
|
Id: n.Id,
|
|
|
|
Name: n.Name,
|
|
|
|
State: n.State,
|
|
|
|
apiClient: n.apiClient,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) StateMachine() machine.Stater {
|
2024-05-09 07:39:45 +00:00
|
|
|
if n.stater == nil {
|
|
|
|
n.stater = StorageMachine(n)
|
|
|
|
}
|
|
|
|
return n.stater
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Notify(m *machine.EventMessage) {
|
|
|
|
ctx := context.Background()
|
|
|
|
switch m.On {
|
|
|
|
case machine.ENTERSTATEEVENT:
|
|
|
|
switch m.Dest {
|
|
|
|
case "start_create":
|
|
|
|
if e := n.Create(ctx); e != nil {
|
2024-05-13 05:41:12 +00:00
|
|
|
if triggerErr := n.stater.Trigger("created"); triggerErr != nil {
|
|
|
|
// transition error
|
|
|
|
}
|
2024-05-09 07:39:45 +00:00
|
|
|
}
|
|
|
|
case "present":
|
|
|
|
n.State = "present"
|
|
|
|
}
|
|
|
|
case machine.EXITSTATEEVENT:
|
|
|
|
}
|
2024-05-06 00:48:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) URI() string {
|
|
|
|
return fmt.Sprintf("container_network://%s", n.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) SetURI(uri string) error {
|
|
|
|
resourceUri, e := url.Parse(uri)
|
|
|
|
if e == nil {
|
|
|
|
if resourceUri.Scheme == n.Type() {
|
|
|
|
n.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
|
|
|
} else {
|
|
|
|
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, n.Type())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) JSON() ([]byte, error) {
|
|
|
|
return json.Marshal(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Validate() error {
|
|
|
|
return fmt.Errorf("failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Apply() error {
|
|
|
|
ctx := context.Background()
|
|
|
|
switch n.State {
|
|
|
|
case "absent":
|
|
|
|
return n.Delete(ctx)
|
|
|
|
case "present":
|
|
|
|
return n.Create(ctx)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Load(r io.Reader) error {
|
|
|
|
d := NewYAMLDecoder(r)
|
|
|
|
return d.Decode(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) LoadDecl(yamlResourceDeclaration string) error {
|
|
|
|
d := NewYAMLStringDecoder(yamlResourceDeclaration)
|
|
|
|
return d.Decode(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Create(ctx context.Context) error {
|
|
|
|
networkResp, err := n.apiClient.NetworkCreate(ctx, n.Name, types.NetworkCreate{
|
|
|
|
Driver: "bridge",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
n.Id = networkResp.ID
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// produce yaml representation of any resource
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Read(ctx context.Context) ([]byte, error) {
|
|
|
|
return yaml.Marshal(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Delete(ctx context.Context) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) Type() string { return "container_network" }
|
|
|
|
|
|
|
|
func (n *ContainerNetwork) ResolveId(ctx context.Context) string {
|
|
|
|
filterArgs := filters.NewArgs()
|
|
|
|
filterArgs.Add("name", "/"+n.Name)
|
|
|
|
containers, err := n.apiClient.ContainerList(ctx, container.ListOptions{
|
|
|
|
All: true,
|
|
|
|
Filters: filterArgs,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("%w: %s %s", err, n.Type(), n.Name))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, container := range containers {
|
|
|
|
for _, containerName := range container.Names {
|
|
|
|
if containerName == n.Name {
|
|
|
|
if n.Id == "" {
|
|
|
|
n.Id = container.ID
|
|
|
|
}
|
|
|
|
return container.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|