From 9d94f491531b2dcfa60ea7498bebe125635b7999 Mon Sep 17 00:00:00 2001 From: michaeljguarino Date: Fri, 14 Apr 2023 16:10:54 -0400 Subject: [PATCH 1/2] Cluster Ownership transfer This has three phases: * call ownership transfer api to sync all installations * overwrite owner entry in workspace.yaml * rebuild bootstrap and console to resync docker creds and rewire the console to use new owner --- cmd/plural/clusters.go | 57 ++++++++++++++++++++++++++++++++++++++++ cmd/plural/plural.go | 34 +++++++++++++++--------- go.mod | 2 +- go.sum | 4 +-- pkg/api/client.go | 1 + pkg/api/cluster.go | 5 ++++ pkg/manifest/manifest.go | 4 +++ pkg/test/mocks/Client.go | 16 ++++++++++- 8 files changed, 106 insertions(+), 17 deletions(-) diff --git a/cmd/plural/clusters.go b/cmd/plural/clusters.go index ed4c79aa..79882c16 100644 --- a/cmd/plural/clusters.go +++ b/cmd/plural/clusters.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/pluralsh/plural/pkg/api" + "github.com/pluralsh/plural/pkg/config" "github.com/pluralsh/plural/pkg/manifest" "github.com/pluralsh/plural/pkg/utils" "github.com/urfave/cli" @@ -16,6 +17,17 @@ func (p *Plural) clusterCommands() []cli.Command { Usage: "lists clusters accessible to your user", Action: latestVersion(p.listClusters), }, + { + Name: "transfer", + Usage: "transfers ownership of the current cluster to another", + Action: latestVersion(rooted(p.transferOwnership)), + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "email", + Usage: "the email of the new owner", + }, + }, + }, { Name: "view", Usage: "shows info for a cluster", @@ -63,6 +75,51 @@ func (p *Plural) listClusters(c *cli.Context) error { }) } +func (p *Plural) transferOwnership(c *cli.Context) error { + p.InitPluralClient() + email := c.String("email") + man, err := manifest.FetchProject() + if err != nil { + return err + } + + if err := p.Client.TransferOwnership(man.Cluster, email); err != nil { + return err + } + + man.Owner.Email = email + if err := man.Flush(); err != nil { + return err + } + + if err := p.assumeServiceAccount(config.Read(), man); err != nil { + return err + } + + utils.Highlight("rebuilding bootstrap and console to sync your cluster with the new owner:\n") + + for _, app := range []string{"bootstrap", "console"} { + installation, err := p.GetInstallation(app) + if err != nil { + return api.GetErrorResponse(err, "GetInstallation") + } else if installation == nil { + continue + } + + if err := p.doBuild(installation, false); err != nil { + return err + } + } + + utils.Highlight("deploying rebuilt applications\n") + if err := p.deploy(c); err != nil { + return err + } + + utils.Success("Ownership successfully transferred to %s", email) + return nil +} + func (p *Plural) showCluster(c *cli.Context) error { p.InitPluralClient() id := c.String("id") diff --git a/cmd/plural/plural.go b/cmd/plural/plural.go index 77aacb66..ae8f4954 100644 --- a/cmd/plural/plural.go +++ b/cmd/plural/plural.go @@ -37,27 +37,35 @@ func (p *Plural) InitKube() error { return nil } +func (p *Plural) assumeServiceAccount(conf config.Config, man *manifest.ProjectManifest) error { + owner := man.Owner + jwt, email, err := api.FromConfig(&conf).ImpersonateServiceAccount(owner.Email) + if err != nil { + utils.Error("You (%s) are not the owner of this repo %s, %v \n", conf.Email, owner.Email, api.GetErrorResponse(err, "ImpersonateServiceAccount")) + return err + } + conf.Email = email + conf.Token = jwt + p.Client = api.FromConfig(&conf) + accessToken, err := p.Client.GrabAccessToken() + if err != nil { + utils.Error("failed to create access token, bailing") + return err + } + conf.Token = accessToken + config.SetConfig(&conf) + return nil +} + func (p *Plural) InitPluralClient() { if p.Client == nil { if project, err := manifest.FetchProject(); err == nil && config.Exists() { conf := config.Read() if owner := project.Owner; owner != nil && conf.Email != owner.Email { utils.LogInfo().Printf("Trying to impersonate service account: %s \n", owner.Email) - jwt, email, err := api.FromConfig(&conf).ImpersonateServiceAccount(owner.Email) - if err != nil { - utils.Error("You (%s) are not the owner of this repo %s, %v \n", conf.Email, owner.Email, api.GetErrorResponse(err, "ImpersonateServiceAccount")) - os.Exit(1) - } - conf.Email = email - conf.Token = jwt - p.Client = api.FromConfig(&conf) - accessToken, err := p.Client.GrabAccessToken() - if err != nil { - utils.Error("failed to create access token, bailing") + if err := p.assumeServiceAccount(conf, project); err != nil { os.Exit(1) } - conf.Token = accessToken - config.SetConfig(&conf) return } } diff --git a/go.mod b/go.mod index cb11be98..aef1d46c 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/packethost/packngo v0.29.0 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 - github.com/pluralsh/gqlclient v1.3.15 + github.com/pluralsh/gqlclient v1.3.16 github.com/pluralsh/plural-operator v0.5.3 github.com/pluralsh/polly v0.1.1 github.com/rodaine/hclencoder v0.0.1 diff --git a/go.sum b/go.sum index eaa05905..01cf7a89 100644 --- a/go.sum +++ b/go.sum @@ -920,8 +920,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pluralsh/controller-reconcile-helper v0.0.4 h1:1o+7qYSyoeqKFjx+WgQTxDz4Q2VMpzprJIIKShxqG0E= github.com/pluralsh/controller-reconcile-helper v0.0.4/go.mod h1:AfY0gtteD6veBjmB6jiRx/aR4yevEf6K0M13/pGan/s= -github.com/pluralsh/gqlclient v1.3.15 h1:QA6VEMDxeG0/e+qXvr7xgorHl45o4/Qg9CwsPyVn2gE= -github.com/pluralsh/gqlclient v1.3.15/go.mod h1:z1qHnvPeqIN/a+5OzFs40e6HI6tDxzh1+yJuEpvqGy4= +github.com/pluralsh/gqlclient v1.3.16 h1:JB26nFoQ/I9CEQ3PhcyClRugV/Qw2ozibz2zf9IGnng= +github.com/pluralsh/gqlclient v1.3.16/go.mod h1:z1qHnvPeqIN/a+5OzFs40e6HI6tDxzh1+yJuEpvqGy4= github.com/pluralsh/oauth v0.9.2 h1:tM9hBK4tCnJUeCOgX0ctxBBCS3hiCDPoxkJLODtedmQ= github.com/pluralsh/oauth v0.9.2/go.mod h1:aTUw/75rzcsbvW+/TLvWtHVDXFIdtFrDtUncOq9vHyM= github.com/pluralsh/plural-operator v0.5.3 h1:GaPL3LgimfzKZNHt7zXzqYZpb0hgyW9noHYnkA+rqNs= diff --git a/pkg/api/client.go b/pkg/api/client.go index 6c6dca7a..7d7098d8 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -86,6 +86,7 @@ type Client interface { Clusters() ([]*Cluster, error) Cluster(id string) (*Cluster, error) CreateUpgrade(queue, repository string, attrs gqlclient.UpgradeAttributes) error + TransferOwnership(name, email string) error } type client struct { diff --git a/pkg/api/cluster.go b/pkg/api/cluster.go index 7e896de8..91bf362f 100644 --- a/pkg/api/cluster.go +++ b/pkg/api/cluster.go @@ -24,6 +24,11 @@ func (client *client) PromoteCluster() error { return err } +func (client *client) TransferOwnership(name, email string) error { + _, err := client.pluralClient.TransferOwnership(client.ctx, name, email) + return err +} + func (client *client) Clusters() ([]*Cluster, error) { resp, err := client.pluralClient.Clusters(client.ctx, nil) if err != nil { diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index 1ac7c20d..8b413e13 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -76,6 +76,10 @@ func ReadProject(path string) (man *ProjectManifest, err error) { return } +func (man *ProjectManifest) Flush() error { + return man.Write(ProjectManifestPath()) +} + func (man *Manifest) Write(path string) error { versioned := &VersionedManifest{ ApiVersion: "plural.sh/v1alpha1", diff --git a/pkg/test/mocks/Client.go b/pkg/test/mocks/Client.go index 48691bd4..47c9586b 100644 --- a/pkg/test/mocks/Client.go +++ b/pkg/test/mocks/Client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.23.2. DO NOT EDIT. +// Code generated by mockery v2.24.0. DO NOT EDIT. package mocks @@ -1308,6 +1308,20 @@ func (_m *Client) Scaffolds(in *api.ScaffoldInputs) ([]*api.ScaffoldFile, error) return r0, r1 } +// TransferOwnership provides a mock function with given fields: name, email +func (_m *Client) TransferOwnership(name string, email string) error { + ret := _m.Called(name, email) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(name, email) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UninstallChart provides a mock function with given fields: id func (_m *Client) UninstallChart(id string) error { ret := _m.Called(id) From 85fc546c8600e1c0cf39a348981cc3be63d420df Mon Sep 17 00:00:00 2001 From: michaeljguarino Date: Mon, 24 Apr 2023 11:24:32 -0400 Subject: [PATCH 2/2] address comments --- cmd/plural/clusters.go | 4 ++-- cmd/plural/plural.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/plural/clusters.go b/cmd/plural/clusters.go index 79882c16..5804a4c7 100644 --- a/cmd/plural/clusters.go +++ b/cmd/plural/clusters.go @@ -83,8 +83,8 @@ func (p *Plural) transferOwnership(c *cli.Context) error { return err } - if err := p.Client.TransferOwnership(man.Cluster, email); err != nil { - return err + if err := p.TransferOwnership(man.Cluster, email); err != nil { + return api.GetErrorResponse(err, "TransferOwnership") } man.Owner.Email = email diff --git a/cmd/plural/plural.go b/cmd/plural/plural.go index ae8f4954..118ffe4e 100644 --- a/cmd/plural/plural.go +++ b/cmd/plural/plural.go @@ -47,10 +47,10 @@ func (p *Plural) assumeServiceAccount(conf config.Config, man *manifest.ProjectM conf.Email = email conf.Token = jwt p.Client = api.FromConfig(&conf) - accessToken, err := p.Client.GrabAccessToken() + accessToken, err := p.GrabAccessToken() if err != nil { utils.Error("failed to create access token, bailing") - return err + return api.GetErrorResponse(err, "GrabAccessToken") } conf.Token = accessToken config.SetConfig(&conf)