297 lines
7.4 KiB
Go
297 lines
7.4 KiB
Go
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
package resource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"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"
|
|
_ "strings"
|
|
"encoding/json"
|
|
"io"
|
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
|
"decl/internal/codec"
|
|
"decl/internal/data"
|
|
"decl/internal/folio"
|
|
"log/slog"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
ContainerNetworkTypeName TypeName = "container-network"
|
|
)
|
|
|
|
type ContainerNetworkClient interface {
|
|
ContainerClient
|
|
NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error)
|
|
NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
|
NetworkInspect(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error)
|
|
}
|
|
|
|
type ContainerNetwork struct {
|
|
*Common `json:",inline" yaml:",inline"`
|
|
stater machine.Stater `json:"-" yaml:"-"`
|
|
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
|
Name string `json:"name" yaml:"name"`
|
|
Driver string `json:"driver,omitempty" yaml:"driver,omitempty"`
|
|
EnableIPv6 bool `json:"enableipv6,omitempty" yaml:"enableipv6,omitempty"`
|
|
Internal bool `json:"internal,omitempty" yaml:"internal,omitempty"`
|
|
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
|
Created time.Time `json:"created" yaml:"created"`
|
|
|
|
apiClient ContainerNetworkClient
|
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
}
|
|
|
|
func init() {
|
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) (n data.Resource) {
|
|
n = NewContainerNetwork(nil)
|
|
if u != nil {
|
|
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *ContainerNetwork) {
|
|
var apiClient ContainerNetworkClient = containerClientApi
|
|
if apiClient == nil {
|
|
var err error
|
|
apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
cn = &ContainerNetwork{
|
|
apiClient: apiClient,
|
|
}
|
|
cn.Common = NewCommon(ContainerNetworkTypeName, true)
|
|
cn.Common.NormalizePath = cn.NormalizePath
|
|
return cn
|
|
}
|
|
|
|
func (n *ContainerNetwork) Init(u data.URIParser) error {
|
|
if u == nil {
|
|
u = folio.URI(n.URI()).Parse()
|
|
}
|
|
return n.SetParsedURI(u)
|
|
}
|
|
|
|
func (n *ContainerNetwork) NormalizePath() error {
|
|
return nil
|
|
}
|
|
|
|
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
|
n.Resources = resources
|
|
}
|
|
|
|
func (n *ContainerNetwork) Clone() data.Resource {
|
|
return &ContainerNetwork {
|
|
Common: n.Common.Clone(),
|
|
Id: n.Id,
|
|
Name: n.Name,
|
|
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()
|
|
slog.Info("Notify()", "ContainerNetwork", n, "m", m)
|
|
switch m.On {
|
|
case machine.ENTERSTATEEVENT:
|
|
switch m.Dest {
|
|
case "start_read":
|
|
if _,readErr := n.Read(ctx); readErr == nil {
|
|
if triggerErr := n.StateMachine().Trigger("state_read"); triggerErr == nil {
|
|
return
|
|
} else {
|
|
n.Common.State = "absent"
|
|
panic(triggerErr)
|
|
}
|
|
} else {
|
|
n.Common.State = "absent"
|
|
panic(readErr)
|
|
}
|
|
case "start_delete":
|
|
if deleteErr := n.Delete(ctx); deleteErr == nil {
|
|
if triggerErr := n.StateMachine().Trigger("deleted"); triggerErr == nil {
|
|
return
|
|
} else {
|
|
n.Common.State = "present"
|
|
panic(triggerErr)
|
|
}
|
|
} else {
|
|
n.Common.State = "present"
|
|
panic(deleteErr)
|
|
}
|
|
case "start_create":
|
|
if e := n.Create(ctx); e == nil {
|
|
if triggerErr := n.StateMachine().Trigger("created"); triggerErr == nil {
|
|
return
|
|
}
|
|
}
|
|
n.Common.State = "absent"
|
|
case "absent":
|
|
n.Common.State = "absent"
|
|
case "present", "created", "read":
|
|
n.Common.State = "present"
|
|
}
|
|
case machine.EXITSTATEEVENT:
|
|
}
|
|
}
|
|
|
|
func (n *ContainerNetwork) URI() string {
|
|
return fmt.Sprintf("container-network://%s", n.Name)
|
|
}
|
|
|
|
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.Common.State {
|
|
case "absent":
|
|
return n.Delete(ctx)
|
|
case "present":
|
|
return n.Create(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *ContainerNetwork) Load(docData []byte, f codec.Format) (err error) {
|
|
err = f.StringDecoder(string(docData)).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *ContainerNetwork) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|
err = f.Decoder(r).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *ContainerNetwork) LoadString(docData string, f codec.Format) (err error) {
|
|
err = f.StringDecoder(docData).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *ContainerNetwork) LoadDecl(yamlResourceDeclaration string) error {
|
|
return n.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
|
}
|
|
|
|
func (n *ContainerNetwork) Create(ctx context.Context) error {
|
|
networkResp, err := n.apiClient.NetworkCreate(ctx, n.Name, network.CreateOptions{
|
|
Driver: "bridge",
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
n.Id = networkResp.ID
|
|
|
|
return nil
|
|
}
|
|
|
|
func (n *ContainerNetwork) Inspect(ctx context.Context, networkID string) error {
|
|
networkInspect, err := n.apiClient.NetworkInspect(ctx, networkID, network.InspectOptions{})
|
|
if client.IsErrNotFound(err) {
|
|
n.Common.State = "absent"
|
|
} else {
|
|
n.Common.State = "present"
|
|
n.Id = networkInspect.ID
|
|
if n.Name == "" {
|
|
if networkInspect.Name[0] == '/' {
|
|
n.Name = networkInspect.Name[1:]
|
|
} else {
|
|
n.Name = networkInspect.Name
|
|
}
|
|
}
|
|
n.Created = networkInspect.Created
|
|
n.Internal = networkInspect.Internal
|
|
n.Driver = networkInspect.Driver
|
|
n.Labels = networkInspect.Labels
|
|
n.EnableIPv6 = networkInspect.EnableIPv6
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *ContainerNetwork) Read(ctx context.Context) ([]byte, error) {
|
|
var networkID string
|
|
filterArgs := filters.NewArgs()
|
|
filterArgs.Add("name", n.Name)
|
|
networks, err := n.apiClient.NetworkList(ctx, network.ListOptions{
|
|
Filters: filterArgs,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %s %s", err, n.Type(), n.Name)
|
|
}
|
|
|
|
for _, net := range networks {
|
|
if net.Name == n.Name {
|
|
networkID = net.ID
|
|
}
|
|
}
|
|
|
|
if inspectErr := n.Inspect(ctx, networkID); inspectErr != nil {
|
|
return nil, fmt.Errorf("%w: network %s", inspectErr, networkID)
|
|
}
|
|
slog.Info("Read() ", "type", n.Type(), "name", n.Name, "Id", n.Id)
|
|
|
|
return yaml.Marshal(n)
|
|
}
|
|
|
|
func (n *ContainerNetwork) Update(ctx context.Context) error {
|
|
return n.Create(ctx)
|
|
}
|
|
|
|
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 ""
|
|
}
|