internal/fabric/stages.go (202 lines of code) (raw):

/* Copyright © 2024 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package fabric import ( "path/filepath" "sync" "github.com/GoogleCloudPlatform/pastures-poc-toolkit/internal/terraform" "github.com/GoogleCloudPlatform/pastures-poc-toolkit/internal/utils" ) const ( // TODO: move all to common file for library fabricDst = "fast" fastSrc = "fast/stages" foundationDir = "foundations" seedDst = "pastures" seedSrc = "terraform" seedDir = "seeds" fabricRepo = "https://github.com/GoogleCloudPlatform/cloud-foundation-fabric.git" seedRepo = "https://github.com/GoogleCloudPlatform/pastures-poc-toolkit" outputBucketSuffix = "-prod-iac-core-outputs-0" ) var ( fastStages = []string{"0-bootstrap", "1-resman"} resmanVars = []string{"0-globals", "0-bootstrap"} ) func InitializeFoundationStages( configPath string, prefix string, vars ...*VarsFile, ) []*Stage { stages := make([]*Stage, 0) deps := make([]*VarsFile, 0) for _, s := range fastStages { if s == "1-resman" { for _, r := range resmanVars { deps = append( deps, resmanDependencies(r, s, prefix, configPath), ) } } else { deps = vars } repo := utils.NewRepo() repo.SetURL(fabricRepo) repo.SetDestination(filepath.Join(configPath, fabricDst)) repo.SetLink( filepath.Join(configPath, foundationDir), filepath.Join(configPath, fabricDst, fastSrc), ) stages = append(stages, &Stage{ Name: s, Type: "foundation", Path: filepath.Join(configPath, foundationDir, s), Repository: repo, ProviderFile: NewProviderFile( s, prefix, filepath.Join(configPath, foundationDir), ), StageVars: deps, }) } return stages } func NewSeedStage(configPath string) *Stage { repo := utils.NewRepo() repo.SetURL(seedRepo) repo.SetDestination(filepath.Join(configPath, seedDst)) repo.SetLink( filepath.Join(configPath, seedDir), filepath.Join(configPath, seedDst, seedSrc), ) return &Stage{ Type: "seed", Repository: repo, } } func (s *Stage) HydrateSeed(name string, prefix string, configPath string) { s.Name = name s.Path = filepath.Join(configPath, seedDir, name) s.ProviderFile = NewProviderFile( name, prefix, filepath.Join(configPath, seedDir), ) } func (s *Stage) AddVarFile(file *VarsFile) { s.StageVars = append(s.StageVars, file) } func (s *Stage) SetFactory(factory FabricFactory) { s.Factories = append(s.Factories, factory) } func (s *Stage) DiscoverFiles() error { files := make([]ConfigFile, 0) // grab all of the var files for _, v := range s.StageVars { files = append(files, v) } // add the provider file files = append(files, s.ProviderFile) // try to download them all for _, f := range files { if err := f.DownloadFile(); err != nil { return err } } return nil } func (s *Stage) Init(verbose bool) error { var migrate bool = false // test if module initialized _, err := terraform.TfPull(s.Path, false) // never verbose a pull function // try to initialize if err != nil { if err := terraform.TfInit(s.Path, false, verbose); err != nil { migrate = true } } // try one more time, but migrate the state if migrate { if err := terraform.TfInit(s.Path, true, verbose); err != nil { return err } } return nil } func (s *Stage) Plan(verbose bool) error { var wg sync.WaitGroup var files []string // make some channels done := make(chan bool) result := make(chan terraform.PlanResult) // extract var files from stage for _, f := range s.StageVars { files = append(files, f.LocalPath) } // start an overwatch if !verbose { go utils.ProgressTicker(s.Type, &wg, done) } // do what we came here to do go func() { planResult := terraform.TfPlan(s.Path, files, nil, verbose) if !verbose { done <- true // only fire this channel if ticker is running } result <- planResult }() // catch the plan result planResult := <-result // wait for stuff to finish wg.Wait() // turndown the channels close(done) close(result) if planResult.Err != nil { return planResult.Err } return nil } func (s *Stage) Apply(vars []*terraform.Vars, verbose bool) error { var wg sync.WaitGroup var files []string // make some channels done := make(chan bool) err := make(chan error) // extract var files from stage for _, f := range s.StageVars { files = append(files, f.LocalPath) } // start an overwatch if !verbose { go utils.ProgressTicker(s.Name, &wg, done) } // do what we came here to do go func() { applyErr := terraform.TfApply(s.Path, files, vars, nil, verbose) if !verbose { done <- true // only fire this channel if ticker is running } err <- applyErr }() // catch any errors tfError := <-err // wait for stuff to finish wg.Wait() // turndown the channels close(done) close(err) if tfError != nil { return tfError } return nil } func (s *Stage) Destroy(vars []*terraform.Vars, verbose bool) error { var wg sync.WaitGroup var files []string // make some channels done := make(chan bool) err := make(chan error) // extract var files from stage for _, f := range s.StageVars { files = append(files, f.LocalPath) } // start an overwatch if !verbose { go utils.ProgressTicker(s.Name, &wg, done) } // do what we came here to do go func() { destroyErr := terraform.TfDestroy(s.Path, files, vars, nil, verbose) if !verbose { done <- true // only fire this channel if ticker is running } err <- destroyErr }() // catch any errors tfError := <-err // wait for stuff to finish wg.Wait() // turndown the channels close(done) close(err) if tfError != nil { return tfError } return nil } func bktName(prefix string) string { return prefix + outputBucketSuffix }