From 51eb7923e6a5bdefedda3fd3b01f677ddc57e87c Mon Sep 17 00:00:00 2001 From: michaeljguarino Date: Sat, 30 Dec 2023 15:23:13 -0500 Subject: [PATCH] `plural up` command This will use a new bootstrapping method more in line with CD-exclusive usecases. It works basically as follows: * set up a git submodule of our bootstrap repo w/ some base charts and terraform * template some of the variable settings from there into the installation repo * run terraform off those templates This will set up a management cluster and fully install console onto it. We can further extend this to do the full OSS app installation process on top of CD. The main benefits here are separating cluster provisioning concerns out of our purview (users will manually manage their own terraform after running up), and reduce our scope to just maintaining the helm chart updates for the console and our runtime via CD. It also makes reconfiguration more natural since the users can delink the submodule and take full ownership whenever needed. --- cmd/plural/cd.go | 21 ++++++ cmd/plural/crypto.go | 1 + cmd/plural/init.go | 5 ++ cmd/plural/plural.go | 80 ++++++++++++-------- cmd/plural/up.go | 67 +++++++++++++++++ cmd/plural/utils.go | 127 -------------------------------- pkg/cd/control_plane_install.go | 90 +++++++++++++++++++++- pkg/provider/aws.go | 4 + pkg/provider/azure.go | 18 +++-- pkg/provider/equinix.go | 4 + pkg/provider/gcp.go | 9 ++- pkg/provider/kind.go | 2 + pkg/provider/provider.go | 1 + pkg/provider/test.go | 2 + pkg/up/context.go | 35 +++++++++ pkg/up/deploy.go | 71 ++++++++++++++++++ pkg/up/generate.go | 45 +++++++++++ pkg/up/ping.go | 36 +++++++++ pkg/up/template.go | 60 +++++++++++++++ pkg/utils/git/repo.go | 5 ++ 20 files changed, 516 insertions(+), 167 deletions(-) create mode 100644 cmd/plural/up.go delete mode 100644 cmd/plural/utils.go create mode 100644 pkg/up/context.go create mode 100644 pkg/up/deploy.go create mode 100644 pkg/up/generate.go create mode 100644 pkg/up/ping.go create mode 100644 pkg/up/template.go diff --git a/cmd/plural/cd.go b/cmd/plural/cd.go index 9de1acbc5..199829508 100644 --- a/cmd/plural/cd.go +++ b/cmd/plural/cd.go @@ -44,6 +44,17 @@ func (p *Plural) cdCommands() []cli.Command { Action: p.handleInstallControlPlane, Usage: "sets up the plural console in an existing k8s cluster", }, + { + Name: "control-plane-values", + Action: p.handlePrintControlPlaneValues, + Usage: "dumps a values file for installing the plural console", + Flags: []cli.Flag{ + cli.StringFlag{Name: "domain", Usage: "The plural domain to use for this console", Required: true}, + cli.StringFlag{Name: "dsn", Usage: "The Postgres DSN to use for database connections", Required: true}, + cli.StringFlag{Name: "name", Usage: "The name given to the cluster", Required: true}, + cli.StringFlag{Name: "file", Usage: "The file to dump values to", Required: true}, + }, + }, { Name: "uninstall", Action: p.handleUninstallOperator, @@ -166,6 +177,16 @@ func (p *Plural) handleInstallControlPlane(_ *cli.Context) error { return nil } +func (p *Plural) handlePrintControlPlaneValues(c *cli.Context) error { + conf := config.Read() + vals, err := cd.ControlPlaneValues(conf, c.String("domain"), c.String("dsn"), c.String("name")) + if err != nil { + return err + } + + return os.WriteFile(c.String("file"), []byte(vals), 0644) +} + func (p *Plural) handleEject(c *cli.Context) (err error) { if !c.Args().Present() { return fmt.Errorf("clusterid cannot be empty") diff --git a/cmd/plural/crypto.go b/cmd/plural/crypto.go index 3193b18c6..feb0046fd 100644 --- a/cmd/plural/crypto.go +++ b/cmd/plural/crypto.go @@ -41,6 +41,7 @@ context.yaml filter=plural-crypt diff=plural-crypt workspace.yaml filter=plural-crypt diff=plural-crypt context.yaml* filter=plural-crypt diff=plural-crypt workspace.yaml* filter=plural-crypt diff=plural-crypt +helm-values/*.yaml filter=plural-crypt diff=plural-crypt .gitattributes !filter !diff ` diff --git a/cmd/plural/init.go b/cmd/plural/init.go index 04b8299e0..47ad4f310 100644 --- a/cmd/plural/init.go +++ b/cmd/plural/init.go @@ -29,6 +29,11 @@ func (p *Plural) handleInit(c *cli.Context) error { gitCreated := false repo := "" + if utils.Exists("./workspace.yaml") { + utils.Highlight("Found workspace.yaml, skipping init as this repo has already been initialized...\n") + return nil + } + git, err := wkspace.Preflight() if err != nil && git { return err diff --git a/cmd/plural/plural.go b/cmd/plural/plural.go index d832e1235..be87eb7a2 100644 --- a/cmd/plural/plural.go +++ b/cmd/plural/plural.go @@ -105,6 +105,53 @@ func (p *Plural) getCommands() []cli.Command { Usage: "Gets cli version info", Action: versionInfo, }, + { + Name: "up", + Usage: "sets up your repository and an initial management cluster", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "endpoint", + Usage: "the endpoint for the plural installation you're working with", + }, + cli.StringFlag{ + Name: "service-account", + Usage: "email for the service account you'd like to use for this workspace", + }, + cli.BoolFlag{ + Name: "ignore-preflights", + Usage: "whether to ignore preflight check failures prior to init", + }, + cli.StringFlag{ + Name: "commit", + Usage: "commits your changes with this message", + }, + }, + Action: latestVersion(p.handleUp), + }, + { + Name: "down", + Usage: "destroys your management cluster and any apps installed on it", + Action: latestVersion(p.handleDown), + }, + { + Name: "init", + Usage: "initializes plural within a git repo", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "endpoint", + Usage: "the endpoint for the plural installation you're working with", + }, + cli.StringFlag{ + Name: "service-account", + Usage: "email for the service account you'd like to use for this workspace", + }, + cli.BoolFlag{ + Name: "ignore-preflights", + Usage: "whether to ignore preflight check failures prior to init", + }, + }, + Action: tracked(latestVersion(p.handleInit), "cli.init"), + }, { Name: "build", Aliases: []string{"bld"}, @@ -272,25 +319,6 @@ func (p *Plural) getCommands() []cli.Command { Usage: "Handles authentication to the plural api", Subcommands: p.authCommands(), }, - { - Name: "init", - Usage: "initializes plural within a git repo", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "endpoint", - Usage: "the endpoint for the plural installation you're working with", - }, - cli.StringFlag{ - Name: "service-account", - Usage: "email for the service account you'd like to use for this workspace", - }, - cli.BoolFlag{ - Name: "ignore-preflights", - Usage: "whether to ignore preflight check failures prior to init", - }, - }, - Action: tracked(latestVersion(p.handleInit), "cli.init"), - }, { Name: "preflights", Usage: "runs provider preflight checks", @@ -443,17 +471,11 @@ func (p *Plural) getCommands() []cli.Command { Subcommands: p.opsCommands(), Category: "Debugging", }, - { - Name: "utils", - Usage: "useful plural utilities", - Subcommands: utilsCommands(), - Category: "Miscellaneous", - }, { Name: "vpn", Usage: "interacting with the plural vpn", Subcommands: p.vpnCommands(), - Category: "Miscellaneous", + Category: "Workspace", }, { Name: "ai", @@ -501,12 +523,6 @@ func (p *Plural) getCommands() []cli.Command { Action: latestVersion(diffed), Category: "Workspace", }, - { - Name: "from-grafana", - Usage: "imports a grafana dashboard to a plural crd", - Action: latestVersion(formatDashboard), - Category: "Publishing", - }, { Name: "bootstrap", Usage: "Commands for bootstrapping cluster", diff --git a/cmd/plural/up.go b/cmd/plural/up.go new file mode 100644 index 000000000..4bc7c2789 --- /dev/null +++ b/cmd/plural/up.go @@ -0,0 +1,67 @@ +package plural + +import ( + "fmt" + + "github.com/urfave/cli" + + "github.com/pluralsh/plural-cli/pkg/up" + "github.com/pluralsh/plural-cli/pkg/utils" + "github.com/pluralsh/plural-cli/pkg/utils/git" +) + +const ( + affirmUp = "Are you ready to set up your initial management cluster? You can check the generated terraform/helm to confirm everything looks good first" + affirmDown = "Are you ready to destroy your plural infrastructure? This will destroy all k8s clusters and any data stored within" +) + +func (p *Plural) handleUp(c *cli.Context) error { + if err := p.handleInit(c); err != nil { + return err + } + p.InitPluralClient() + + repoRoot, err := git.Root() + if err != nil { + return err + } + + ctx, err := up.Build() + if err != nil { + return err + } + + if err := ctx.Generate(); err != nil { + return err + } + + if !affirm(affirmUp, "PLURAL_UP_AFFIRM_DEPLOY") { + return fmt.Errorf("cancelled deploy") + } + + if err := ctx.Deploy(); err != nil { + return err + } + + utils.Highlight("\n==> Commit and push your configuration\n\n") + + if commit := commitMsg(c); commit != "" { + utils.Highlight("Pushing upstream...\n") + return git.Sync(repoRoot, commit, c.Bool("force")) + } + + return nil +} + +func (p *Plural) handleDown(c *cli.Context) error { + if !affirm(affirmDown, "PLURAL_DOWN_AFFIRM_DESTROY") { + return fmt.Errorf("cancelled destroy") + } + + ctx, err := up.Build() + if err != nil { + return err + } + + return ctx.Destroy() +} diff --git a/cmd/plural/utils.go b/cmd/plural/utils.go deleted file mode 100644 index 991173848..000000000 --- a/cmd/plural/utils.go +++ /dev/null @@ -1,127 +0,0 @@ -package plural - -import ( - "bufio" - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/coreos/go-semver/semver" - "github.com/pluralsh/plural-cli/pkg/utils" - "github.com/pluralsh/plural-cli/pkg/utils/pathing" - "github.com/thoas/go-funk" - "github.com/urfave/cli" - "sigs.k8s.io/yaml" -) - -func utilsCommands() []cli.Command { - return []cli.Command{ - { - Name: "image-bump", - ArgsUsage: "CHART", - Usage: "Bumps a chart's image tag", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "path", - Usage: "path to tag in helm values file", - }, - cli.StringFlag{ - Name: "tag", - Usage: "the image tag to set to", - }, - }, - Action: latestVersion(handleImageBump), - }, - } -} - -func handleImageBump(c *cli.Context) error { - chartPath := c.Args().Get(0) - path, tag := c.String("path"), c.String("tag") - - chartPath, _ = filepath.Abs(chartPath) - - chart := make(map[string]interface{}) - vals := make(map[string]interface{}) - - if err := readHelmYaml(pathing.SanitizeFilepath(filepath.Join(chartPath, "Chart.yaml")), &chart); err != nil { - return err - } - - if err := readHelmYaml(pathing.SanitizeFilepath(filepath.Join(chartPath, "values.yaml")), &vals); err != nil { - return err - } - - currentTag := funk.Get(vals, path) - if currentTag == tag { - utils.Highlight("No change in version tag\n") - return nil - } - - currentVersion := (chart["version"]).(string) - sv, err := semver.NewVersion(currentVersion) - if err != nil { - return err - } - - if err := replaceVals(pathing.SanitizeFilepath(filepath.Join(chartPath, "values.yaml")), tag); err != nil { - return err - } - - sv.BumpPatch() - chart["version"] = sv.String() - - return writeHelmYaml(pathing.SanitizeFilepath(filepath.Join(chartPath, "Chart.yaml")), chart) -} - -const replPattern = `(?s).*## PLRL-REPLACE\[(.*)\].*` - -func replaceVals(path string, val string) error { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - result := make([]string, 0) - scanner := bufio.NewScanner(file) - // optionally, resize scanner's capacity for lines over 64K, see next example - re := regexp.MustCompile(replPattern) - for scanner.Scan() { - line := scanner.Text() - matches := re.FindStringSubmatch(line) - if len(matches) > 0 { - line = fmt.Sprintf(matches[1]+" ## PLRL-REPLACE[%s]", val, matches[1]) - } - - result = append(result, line) - } - - if err := scanner.Err(); err != nil { - return err - } - - content := strings.Join(result, "\n") - - return os.WriteFile(path, []byte(content), 0644) -} - -func readHelmYaml(path string, result *map[string]interface{}) error { - content, err := os.ReadFile(path) - if err != nil { - return err - } - - return yaml.Unmarshal(content, result) -} - -func writeHelmYaml(path string, vals map[string]interface{}) error { - io, err := yaml.Marshal(&vals) - if err != nil { - return err - } - - return os.WriteFile(path, io, 0644) -} diff --git a/pkg/cd/control_plane_install.go b/pkg/cd/control_plane_install.go index bbe9c7dbf..13a458f8c 100644 --- a/pkg/cd/control_plane_install.go +++ b/pkg/cd/control_plane_install.go @@ -20,9 +20,97 @@ var ( ) const ( - templateUrl = "https://raw.githubusercontent.com/pluralsh/console/cd-scaffolding/charts/console/values.yaml.liquid" + templateUrl = "https://raw.githubusercontent.com/pluralsh/console/fix-migration-job/templates/values.yaml.liquid" ) +func ControlPlaneValues(conf config.Config, domain, dsn, name string) (string, error) { + consoleDns := fmt.Sprintf("console.%s", domain) + kasDns := fmt.Sprintf("kas.%s", domain) + randoms := map[string]string{} + for _, key := range []string{"jwt", "erlang", "adminPassword", "kasApi", "kasPrivateApi", "kasRedis"} { + rand, err := crypto.RandStr(32) + if err != nil { + return "", err + } + randoms[key] = rand + } + + client := api.FromConfig(&conf) + me, err := client.Me() + if err != nil { + return "", fmt.Errorf("you must run `plural login` before installing") + } + + configuration := map[string]string{ + "consoleDns": consoleDns, + "kasDns": kasDns, + "aesKey": utils.GenAESKey(), + "adminName": me.Email, + "adminEmail": me.Email, + "clusterName": name, + "pluralToken": conf.Token, + "postgresUrl": dsn, + "clusterIssuer": "plural", + } + for k, v := range randoms { + configuration[k] = v + } + + cryptos, err := cryptoVals() + if err != nil { + return "", err + } + + for k, v := range cryptos { + configuration[k] = v + } + + clientId, clientSecret, err := ensureInstalledAndOidc(client, consoleDns) + if err != nil { + return "", err + } + configuration["pluralClientId"] = clientId + configuration["pluralClientSecret"] = clientSecret + + tpl, err := fetchTemplate() + if err != nil { + return "", err + } + + bindings := map[string]interface{}{ + "configuration": configuration, + } + + res, err := liquidEngine.ParseAndRender(tpl, bindings) + return string(res), err +} + +func cryptoVals() (map[string]string, error) { + res := make(map[string]string) + keyFile, err := config.PluralDir("key") + if err != nil { + return res, err + } + + aes, err := utils.ReadFile(keyFile) + if err != nil { + return res, err + } + res["key"] = aes + + identityFile, err := config.PluralDir("identity") + if err != nil { + return res, nil + } + + identity, err := utils.ReadFile(identityFile) + if err != nil { + return res, nil + } + res["identity"] = identity + return res, nil +} + func CreateControlPlane(conf config.Config) (string, error) { client := api.FromConfig(&conf) me, err := client.Me() diff --git a/pkg/provider/aws.go b/pkg/provider/aws.go index 7ff94b1bd..d90a92b5f 100644 --- a/pkg/provider/aws.go +++ b/pkg/provider/aws.go @@ -237,6 +237,10 @@ func getAwsConfig(ctx context.Context) (aws.Config, error) { return cfg, plrlErrors.ErrorWrap(err, "Failed to initialize aws client: ") } +func (aws *AWSProvider) CreateBucket() error { + return aws.mkBucket(aws.bucket) +} + func (aws *AWSProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { if err := aws.mkBucket(aws.bucket); err != nil { return "", plrlErrors.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state bucket %s", aws.bucket)) diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go index e160015c9..5bc8d14da 100644 --- a/pkg/provider/azure.go +++ b/pkg/provider/azure.go @@ -201,13 +201,21 @@ func AzureFromManifest(man *manifest.ProjectManifest, clientSet *ClientSet) (*Az return &AzureProvider{man.Cluster, man.Project, man.Bucket, man.Region, man.Context, nil, clients}, nil } -func (az *AzureProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { +func (az *AzureProvider) CreateBucket() error { if err := az.CreateResourceGroup(az.Project()); err != nil { - return "", pluralerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state resource group %s", az.Project())) + return pluralerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state resource group %s", az.Project())) + } + + if err := az.createContainer(az.bucket); err != nil { + return pluralerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state bucket %s", az.bucket)) } - if err := az.CreateBucket(az.bucket); err != nil { - return "", pluralerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state bucket %s", az.bucket)) + return nil +} + +func (az *AzureProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { + if err := az.CreateBucket(); err != nil { + return "", err } ctx["Region"] = az.Region() @@ -230,7 +238,7 @@ func (az *AzureProvider) CreateBackend(prefix string, version string, ctx map[st return template.RenderString(scaffold, ctx) } -func (az *AzureProvider) CreateBucket(bucket string) (err error) { +func (az *AzureProvider) createContainer(bucket string) (err error) { acc, err := az.upsertStorageAccount(utils.ToString(az.Context()["StorageAccount"])) if err != nil { return diff --git a/pkg/provider/equinix.go b/pkg/provider/equinix.go index a4a9edd40..cd7a2de23 100644 --- a/pkg/provider/equinix.go +++ b/pkg/provider/equinix.go @@ -117,6 +117,10 @@ func equinixFromManifest(man *manifest.ProjectManifest) (*EQUINIXProvider, error return &EQUINIXProvider{man.Cluster, man.Project, man.Bucket, man.Region, man.Context, nil}, nil } +func (equinix *EQUINIXProvider) CreateBucket() error { + return nil +} + func (equinix *EQUINIXProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { ctx["Region"] = equinix.Region() diff --git a/pkg/provider/gcp.go b/pkg/provider/gcp.go index 3f2e66b67..ce506ca75 100644 --- a/pkg/provider/gcp.go +++ b/pkg/provider/gcp.go @@ -248,9 +248,14 @@ func (gcp *GCPProvider) Flush() error { return gcp.writer() } +func (gcp *GCPProvider) CreateBucket() error { + err := gcp.mkBucket(gcp.bucket) + return utilerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state bucket %s", gcp.Bucket())) +} + func (gcp *GCPProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { - if err := gcp.mkBucket(gcp.bucket); err != nil { - return "", utilerr.ErrorWrap(err, fmt.Sprintf("Failed to create terraform state bucket %s", gcp.Bucket())) + if err := gcp.CreateBucket(); err != nil { + return "", err } ctx["Project"] = gcp.Project() diff --git a/pkg/provider/kind.go b/pkg/provider/kind.go index cb3c24477..00c3c55cd 100644 --- a/pkg/provider/kind.go +++ b/pkg/provider/kind.go @@ -71,6 +71,8 @@ func kindFromManifest(man *manifest.ProjectManifest) (*KINDProvider, error) { return &KINDProvider{man.Cluster, man.Project, man.Bucket, man.Region, man.Context, nil}, nil } +func (kind *KINDProvider) CreateBucket() error { return nil } + func (kind *KINDProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { ctx["Region"] = kind.Region() diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 507b04bc6..fc407c1d0 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -25,6 +25,7 @@ type Provider interface { KubeConfig() error KubeContext() string CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) + CreateBucket() error Context() map[string]interface{} Decommision(node *v1.Node) error Preflights() []*Preflight diff --git a/pkg/provider/test.go b/pkg/provider/test.go index 9519367a5..2b4a578a2 100644 --- a/pkg/provider/test.go +++ b/pkg/provider/test.go @@ -44,6 +44,8 @@ func (t TestProvider) KubeContext() string { return t.Clust } +func (t TestProvider) CreateBucket() error { return nil } + func (t TestProvider) CreateBackend(prefix string, version string, ctx map[string]interface{}) (string, error) { return "test", nil } diff --git a/pkg/up/context.go b/pkg/up/context.go new file mode 100644 index 000000000..484b74cd7 --- /dev/null +++ b/pkg/up/context.go @@ -0,0 +1,35 @@ +package up + +import ( + "path/filepath" + + "github.com/pluralsh/plural-cli/pkg/config" + "github.com/pluralsh/plural-cli/pkg/manifest" + "github.com/pluralsh/plural-cli/pkg/provider" +) + +type Context struct { + Provider provider.Provider + Manifest *manifest.ProjectManifest + Config *config.Config +} + +func Build() (*Context, error) { + projPath, _ := filepath.Abs("workspace.yaml") + project, err := manifest.ReadProject(projPath) + if err != nil { + return nil, err + } + + prov, err := provider.FromManifest(project) + if err != nil { + return nil, err + } + + conf := config.Read() + return &Context{ + Provider: prov, + Config: &conf, + Manifest: project, + }, nil +} diff --git a/pkg/up/deploy.go b/pkg/up/deploy.go new file mode 100644 index 000000000..32e24a4d0 --- /dev/null +++ b/pkg/up/deploy.go @@ -0,0 +1,71 @@ +package up + +import ( + "fmt" + "os" + "os/exec" + "time" + + "github.com/pluralsh/plural-cli/pkg/utils" +) + +type terraformCmd struct { + dir string + cmd string + args []string + retries int +} + +func (ctx *Context) Deploy() error { + if err := ctx.Provider.CreateBucket(); err != nil { + return err + } + + if err := runAll([]terraformCmd{ + {dir: "./clusters", cmd: "init", args: []string{"-upgrade"}}, + {dir: "./clusters", cmd: "apply", args: []string{"-auto-approve"}, retries: 1}, + }); err != nil { + return err + } + + return ping(fmt.Sprintf("https://console.%s", ctx.Manifest.Network.Subdomain)) +} + +func (ctx *Context) Destroy() error { + return runAll([]terraformCmd{ + {dir: "./clusters", cmd: "init", args: []string{"-upgrade"}}, + {dir: "./clusters", cmd: "destroy", args: []string{"-auto-approve"}, retries: 1}, + }) +} + +func runAll(cmds []terraformCmd) error { + for _, cmd := range cmds { + if err := cmd.run(); err != nil { + return err + } + } + + return nil +} + +func (tf *terraformCmd) run() (err error) { + for tf.retries >= 0 { + args := append([]string{tf.cmd}, tf.args...) + cmd := exec.Command("terraform", args...) + cmd.Dir = tf.dir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err == nil { + return + } + + tf.retries -= 1 + if tf.retries >= 0 { + utils.Warn("terraform cmd failed, retrying") + time.Sleep(10 * time.Second) + } + } + + return +} diff --git a/pkg/up/generate.go b/pkg/up/generate.go new file mode 100644 index 000000000..8a8fffe5f --- /dev/null +++ b/pkg/up/generate.go @@ -0,0 +1,45 @@ +package up + +import ( + "fmt" + + "github.com/pluralsh/plural-cli/pkg/utils" + "github.com/pluralsh/plural-cli/pkg/utils/git" +) + +type templatePair struct { + from string + to string + overwrite bool +} + +func (ctx *Context) Generate() error { + if !utils.Exists("./bootstrap") { + if err := git.Submodule("https://github.com/pluralsh/bootstrap.git"); err != nil { + return err + } + } + + prov := ctx.Provider.Name() + tpls := []templatePair{ + {from: "./bootstrap/charts/runtime/values.yaml.tpl", to: "./helm-values/runtime.yaml", overwrite: true}, + {from: fmt.Sprintf("./bootstrap/templates/providers/bootstrap/%s.tf", prov), to: "clusters/provider.tf"}, + {from: fmt.Sprintf("./bootstrap/templates/setup/providers/%s.tf", prov), to: "clusters/mgmt.tf"}, + {from: "./bootstrap/templates/setup/console.tf", to: "clusters/console.tf"}, + {from: fmt.Sprintf("./bootstrap/templates/providers/apps/%s.tf", prov), to: "apps/provider.tf"}, + {from: "./bootstrap/README.md", to: "README.md", overwrite: true}, + } + + for _, tpl := range tpls { + if utils.Exists(tpl.to) && !tpl.overwrite { + fmt.Printf("%s already exists, skipping for now...\n", tpl.to) + continue + } + + if err := ctx.templateFrom(tpl.from, tpl.to); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/up/ping.go b/pkg/up/ping.go new file mode 100644 index 000000000..88a660383 --- /dev/null +++ b/pkg/up/ping.go @@ -0,0 +1,36 @@ +package up + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/pluralsh/plural-cli/pkg/utils" +) + +func ping(url string) error { + done := make(chan bool) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + go func() { + for { + fmt.Printf("Pinging %s...\n", url) + resp, err := http.Get(url) + if err == nil && resp.StatusCode == 200 { + utils.Success("Found status code 200, console up!\n") + done <- true + return + } + time.Sleep(10 * time.Second) + } + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return fmt.Errorf("Console failed to become ready after 5 minutes, you might want to inspect the resources in the plrl-console namespace") + } +} diff --git a/pkg/up/template.go b/pkg/up/template.go new file mode 100644 index 000000000..39e0ee585 --- /dev/null +++ b/pkg/up/template.go @@ -0,0 +1,60 @@ +package up + +import ( + "bytes" + "time" + + "text/template" + + "github.com/pluralsh/plural-cli/pkg/api" + "github.com/pluralsh/plural-cli/pkg/utils" + "github.com/pluralsh/polly/retry" +) + +func (ctx *Context) templateFrom(file, to string) error { + buf, err := utils.ReadFile(file) + if err != nil { + return err + } + + res, err := ctx.template(buf) + if err != nil { + return err + } + + return utils.WriteFile(to, []byte(res)) +} + +func (ctx *Context) template(tmplate string) (string, error) { + cluster, provider := ctx.Provider.Cluster(), ctx.Provider.Name() + client := api.NewClient() + retrier := retry.NewConstant(15*time.Millisecond, 3) + eabCredential, err := retry.Retry(retrier, func() (*api.EabCredential, error) { + return client.GetEabCredential(cluster, provider) + }) + if err != nil { + return "", err + } + + values := map[string]interface{}{ + "Cluster": cluster, + "Provider": provider, + "Subdomain": ctx.Manifest.Network.Subdomain, + "Network": ctx.Manifest.Network, + "Bucket": ctx.Provider.Bucket(), + "Project": ctx.Provider.Project(), + "Region": ctx.Provider.Region(), + "Context": ctx.Provider.Context(), + "Config": ctx.Config, + "Acme": eabCredential, + } + + tpl, err := template.New("gotpl").Parse(tmplate) + if err != nil { + return "", err + } + + var buf bytes.Buffer + err = tpl.Execute(&buf, values) + return buf.String(), err +} diff --git a/pkg/utils/git/repo.go b/pkg/utils/git/repo.go index 33939748e..dfc11119c 100644 --- a/pkg/utils/git/repo.go +++ b/pkg/utils/git/repo.go @@ -24,6 +24,11 @@ func CurrentBranch() (string, error) { return GitRaw("rev-parse", "--abbrev-ref", "HEAD") } +func Submodule(url string) error { + _, err := GitRaw("submodule", "add", url) + return err +} + func HasUpstreamChanges() (bool, string, error) { headRef, err := GitRaw("rev-parse", "--symbolic-full-name", "HEAD") if err != nil {