github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/boom/gitcrd/gitcrd.go (about)

     1  package gitcrd
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/caos/orbos/internal/operator/boom/api"
    12  	toolsetslatest "github.com/caos/orbos/internal/operator/boom/api/latest"
    13  	bundleconfig "github.com/caos/orbos/internal/operator/boom/bundle/config"
    14  	"github.com/caos/orbos/internal/operator/boom/crd"
    15  	crdconfig "github.com/caos/orbos/internal/operator/boom/crd/config"
    16  	"github.com/caos/orbos/internal/operator/boom/current"
    17  	"github.com/caos/orbos/internal/operator/boom/gitcrd/config"
    18  	"github.com/caos/orbos/internal/operator/boom/metrics"
    19  	"github.com/caos/orbos/internal/utils/clientgo"
    20  	"github.com/caos/orbos/internal/utils/helper"
    21  	"github.com/caos/orbos/internal/utils/kubectl"
    22  	"github.com/caos/orbos/internal/utils/kustomize"
    23  	"github.com/caos/orbos/mntr"
    24  	"github.com/caos/orbos/pkg/git"
    25  	"gopkg.in/yaml.v3"
    26  )
    27  
    28  type GitCrd struct {
    29  	crd              *crd.Crd
    30  	git              *git.Client
    31  	crdDirectoryPath string
    32  	status           error
    33  	monitor          mntr.Monitor
    34  }
    35  
    36  func New(conf *config.Config) *GitCrd {
    37  
    38  	monitor := conf.Monitor.WithFields(map[string]interface{}{
    39  		"type": "gitcrd",
    40  	})
    41  
    42  	monitor.Info("New GitCRD")
    43  
    44  	gitCrd := &GitCrd{
    45  		crdDirectoryPath: conf.CrdDirectoryPath,
    46  		git:              conf.Git,
    47  		monitor:          monitor,
    48  	}
    49  
    50  	crdConf := &crdconfig.Config{
    51  		Monitor: monitor,
    52  	}
    53  
    54  	gitCrd.crd = crd.New(crdConf)
    55  
    56  	return gitCrd
    57  }
    58  
    59  func (c *GitCrd) Clone(url string, key []byte) error {
    60  	err := c.git.Configure(url, key)
    61  	if err != nil {
    62  		c.monitor.Error(err)
    63  		return err
    64  	}
    65  
    66  	err = c.git.Clone()
    67  	if err != nil {
    68  		c.monitor.Error(err)
    69  		metrics.FailedGitClone(url)
    70  		return err
    71  	}
    72  	metrics.SuccessfulGitClone(url)
    73  	return nil
    74  }
    75  
    76  func (c *GitCrd) GetStatus() error {
    77  	return c.status
    78  }
    79  
    80  func (c *GitCrd) SetBackStatus() {
    81  	c.crd.SetBackStatus()
    82  	c.status = nil
    83  }
    84  
    85  func (c *GitCrd) SetBundle(conf *bundleconfig.Config) {
    86  	if c.status != nil {
    87  		return
    88  	}
    89  
    90  	toolsetCRD, err := c.getCrdMetadata()
    91  	if err != nil {
    92  		c.status = err
    93  		return
    94  	}
    95  
    96  	monitor := c.monitor.WithFields(map[string]interface{}{
    97  		"CRD": toolsetCRD.Metadata.Name,
    98  	})
    99  
   100  	bundleConf := &bundleconfig.Config{
   101  		Monitor:           monitor,
   102  		CrdName:           toolsetCRD.Metadata.Name,
   103  		BundleName:        conf.BundleName,
   104  		BaseDirectoryPath: conf.BaseDirectoryPath,
   105  		Templator:         conf.Templator,
   106  		Orb:               conf.Orb,
   107  	}
   108  
   109  	c.crd.SetBundle(bundleConf)
   110  	c.status = c.crd.GetStatus()
   111  }
   112  
   113  func (c *GitCrd) CleanUp() {
   114  	if c.status != nil {
   115  		return
   116  	}
   117  
   118  	c.status = os.RemoveAll(c.crdDirectoryPath)
   119  }
   120  
   121  func (c *GitCrd) GetRepoURL() string {
   122  	return c.git.GetURL()
   123  }
   124  
   125  func (c *GitCrd) Reconcile(currentResourceList []*clientgo.Resource) {
   126  	if c.status != nil {
   127  		return
   128  	}
   129  
   130  	monitor := c.monitor.WithFields(map[string]interface{}{
   131  		"action": "reconiling",
   132  	})
   133  
   134  	toolsetCRD, err := c.getCrdContent()
   135  	if err != nil {
   136  		c.status = err
   137  		return
   138  	}
   139  
   140  	// pre-steps
   141  	if toolsetCRD.Spec.PreApply != nil {
   142  		preapplymonitor := monitor.WithField("application", "preapply")
   143  		preapplymonitor.Info("Start")
   144  		if err := c.applyFolder(preapplymonitor, toolsetCRD.Spec.PreApply, toolsetCRD.Spec.ForceApply); err != nil {
   145  			c.status = fmt.Errorf("preapply failed: %w", err)
   146  			return
   147  		}
   148  		preapplymonitor.Info("Done")
   149  	}
   150  
   151  	c.crd.Reconcile(currentResourceList, toolsetCRD, true)
   152  	err = c.crd.GetStatus()
   153  	if err != nil {
   154  		c.status = err
   155  		return
   156  	}
   157  
   158  	// post-steps
   159  	if toolsetCRD.Spec.PostApply != nil {
   160  		preapplymonitor := monitor.WithField("application", "postapply")
   161  		preapplymonitor.Info("Start")
   162  		if err := c.applyFolder(monitor, toolsetCRD.Spec.PostApply, toolsetCRD.Spec.ForceApply); err != nil {
   163  			c.status = fmt.Errorf("postapply failed: %w", err)
   164  			return
   165  		}
   166  		preapplymonitor.Info("Done")
   167  	}
   168  }
   169  
   170  func (c *GitCrd) getCrdMetadata() (*toolsetslatest.ToolsetMetadata, error) {
   171  	toolsetCRD := &toolsetslatest.ToolsetMetadata{}
   172  	err := c.git.ReadYamlIntoStruct("boom.yml", toolsetCRD)
   173  	if err != nil {
   174  		return nil, fmt.Errorf("error while unmarshaling boom.yml to struct: %w", err)
   175  	}
   176  
   177  	return toolsetCRD, nil
   178  }
   179  
   180  func (c *GitCrd) getCrdContent() (*toolsetslatest.Toolset, error) {
   181  	desiredTree, err := c.git.ReadTree(git.BoomFile)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	desiredKind, _, _, _, err := api.ParseToolset(desiredTree)
   187  	if err != nil {
   188  		return nil, fmt.Errorf("parsing desired state failed: %w", err)
   189  	}
   190  	desiredTree.Parsed = desiredKind
   191  
   192  	return desiredKind, nil
   193  }
   194  
   195  func (c *GitCrd) WriteBackCurrentState(currentResourceList []*clientgo.Resource) {
   196  	if c.status != nil {
   197  		return
   198  	}
   199  
   200  	content, err := yaml.Marshal(current.ResourcesToYaml(currentResourceList))
   201  	if err != nil {
   202  		c.status = err
   203  		return
   204  	}
   205  
   206  	toolsetCRD, err := c.getCrdContent()
   207  	if err != nil {
   208  		c.status = err
   209  		return
   210  	}
   211  
   212  	currentFolder := toolsetCRD.Spec.CurrentStateFolder
   213  	if currentFolder == "" {
   214  		currentFolder = filepath.Join("caos-internal", "boom")
   215  	}
   216  
   217  	file := git.File{
   218  		Path:    filepath.Join(currentFolder, "current.yaml"),
   219  		Content: content,
   220  	}
   221  
   222  	c.status = c.git.UpdateRemote("current state changed", func() []git.File { return []git.File{file} })
   223  }
   224  
   225  func (c *GitCrd) applyFolder(monitor mntr.Monitor, apply *toolsetslatest.Apply, force bool) error {
   226  	if apply.Folder == "" {
   227  		monitor.Info("No folder provided")
   228  		return nil
   229  	}
   230  
   231  	err := helper.CopyFolderToLocal(c.git, c.crdDirectoryPath, apply.Folder)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	localFolder := filepath.Join(c.crdDirectoryPath, apply.Folder)
   237  	if !helper.FolderExists(localFolder) {
   238  		return errors.New("Folder is nonexistent")
   239  	}
   240  
   241  	if empty, err := helper.FolderEmpty(localFolder); empty == true || err != nil {
   242  		monitor.Info("Provided folder is empty")
   243  		return nil
   244  	}
   245  
   246  	if err := useFolder(monitor, apply.Deploy, localFolder, force); err != nil {
   247  		return err
   248  	}
   249  	return nil
   250  }
   251  
   252  func useFolder(monitor mntr.Monitor, deploy bool, folderPath string, force bool) error {
   253  
   254  	files, err := getFilesInDirectory(folderPath)
   255  	if err != nil {
   256  		return err
   257  	}
   258  	kustomizeFile := false
   259  	for _, file := range files {
   260  		if strings.HasSuffix(file, "kustomization.yaml") {
   261  			kustomizeFile = true
   262  			break
   263  		}
   264  	}
   265  
   266  	if kustomizeFile {
   267  		command, err := kustomize.New(folderPath)
   268  		if err != nil {
   269  			return err
   270  		}
   271  		command = command.Apply(force)
   272  		if !deploy {
   273  			command = command.Delete()
   274  		}
   275  		return helper.Run(monitor, command.Build())
   276  	} else {
   277  		return recursiveFolder(monitor, folderPath, deploy, force)
   278  	}
   279  }
   280  
   281  func recursiveFolder(monitor mntr.Monitor, folderPath string, deploy, force bool) error {
   282  	command := kubectl.NewApply(folderPath).Build()
   283  	if force {
   284  		command = kubectl.NewApply(folderPath).Force().Build()
   285  	}
   286  	if !deploy {
   287  		command = kubectl.NewDelete(folderPath).Build()
   288  	}
   289  
   290  	folders, err := getDirsInDirectory(folderPath)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	for _, folder := range folders {
   296  		if folderPath != folder {
   297  			if err := recursiveFolder(monitor, filepath.Join(folderPath, folder), deploy, force); err != nil {
   298  				return err
   299  			}
   300  		}
   301  	}
   302  	return helper.Run(monitor, command)
   303  }
   304  
   305  func getFilesInDirectory(dirPath string) ([]string, error) {
   306  	files := make([]string, 0)
   307  
   308  	infos, err := ioutil.ReadDir(dirPath)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	for _, info := range infos {
   314  		if !info.IsDir() {
   315  			files = append(files, filepath.Join(dirPath, info.Name()))
   316  		}
   317  	}
   318  
   319  	return files, err
   320  }
   321  
   322  func getDirsInDirectory(dirPath string) ([]string, error) {
   323  	dirs := make([]string, 0)
   324  
   325  	infos, err := ioutil.ReadDir(dirPath)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	for _, info := range infos {
   331  		if info.IsDir() {
   332  			dirs = append(dirs, filepath.Join(dirPath, info.Name()))
   333  		}
   334  	}
   335  	return dirs, err
   336  }