Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions pkg/asset/installconfig/gcp/.mock/gcpclient_generated.go

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

34 changes: 34 additions & 0 deletions pkg/asset/installconfig/gcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/pkg/errors"
"google.golang.org/api/cloudresourcemanager/v1"
compute "google.golang.org/api/compute/v1"
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/option"
Expand All @@ -20,6 +21,7 @@ type API interface {
GetPublicDomains(ctx context.Context, project string) ([]string, error)
GetPublicDNSZone(ctx context.Context, baseDomain, project string) (*dns.ManagedZone, error)
GetSubnetworks(ctx context.Context, network, project, region string) ([]*compute.Subnetwork, error)
GetProjects(ctx context.Context) (map[string]string, error)
}

// Client makes calls to the GCP API.
Expand Down Expand Up @@ -151,3 +153,35 @@ func (c *Client) getDNSService(ctx context.Context) (*dns.Service, error) {
}
return svc, nil
}

// GetProjects gets the list of project names and ids associated with the current user in the form
// of a map whose keys are ids and values are names.
func (c *Client) GetProjects(ctx context.Context) (map[string]string, error) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()

svc, err := c.getCloudResourceService(ctx)
if err != nil {
return nil, err
}

req := svc.Projects.List()
projects := make(map[string]string)
if err := req.Pages(ctx, func(page *cloudresourcemanager.ListProjectsResponse) error {
for _, project := range page.Projects {
projects[project.ProjectId] = project.Name
}
return nil
}); err != nil {
return nil, err
}
return projects, nil
}

func (c *Client) getCloudResourceService(ctx context.Context) (*cloudresourcemanager.Service, error) {
svc, err := cloudresourcemanager.NewService(ctx, option.WithCredentials(c.ssn.Credentials))
if err != nil {
return nil, errors.Wrap(err, "failed to create cloud resource service")
}
return svc, nil
}
31 changes: 29 additions & 2 deletions pkg/asset/installconfig/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,43 @@ func selectProject(ctx context.Context) (string, error) {
}
defaultProject := ssn.Credentials.ProjectID

client := &Client{
ssn: ssn,
}

projects, err := client.GetProjects(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to get projects")
}

var options []string
ids := make(map[string]string)

var defaultValue string

for id, name := range projects {
option := fmt.Sprintf("%s (%s)", name, id)
ids[option] = id
if id == defaultProject {
defaultValue = option
}
options = append(options, option)
}
sort.Strings(options)

var selectedProject string
err = survey.Ask([]*survey.Question{
{
Prompt: &survey.Input{
Prompt: &survey.Select{
Message: "Project ID",
Help: "The project id where the cluster will be provisioned. The default is taken from the provided service account.",
Default: defaultProject,
Default: defaultValue,
Options: options,
},
},
}, &selectedProject)

selectedProject = ids[selectedProject]
return selectedProject, nil
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/asset/installconfig/gcp/mock/gcpclient_generated.go

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

17 changes: 17 additions & 0 deletions pkg/asset/installconfig/gcp/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,28 @@ import (
func Validate(client API, ic *types.InstallConfig) error {
allErrs := field.ErrorList{}

allErrs = append(allErrs, validateProject(client, ic, field.NewPath("platform").Child("gcp"))...)
allErrs = append(allErrs, validateNetworks(client, ic, field.NewPath("platform").Child("gcp"))...)

return allErrs.ToAggregate()
}

func validateProject(client API, ic *types.InstallConfig, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if ic.GCP.ProjectID != "" {
projects, err := client.GetProjects(context.TODO())
if err != nil {
return append(allErrs, field.InternalError(fieldPath.Child("project"), err))
}
if _, found := projects[ic.GCP.ProjectID]; !found {
return append(allErrs, field.Invalid(fieldPath.Child("project"), ic.GCP.ProjectID, "invalid project ID"))
}
}

return allErrs
}

// validateNetworks checks that the user-provided VPC is in the project and the provided subnets are valid.
func validateNetworks(client API, ic *types.InstallConfig, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
Expand Down
8 changes: 8 additions & 0 deletions pkg/asset/installconfig/gcp/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,19 @@ func TestGCPInstallConfigValidation(t *testing.T) {
expectedError: true,
expectedErrMsg: "network: Invalid value",
},
{
name: "Invalid project ID",
edits: editFunctions{invalidateProject, removeSubnets, removeVPC},
expectedError: true,
expectedErrMsg: "platform.gcp.project: Invalid value: \"invalid-project\": invalid project ID",
},
}
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

gcpClient := mock.NewMockAPI(mockCtrl)
// Should get the list of projects.
gcpClient.EXPECT().GetProjects(gomock.Any()).Return(map[string]string{"valid-project": "valid-project"}, nil).AnyTimes()
// When passed the correct network & project, return an empty network, which should be enough to validate ok.
gcpClient.EXPECT().GetNetwork(gomock.Any(), validNetworkName, validProjectName).Return(&compute.Network{}, nil).AnyTimes()

Expand Down