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 }