// Copyright 2024 Matthew Rich . 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 { stater machine.Stater `json:"-" yaml:"-"` 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 { 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 { n.stater.Trigger("created") } case "present": n.State = "present" } case machine.EXITSTATEEVENT: } } 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 "" }