func LoadResourceFromFiles() (*Resource, error) {
	r := &Resource{}
	c, err := readYamlFile("config.yaml")
	if err != nil {
		return nil, fmt.Errorf("reading config.yaml file: %w", err)
	}
	s, err := readYamlFile("state.yaml")
	if err != nil {
		return nil, fmt.Errorf("reading state.yaml file: %w", err)
	}
	if err := yaml.Unmarshal([]byte(string(c)+string(s)), r); err != nil {
		return nil, fmt.Errorf("parsing files: %w", err)
	}
	return r, nil
}
LoadResourceFromFiles loads Resource struct from config.yaml and state.yaml files.
package flexkube
import (
	"bufio"
	"bytes"
	"fmt"
	"io/fs"
	"io/ioutil"
	"os"
	"strings"
	"text/template"
	sprig "github.com/Masterminds/sprig/v3"
	"github.com/google/go-cmp/cmp"
	"sigs.k8s.io/yaml"
	"github.com/flexkube/libflexkube/internal/util"
	"github.com/flexkube/libflexkube/pkg/apiloadbalancer"
	"github.com/flexkube/libflexkube/pkg/container"
	"github.com/flexkube/libflexkube/pkg/container/resource"
	"github.com/flexkube/libflexkube/pkg/controlplane"
	"github.com/flexkube/libflexkube/pkg/etcd"
	"github.com/flexkube/libflexkube/pkg/kubelet"
	"github.com/flexkube/libflexkube/pkg/kubernetes/client"
	"github.com/flexkube/libflexkube/pkg/pki"
	"github.com/flexkube/libflexkube/pkg/types"
)
type Resource struct {
	Etcd *etcd.Cluster `json:"etcd,omitempty"`
	Controlplane *controlplane.Controlplane `json:"controlplane,omitempty"`
	PKI *pki.PKI `json:"pki,omitempty"`
	KubeletPools map[string]*kubelet.Pool `json:"kubeletPools,omitempty"`
	APILoadBalancerPools map[string]*apiloadbalancer.APILoadBalancers `json:"apiLoadBalancerPools,omitempty"`
	Containers map[string]*container.ContainersState `json:"containers,omitempty"`
	State *ResourceState `json:"state,omitempty"`
	Confirmed bool `json:"confirmed,omitempty"`
	Noop bool `json:"noop,omitempty"`
}
type ResourceState struct {
	Etcd *container.ContainersState `json:"etcd,omitempty"`
	Controlplane *container.ContainersState `json:"controlplane,omitempty"`
	KubeletPools map[string]*container.ContainersState `json:"kubeletPools,omitempty"`
	APILoadBalancerPools map[string]*container.ContainersState `json:"apiLoadBalancerPools,omitempty"`
	Containers map[string]*container.ContainersState `json:"containers,omitempty"`
	PKI *pki.PKI `json:"pki,omitempty"`
}
func (r *Resource) getEtcd() (types.Resource, error) {
	if r.Etcd == nil {
		if r.State == nil || r.State.Etcd == nil {
			return nil, fmt.Errorf("etcd management not enabled in the configuration and state not found")
		}
		r.Etcd = &etcd.Cluster{}
	}
	if r.State != nil && r.State.Etcd != nil {
		r.Etcd.State = *r.State.Etcd
	}
	if r.State != nil && r.State.PKI != nil {
		r.Etcd.PKI = r.State.PKI
	}
	return validateAndNew(r.Etcd)
}
func (r *Resource) getControlplane() (types.Resource, error) {
	if r.Controlplane == nil {
		if r.State == nil || r.State.Controlplane == nil {
			return nil, fmt.Errorf("controlplane not configured and state not found")
		}
		r.Controlplane = &controlplane.Controlplane{
			Destroy: true,
		}
	}
	if r.State != nil {
		r.Controlplane.State = r.State.Controlplane
	}
	if r.State != nil && r.State.PKI != nil {
		r.Controlplane.PKI = r.State.PKI
	}
	return validateAndNew(r.Controlplane)
}
func (r *Resource) getKubeletPool(name string) (types.Resource, error) {
	stateFound := r.State != nil && r.State.KubeletPools != nil && r.State.KubeletPools[name] != nil
	configPool, configFound := r.KubeletPools[name]
	if !stateFound && !configFound {
		return nil, fmt.Errorf("pool not configured and state not found")
	}
	pool := &kubelet.Pool{}
	if configFound {
		pool = configPool
	}
	if stateFound {
		pool.State = *r.State.KubeletPools[name]
	}
	if r.State != nil && r.State.PKI != nil {
		pool.PKI = r.State.PKI
	}
	return validateAndNew(pool)
}
func (r *Resource) getPKI() (*pki.PKI, error) {
	if r.PKI == nil {
		return nil, fmt.Errorf("PKI config configured")
	}
	pki := &pki.PKI{}
	if r.State != nil && r.State.PKI != nil {
		fmt.Println("Loading existing PKI state from state.yaml file")
		pki = r.State.PKI
	}
	pkic, err := yaml.Marshal(r.PKI)
	if err != nil {
		return nil, fmt.Errorf("serializing PKI configuration: %w", err)
	}
	if err := yaml.Unmarshal(pkic, pki); err != nil {
		return nil, fmt.Errorf("merging PKI configuration with state: %w", err)
	}
	return pki, nil
}
func (r *Resource) getAPILoadBalancerPool(name string) (types.Resource, error) {
	stateFound := r.State != nil && r.State.APILoadBalancerPools != nil && r.State.APILoadBalancerPools[name] != nil
	configPool, configFound := r.APILoadBalancerPools[name]
	if !stateFound && !configFound {
		return nil, fmt.Errorf("pool not configured and state not found")
	}
	pool := &apiloadbalancer.APILoadBalancers{}
	if configFound {
		pool = configPool
	}
	if stateFound {
		pool.State = *r.State.APILoadBalancerPools[name]
	}
	return validateAndNew(pool)
}
func (r *Resource) getContainers(name string) (types.Resource, error) {
	stateFound := r.State != nil && r.State.Containers != nil && r.State.Containers[name] != nil
	config, configFound := r.Containers[name]
	if !stateFound && !configFound {
		return nil, fmt.Errorf("group not configured and state not found")
	}
	containers := &resource.Containers{}
	if configFound {
		containers.Containers = *config
	}
	if stateFound {
		containers.State = *r.State.Containers[name]
	}
	return validateAndNew(containers)
}
func validateAndNew(rc types.ResourceConfig) (types.Resource, error) {
	if err := rc.Validate(); err != nil {
		return nil, fmt.Errorf("validating configuration: %w", err)
	}
	r, err := rc.New()
	if err != nil {
		return nil, fmt.Errorf("initializing object: %w", err)
	}
	return r, nil
}
func (r *Resource) checkState(rs types.Resource) (string, error) {
	fmt.Println("Checking current state")
	if err := rs.CheckCurrentState(); err != nil {
		return "", fmt.Errorf("checking current state: %w", err)
	}
	fmt.Printf("Calculating diff...\n\n")
	d := cmp.Diff(rs.Containers().ToExported().PreviousState, rs.Containers().DesiredState())
	if d == "" {
		fmt.Println("No changes required")
		return d, nil
	}
	fmt.Printf("Following changes required:\n\n%s\n\n", util.ColorizeDiff(d))
	return d, nil
}
func (r *Resource) execute(rs types.Resource, saveStateF func(types.Resource)) error {
	diff, err := r.checkState(rs)
	if err != nil {
		return fmt.Errorf("checking current state: %w", err)
	}
	if r.Noop || diff == "" {
		return nil
	}
	return r.deploy(rs, saveStateF)
}
func (r *Resource) deploy(rs types.Resource, saveStateF func(types.Resource)) error {
	if !r.Confirmed {
		confirmed, err := askForConfirmation()
		if err != nil {
			return fmt.Errorf("asking for confirmation: %w", err)
		}
		if !confirmed {
			fmt.Println("Aborted")
			return nil
		}
	}
	deployErr := rs.Deploy()
	if r.State == nil {
		r.State = &ResourceState{}
	}
	saveStateF(rs)
	return r.StateToFile(deployErr)
}
func askForConfirmation() (bool, error) {
	r := bufio.NewReader(os.Stdin)
	fmt.Printf("To continue, type (y)es nad press enter: ")
	response, err := r.ReadString('\n')
	if err != nil {
		return false, fmt.Errorf("reading user response: %w", err)
	}
	switch strings.ToLower(strings.TrimSpace(response)) {
	case "y", "yes":
		return true, nil
	case "n", "no":
		return false, nil
	default:
		return askForConfirmation()
	}
}
func readYamlFile(file string) ([]byte, error) {
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return []byte(""), nil
	}
	c, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, fmt.Errorf("reading file: %w", err)
	}
	if string(c) == "{}\n" {
		return []byte{}, nil
	}
	return c, nil
}