Skip to content

Commit

Permalink
feat: Cluster Ownership transfer (#398)
Browse files Browse the repository at this point in the history
* 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

* address comments
  • Loading branch information
michaeljguarino authored Apr 24, 2023
1 parent cb40526 commit c1b56b3
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 17 deletions.
57 changes: 57 additions & 0 deletions cmd/plural/clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down Expand Up @@ -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.TransferOwnership(man.Cluster, email); err != nil {
return api.GetErrorResponse(err, "TransferOwnership")
}

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")
Expand Down
34 changes: 21 additions & 13 deletions cmd/plural/plural.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.GrabAccessToken()
if err != nil {
utils.Error("failed to create access token, bailing")
return api.GetErrorResponse(err, "GrabAccessToken")
}
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
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 1 addition & 0 deletions pkg/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions pkg/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 15 additions & 1 deletion pkg/test/mocks/Client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c1b56b3

Please sign in to comment.