func (c *containers) Containers() ContainersInterface {
	return c
}
Containers implement types.Resource interface.
package container
import (
	"fmt"
	"reflect"
	"github.com/google/go-cmp/cmp"
	"sigs.k8s.io/yaml"
	"github.com/flexkube/libflexkube/internal/util"
	"github.com/flexkube/libflexkube/pkg/container/types"
)
type ContainersInterface interface {
	CheckCurrentState() error
	Deploy() error
	StateToYaml() ([]byte, error)
	ToExported() *Containers
	DesiredState() ContainersState
}
type Containers struct {
	PreviousState ContainersState `json:"previousState,omitempty"`
	DesiredState ContainersState `json:"desiredState,omitempty"`
}
type containers struct {
	previousState containersState
	currentState containersState
	desiredState containersState
}
func (c *Containers) New() (ContainersInterface, error) {
	if err := c.Validate(); err != nil {
		return nil, fmt.Errorf("validating containers configuration: %w", err)
	}
	previousState, _ := c.PreviousState.New()  
	desiredState, _ := c.DesiredState.New()    
	return &containers{
		previousState: previousState.(containersState),
		desiredState:  desiredState.(containersState),
	}, nil
}
func (c *Containers) Validate() error {
	var errors util.ValidateErrors
	if c == nil {
		errors = append(errors, fmt.Errorf("containers must be defined"))
		return errors.Return()
	}
	if (c.PreviousState == nil && c.DesiredState == nil) || (len(c.PreviousState) == 0 && len(c.DesiredState) == 0) {
		errors = append(errors, fmt.Errorf("either current state or desired state must be defined"))
	}
	if _, err := c.PreviousState.New(); err != nil {
		errors = append(errors, fmt.Errorf("validating previous state failed: %w", err))
	}
	if _, err := c.DesiredState.New(); err != nil {
		errors = append(errors, fmt.Errorf("validating desired state failed: %w", err))
	}
	return errors.Return()
}
func (c *Containers) CheckCurrentState() error {
	containers, err := c.New()
	if err != nil {
		return fmt.Errorf("creating containers configuration: %w", err)
	}
	if err := containers.CheckCurrentState(); err != nil {
		return fmt.Errorf("checking current state of the containers: %w", err)
	}
	*c = *containers.ToExported()
	return nil
}
func (c *Containers) Deploy() error {
	containers, err := c.New()
	if err != nil {
		return fmt.Errorf("initializing containers: %w", err)
	}
	if err := containers.CheckCurrentState(); err != nil {
		return fmt.Errorf("checking current state: %w", err)
	}
	if err := containers.Deploy(); err != nil {
		return fmt.Errorf("deploying: %w", err)
	}
	*c = *containers.ToExported()
	return nil
}
func (c *containers) CheckCurrentState() error {
	if c.currentState == nil {
		c.currentState = c.previousState
	}
	return c.currentState.CheckState()
}
func filesToUpdate(d hostConfiguredContainer, c *hostConfiguredContainer) []string {
	if c == nil {
		return util.KeysStringMap(d.configFiles)
	}
	files := []string{}
	for p, content := range d.configFiles {
		if currentContent, exists := c.configFiles[p]; !exists || content != currentContent {
			fmt.Printf("Detected configuration drift for file %q\n", p)
			fmt.Printf("  current: \n%+v\n", currentContent)
			fmt.Printf("  desired: \n%+v\n", content)
			files = append(files, p)
		}
	}
	return files
}
func (c *containers) ensureConfigured(n string) error {
	d := c.desiredState[n]
	if d == nil {
		return nil
	}
	r := c.currentState[n]
	f := filesToUpdate(*d, r)
	err := d.Configure(f)
	if err != nil && reflect.DeepEqual(f, filesToUpdate(*d, r)) {
		return fmt.Errorf("no files has been updated: %w", err)
	}
	if r == nil {
		c.currentState[n] = d
		r = d
	}
	r.configFiles = d.configFiles
	if err != nil {
		return fmt.Errorf("updating configuration: %w", err)
	}
	return nil
}
func ensureRunning(c *hostConfiguredContainer) error {
	if c == nil {
		return fmt.Errorf("can't start non-existing container")
	}
	if c.container.Status().Running() {
		return nil
	}
	return c.Start()
}
func (c *containers) ensureExists(n string) error {
	r := c.currentState[n]
	if r != nil && r.container.Status().Exists() {
		return nil
	}
	fmt.Printf("Creating new container %q\n", n)
	d := c.desiredState[n]
	err := c.desiredState.CreateAndStart(n)
	if err != nil && !d.container.Status().Exists() {  
		return fmt.Errorf("creating container: %w", err)
	}
	if r == nil {
		c.currentState[n] = d
		r = d
	}
	*r.container.Status() = *d.container.Status()
	if err != nil {
		return fmt.Errorf("starting container: %w", err)
	}
	return nil
}
func (c *containers) isUpdatable(n string) error {
	if _, ok := c.currentState[n]; !ok {
		return fmt.Errorf("can't update non-existing container %q", n)
	}
	if _, ok := c.desiredState[n]; !ok {
		return fmt.Errorf("can't update container %q, which is scheduler for removal", n)
	}
	return nil
}
func (c *containers) diffHost(n string) (string, error) {
	if err := c.isUpdatable(n); err != nil {
		return "", fmt.Errorf("can't diff container: %w", err)
	}
	return cmp.Diff(c.currentState[n].host, c.desiredState[n].host), nil
}
func (c *containers) recreate(n string) error {
	if err := c.currentState.RemoveContainer(n); err != nil {
		return fmt.Errorf("removing old container to recreate it: %w", err)
	}
	c.currentState[n] = nil
	err := c.desiredState.CreateAndStart(n)
	c.currentState[n] = c.desiredState[n]
	if err != nil {
		return fmt.Errorf("creating and starting new container %q: %w", n, err)
	}
	return nil
}
func (c *containers) ensureHost(n string) error {
	diff, err := c.diffHost(n)
	if err != nil {
		return fmt.Errorf("checking host diff: %w", err)
	}
	if diff == "" {
		return nil
	}
	fmt.Printf("Detected host configuration drift %q\n", n)
	fmt.Printf("  Diff: %v\n", util.ColorizeDiff(diff))
	return c.recreate(n)
}
func (c *containers) diffContainer(n string) (string, error) {
	if err := c.isUpdatable(n); err != nil {
		return "", fmt.Errorf("can't diff container: %w", err)
	}
	cd := cmp.Diff(c.currentState[n].container.Config(), c.desiredState[n].container.Config())
	rcd := cmp.Diff(c.currentState[n].container.RuntimeConfig(), c.desiredState[n].container.RuntimeConfig())
	return cd + rcd, nil
}
func (c *containers) ensureContainer(n string) error {
	diff, err := c.diffContainer(n)
	if err != nil {
		return fmt.Errorf("checking container diff: %w", err)
	}
	if diff == "" {
		return nil
	}
	fmt.Printf("Detected container configuration drift %q\n", n)
	fmt.Printf("  Diff: %v\n", util.ColorizeDiff(diff))
	return c.recreate(n)
}
func (c *containers) hasUpdates(n string) (bool, error) {
	diffHost, err := c.diffHost(n)
	if err != nil {
		return false, fmt.Errorf("checking host diff: %w", err)
	}
	f := filesToUpdate(*c.desiredState[n], c.currentState[n])
	diffContainer, err := c.diffContainer(n)
	if err != nil {
		return false, fmt.Errorf("checking container diff: %w", err)
	}
	return diffHost != "" || len(f) != 0 || diffContainer != "", nil
}
func (c *containers) ensureCurrentContainer(n string, r hostConfiguredContainer) (*hostConfiguredContainer, error) {
	exists := r.container.Status().Exists()
	_, isDesired := c.desiredState[n]
	hasUpdates := false
	if err := c.isUpdatable(n); err == nil {
		u, err := c.hasUpdates(n)
		if err != nil {
			return &r, fmt.Errorf("checking if container has pending updates: %w", err)
		}
		hasUpdates = u
	}
	if !exists {
		delete(c.currentState, n)
		return nil, nil
	}
	if exists && isDesired && !hasUpdates {
		return &r, ensureRunning(&r)
	}
	return &r, nil
}
func (c *containers) ensureNewContainer(i string) error {
	if _, existingContainer := c.currentState[i]; existingContainer {
		return nil
	}
	if err := c.ensureConfigured(i); err != nil {
		return fmt.Errorf("configuring container %q: %w", i, err)
	}
	if err := c.ensureExists(i); err != nil {
		return fmt.Errorf("creating new container %q: %w", i, err)
	}
	return nil
}
func (c *containers) ensureUpToDate(i string) error {
	if err := c.ensureHost(i); err != nil {
		return fmt.Errorf("updating host configuration of container %q: %w", i, err)
	}
	if err := c.ensureConfigured(i); err != nil {
		return fmt.Errorf("updating configuration for container %q: %w", i, err)
	}
	if err := c.ensureContainer(i); err != nil {
		return fmt.Errorf("updating container %q: %w", i, err)
	}
	return nil
}
func (c *containers) updateExistingContainers() error {
	for i := range c.currentState {
		if _, exists := c.desiredState[i]; !exists {
			if err := c.currentState.RemoveContainer(i); err != nil {
				return fmt.Errorf("removing old container: %w", err)
			}
			continue
		}
		if err := c.ensureUpToDate(i); err != nil {
			return fmt.Errorf("ensuring, that container %q is up to date: %w", i, err)
		}
	}
	return nil
}
func (c *containers) Deploy() error {
	if c.currentState == nil {
		return fmt.Errorf("can't execute without knowing current state of the containers")
	}
	fmt.Println("Checking for stopped and missing containers")
	for n, r := range c.currentState {
		d, err := c.ensureCurrentContainer(n, *r)
		if d != nil {
			c.currentState[n] = d
		}
		if err != nil {
			return fmt.Errorf("handling existing container %q: %w", n, err)
		}
	}
	fmt.Println("Configuring and creating new containers")
	for i := range c.desiredState {
		if err := c.ensureNewContainer(i); err != nil {
			return fmt.Errorf("creating new container %q: %w", i, err)
		}
	}
	fmt.Println("Updating existing containers")
	return c.updateExistingContainers()
}
func FromYaml(c []byte) (ContainersInterface, error) {
	containers := &Containers{}
	if err := yaml.Unmarshal(c, &containers); err != nil {
		return nil, fmt.Errorf("parsing input YAML: %w", err)
	}
	cl, err := containers.New()
	if err != nil {
		return nil, fmt.Errorf("creating containers object: %w", err)
	}
	return cl, nil
}
func (c *containers) StateToYaml() ([]byte, error) {
	containers := &Containers{
		PreviousState: c.previousState.Export(),
	}
	return yaml.Marshal(containers)
}
func (c *containers) ToExported() *Containers {
	return &Containers{
		PreviousState: c.previousState.Export(),
		DesiredState:  c.desiredState.Export(),
	}
}
func (c *containers) DesiredState() ContainersState {
	d := c.desiredState.Export()
	for h := range d {
		id := ""
		cs, ok := c.previousState[h]
		if ok && cs.container.Status().ID != "" {
			id = cs.container.Status().ID
		}
		d[h].Container.Status = &types.ContainerStatus{
			Status: "running",
			ID:     id,
		}
	}
	return d
}