Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes lp#1830949: add-k8s validation for user supplied cloud/region #10523

Merged
merged 2 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions cloud/clouds.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,13 @@ func RegionByName(regions []Region, name string) (*Region, error) {
}
return &region, nil
}
availableRegions := "cloud has no regions"
if len(regions) > 0 {
availableRegions = fmt.Sprintf("expected one of %q", RegionNames(regions))
}
return nil, errors.NewNotFound(nil, fmt.Sprintf(
"region %q not found (expected one of %q)",
name, RegionNames(regions),
"region %q not found (%v)",
name, availableRegions,
))
}

Expand Down
34 changes: 34 additions & 0 deletions cloud/clouds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package cloud_test
import (
"io/ioutil"
"path/filepath"
"regexp"

"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"

Expand Down Expand Up @@ -329,3 +331,35 @@ ca-certificates: [fakecacert]
CACertificates: []string{"fakecacert"},
})
}

func (s *cloudSuite) TestRegionByNameNoRegions(c *gc.C) {
r, err := cloud.RegionByName([]cloud.Region{}, "star")
c.Assert(r, gc.IsNil)
c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`region "star" not found (cloud has no regions)`))
}

func (s *cloudSuite) TestRegionByName(c *gc.C) {
regions := []cloud.Region{
{Name: "mars"},
{Name: "earth"},
{Name: "jupiter"},
}

r, err := cloud.RegionByName(regions, "mars")
c.Assert(err, jc.ErrorIsNil)
c.Assert(r, gc.Not(gc.IsNil))
c.Assert(r, gc.DeepEquals, &cloud.Region{Name: "mars"})
}

func (s *cloudSuite) TestRegionByNameNotFound(c *gc.C) {
regions := []cloud.Region{
{Name: "mars"},
{Name: "earth"},
{Name: "jupiter"},
}

r, err := cloud.RegionByName(regions, "star")
c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`region "star" not found (expected one of ["earth" "jupiter" "mars"])`))
c.Assert(err, jc.Satisfies, errors.IsNotFound)
c.Assert(r, gc.IsNil)
}
35 changes: 33 additions & 2 deletions cmd/juju/caas/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ type AddCAASCommand struct {
// cloud is an alias of the hostCloudRegion.
cloud string

// givenHostCloudRegion holds a copy of cloud name/type and/or region as supplied by the user via options.
givenHostCloudRegion string

// workloadStorage is a storage class specified by the user.
workloadStorage string

Expand Down Expand Up @@ -228,6 +231,8 @@ func (c *AddCAASCommand) Init(args []string) (err error) {
if err != nil {
return errors.Trace(err)
}
// Keep a copy of the original user supplied value for comparison and validation later.
c.givenHostCloudRegion = c.hostCloudRegion
}

if c.gke {
Expand Down Expand Up @@ -322,6 +327,7 @@ func (c *AddCAASCommand) getGKEKubeConfig(ctx *cmd.Context) (io.Reader, string,
func (c *AddCAASCommand) getAKSKubeConfig(ctx *cmd.Context) (io.Reader, string, error) {
p := &clusterParams{
name: c.clusterName,
region: c.hostCloudRegion,
resourceGroup: c.resourceGroup,
}

Expand Down Expand Up @@ -421,6 +427,11 @@ func (c *AddCAASCommand) Run(ctx *cmd.Context) (err error) {
if err != nil {
return errors.Trace(err)
}
// By this stage, we know if cloud name/type and/or region input is needed from the user.
// If we could not detect it, check what was provided.
if err := checkCloudRegion(c.givenHostCloudRegion, newCloud.HostCloudRegion); err != nil {
return errors.Trace(err)
}

if err := addCloudToLocal(c.cloudMetadataStore, newCloud); err != nil {
return errors.Trace(err)
Expand All @@ -433,7 +444,7 @@ func (c *AddCAASCommand) Run(ctx *cmd.Context) (err error) {
if clusterName == "" {
clusterName = newCloud.HostCloudRegion
}
if c.controllerName == "" {
if c.Local {
successMsg := fmt.Sprintf("k8s substrate %q added as cloud %q%s", clusterName, c.caasName, storageMsg)
successMsg += fmt.Sprintf("\nYou can now bootstrap to this cloud by running 'juju bootstrap %s'.", c.caasName)
fmt.Fprintln(ctx.Stdout, successMsg)
Expand All @@ -458,12 +469,32 @@ func (c *AddCAASCommand) Run(ctx *cmd.Context) (err error) {
return nil
}

func checkCloudRegion(given, detected string) error {
if given == "" {
// User provided no host cloud/region information.
return nil
}
givenCloud, givenRegion, _ := jujucloud.SplitHostCloudRegion(given)
detectedCloud, detectedRegion, _ := jujucloud.SplitHostCloudRegion(detected)
if givenCloud != "" && givenCloud != detectedCloud {
if givenRegion != "" || givenCloud != detectedRegion {
// If givenRegion is empty, then givenCloud may be a region.
// Check that it is not a region.
return errors.Errorf("given cloud %q was different to the detected cloud %q: re-run the command without specifying the cloud", givenCloud, detectedCloud)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/given/specified I think

}
}
if givenRegion != "" && givenRegion != detectedRegion {
return errors.Errorf("given region %q was different to the detected region %q: re-run the command without specifying the region", givenRegion, detectedRegion)
}
return nil
}

func (c *AddCAASCommand) newK8sClusterBroker(cloud jujucloud.Cloud, credential jujucloud.Credential) (caas.ClusterMetadataChecker, error) {
openParams, err := provider.BaseKubeCloudOpenParams(cloud, credential)
if err != nil {
return nil, errors.Trace(err)
}
if c.controllerName != "" {
if !c.Local {
ctrlUUID, err := c.ControllerUUID(c.store, c.controllerName)
if err != nil {
return nil, errors.Trace(err)
Expand Down
26 changes: 26 additions & 0 deletions cmd/juju/caas/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,32 @@ func (s *addCAASSuite) TestAddGkeCluster(c *gc.C) {
s.assertStoreClouds(c, "gce/us-east1")
}

func (s *addCAASSuite) TestGivenCloudMatch(c *gc.C) {
err := caas.CheckCloudRegion("gce", "gce/us-east1")
c.Assert(err, jc.ErrorIsNil)
}

func (s *addCAASSuite) TestGivenCloudMismatch(c *gc.C) {
err := caas.CheckCloudRegion("maas", "gce")
c.Assert(err, gc.ErrorMatches, `given cloud "maas" was different to the detected cloud "gce": re-run the command without specifying the cloud`)

err = caas.CheckCloudRegion("maas", "gce/us-east1")
c.Assert(err, gc.ErrorMatches, `given cloud "maas" was different to the detected cloud "gce": re-run the command without specifying the cloud`)
}

func (s *addCAASSuite) TestGivenRegionMatch(c *gc.C) {
err := caas.CheckCloudRegion("/us-east1", "gce/us-east1")
c.Assert(err, jc.ErrorIsNil)

err = caas.CheckCloudRegion("us-east1", "gce/us-east1")
c.Assert(err, jc.ErrorIsNil)
}

func (s *addCAASSuite) TestGivenRegionMismatch(c *gc.C) {
err := caas.CheckCloudRegion("gce/us-east1", "gce/us-east10")
c.Assert(err, gc.ErrorMatches, `given region "us-east1" was different to the detected region "us-east10": re-run the command without specifying the region`)
}

func (s *addCAASSuite) assertStoreClouds(c *gc.C, hostCloud string) {
s.cloudMetadataStore.CheckCall(c, 2, "WritePersonalCloudMetadata",
map[string]cloud.Cloud{
Expand Down
4 changes: 4 additions & 0 deletions cmd/juju/caas/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,7 @@ func (f *fakeCluster) ensureExecutable() error {
func FakeCluster(config string) k8sCluster {
return &fakeCluster{config: config, cloudType: "gce"}
}

var (
CheckCloudRegion = checkCloudRegion
)
5 changes: 1 addition & 4 deletions cmd/juju/controller/addmodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ are the ones used to create any future resources within the model.
If no cloud/region is specified, then the model will be deployed to
the same cloud/region as the controller model. If a region is specified
without a cloud qualifier, then it is assumed to be in the same cloud
as the controller model. It is not currently possible for a controller
to manage multiple clouds, so the only valid cloud is the same cloud
as the controller model is deployed to. This may change in a future
release.
as the controller model.

Examples:

Expand Down