func (r *Resource) Template(templateContent string) (string, error) {
	tmpl, err := template.New("template").Funcs(sprig.TxtFuncMap()).Parse(templateContent)
	if err != nil {
		return "", fmt.Errorf("parsing template: %w", err)
	}
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, r); err != nil {
		return "", fmt.Errorf("executing template: %w", err)
	}
	return buf.String(), nil
}
Template executes given Go template using configuration and state.
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
}
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
}
func (r *Resource) StateToFile(actionErr error) error {
	rs := &Resource{
		State: r.State,
	}
	rb, err := yaml.Marshal(rs)
	if err != nil {
		return fmt.Errorf("serializing state: %w", err)
	}
	if string(rb) == "{}\n" {
		rb = []byte{}
	}
	readWriteOwnerOnly := 0o600
	if err := ioutil.WriteFile("state.yaml", rb, fs.FileMode(readWriteOwnerOnly)); err != nil {
		if actionErr == nil {
			return fmt.Errorf("writing new state to file: %w", err)
		}
		fmt.Println("Failed to write state.yaml file: %w", err)
	}
	if actionErr != nil {
		return fmt.Errorf("executing action: %w", actionErr)
	}
	fmt.Println("Action complete")
	return nil
}
func (r *Resource) validateKubeconfigPKI() error {
	if r.State.PKI == nil {
		return fmt.Errorf("PKI management not enabled")
	}
	if r.State.PKI.Kubernetes == nil {
		return fmt.Errorf("Kubernetes PKI management not enabled")
	}
	if r.State.PKI.Kubernetes.AdminCertificate == nil {
		return fmt.Errorf("Kubernetes admin certificate not available in PKI")
	}
	return nil
}
func (r *Resource) validateKubeconfigControlplane() error {
	if r.Controlplane == nil {
		return fmt.Errorf("Kubernetes controlplane management not enabled")
	}
	if r.Controlplane.APIServerAddress == "" {
		return fmt.Errorf("Kubernetes controlplane has no API server address set")
	}
	if r.Controlplane.APIServerPort == 0 {
		return fmt.Errorf("Kubernetes controlplane has no API server port set")
	}
	return nil
}
func (r *Resource) validateKubeconfig() error {
	if err := r.validateKubeconfigPKI(); err != nil {
		return fmt.Errorf("validating PKI fields required for generating kubeconfig: %w", err)
	}
	if err := r.validateKubeconfigControlplane(); err != nil {
		return fmt.Errorf("validating controlplane fields required for generating kubeconfig: %w", err)
	}
	return nil
}
func (r *Resource) Kubeconfig() (string, error) {
	if err := r.validateKubeconfig(); err != nil {
		return "", fmt.Errorf("validating kubeconfig: %w", err)
	}
	cc := &client.Config{
		Server:            fmt.Sprintf("%s:%d", r.Controlplane.APIServerAddress, r.Controlplane.APIServerPort),
		CACertificate:     r.State.PKI.Kubernetes.CA.X509Certificate,
		ClientCertificate: r.State.PKI.Kubernetes.AdminCertificate.X509Certificate,
		ClientKey:         r.State.PKI.Kubernetes.AdminCertificate.PrivateKey,
	}
	k, err := cc.ToYAMLString()
	if err != nil {
		return "", fmt.Errorf("generating client configuration: %w", err)
	}
	return k, nil
}
func (r *Resource) RunAPILoadBalancerPool(name string) error {
	p, err := r.getAPILoadBalancerPool(name)
	if err != nil {
		return fmt.Errorf("getting API Load Balancer pool %q from configuration: %w", name, err)
	}
	saveStateF := func(rs types.Resource) {
		if r.State.APILoadBalancerPools == nil {
			r.State.APILoadBalancerPools = map[string]*container.ContainersState{}
		}
		r.State.APILoadBalancerPools[name] = &p.Containers().ToExported().PreviousState
	}
	return r.execute(p, saveStateF)
}
func (r *Resource) RunControlplane() error {
	e, err := r.getControlplane()
	if err != nil {
		return fmt.Errorf("getting controlplane from the configuration: %w", err)
	}
	saveStateF := func(rs types.Resource) {
		r.State.Controlplane = &e.Containers().ToExported().PreviousState
	}
	return r.execute(e, saveStateF)
}
func (r *Resource) RunEtcd() error {
	e, err := r.getEtcd()
	if err != nil {
		return fmt.Errorf("getting etcd from the configuration: %w", err)
	}
	saveStateF := func(rs types.Resource) {
		r.State.Etcd = &e.Containers().ToExported().PreviousState
	}
	return r.execute(e, saveStateF)
}
func (r *Resource) RunKubeletPool(name string) error {
	p, err := r.getKubeletPool(name)
	if err != nil {
		return fmt.Errorf("getting kubelet pool %q from configuration: %w", name, err)
	}
	saveStateF := func(rs types.Resource) {
		if r.State.KubeletPools == nil {
			r.State.KubeletPools = map[string]*container.ContainersState{}
		}
		r.State.KubeletPools[name] = &p.Containers().ToExported().PreviousState
	}
	return r.execute(p, saveStateF)
}
func (r *Resource) RunPKI() error {
	pki, err := r.getPKI()
	if err != nil {
		return fmt.Errorf("loading PKI configuration: %w", err)
	}
	fmt.Println("Generating PKI...")
	genErr := pki.Generate()
	if r.State == nil {
		r.State = &ResourceState{}
	}
	r.State.PKI = pki
	return r.StateToFile(genErr)
}
func (r *Resource) RunContainers(name string) error {
	p, err := r.getContainers(name)
	if err != nil {
		return fmt.Errorf("getting containers group %q from configuration: %w", name, err)
	}
	saveStateF := func(rs types.Resource) {
		if r.State.Containers == nil {
			r.State.Containers = map[string]*container.ContainersState{}
		}
		r.State.Containers[name] = &p.Containers().ToExported().PreviousState
	}
	return r.execute(p, saveStateF)
}