cli_tools/common/utils/param/param_utils.go (126 lines of code) (raw):
// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package param
import (
"context"
"fmt"
"regexp"
"strings"
daisy "github.com/GoogleCloudPlatform/compute-daisy"
daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute"
"google.golang.org/api/option"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/domain"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/paramhelper"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/storage"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/validation"
)
// GetProjectID gets project id from flag if exists; otherwise, try to retrieve from GCE metadata.
func GetProjectID(mgce domain.MetadataGCEInterface, projectFlag string) (string, error) {
if projectFlag == "" {
if !mgce.OnGCE() {
return "", daisy.Errf("project cannot be determined because build is not running on GCE")
}
aProject, err := mgce.ProjectID()
if err != nil || aProject == "" {
return "", daisy.Errf("project cannot be determined %v", err)
}
return aProject, nil
}
return projectFlag, nil
}
// populateScratchBucketGcsPath validates the scratch bucket, creating a new one if not
// provided, and returns the region of the scratch bucket. If the scratch bucket is
// already populated, and the owning project doesn't match `project`, then an error is returned.
// In that case, if `file` resides in the non-owned scratch bucket and `removeFileWhenScratchNotOwned`
// is specified, then `file` is deleted from GCS.
func populateScratchBucketGcsPath(scratchBucketGcsPath *string, zone string, mgce domain.MetadataGCEInterface,
scratchBucketCreator domain.ScratchBucketCreatorInterface, file string, project *string,
storageClient domain.StorageClientInterface, removeFileWhenScratchNotOwned bool) (string, error) {
scratchBucketRegion := ""
if *scratchBucketGcsPath == "" {
fallbackZone := zone
if fallbackZone == "" && mgce.OnGCE() {
var err error
if fallbackZone, err = mgce.Zone(); err != nil {
// reset fallback zone if failed to get zone from running GCE
fallbackZone = ""
}
}
scratchBucketName, sbr, err := scratchBucketCreator.CreateScratchBucket(file, *project, fallbackZone)
scratchBucketRegion = sbr
if err != nil {
return "", daisy.Errf("failed to create scratch bucket: %v", err)
}
*scratchBucketGcsPath = fmt.Sprintf("gs://%v/", scratchBucketName)
} else {
scratchBucketName, err := storage.GetBucketNameFromGCSPath(*scratchBucketGcsPath)
if err != nil {
return "", daisy.Errf("invalid scratch bucket GCS path %v", scratchBucketGcsPath)
}
if !scratchBucketCreator.IsBucketInProject(*project, scratchBucketName) {
anonymizedErrorMessage := "Scratch bucket %q is not in project %q"
substitutions := []interface{}{scratchBucketName, *project}
if removeFileWhenScratchNotOwned && strings.HasPrefix(file, fmt.Sprintf("gs://%s/", scratchBucketName)) {
err := storageClient.DeleteObject(file)
if err == nil {
anonymizedErrorMessage += ". Deleted %q"
substitutions = append(substitutions, file)
} else {
anonymizedErrorMessage += ". Failed to delete %q: %v. " +
"Check with the owner of gs://%q for more information"
substitutions = append(substitutions, file, err, scratchBucketName)
}
}
return "", daisy.Errf(anonymizedErrorMessage, substitutions...)
}
scratchBucketAttrs, err := storageClient.GetBucketAttrs(scratchBucketName)
if err == nil {
scratchBucketRegion = scratchBucketAttrs.Location
}
}
return scratchBucketRegion, nil
}
// PopulateProjectIfMissing populates project id for cli tools
func PopulateProjectIfMissing(mgce domain.MetadataGCEInterface, projectFlag *string) error {
var err error
*projectFlag, err = GetProjectID(mgce, *projectFlag)
return err
}
// PopulateRegion populates region based on the value extracted from zone param
func PopulateRegion(region *string, zone string) error {
aRegion, err := paramhelper.GetRegion(zone)
if err != nil {
return err
}
*region = aRegion
return nil
}
// CreateComputeClient creates a new compute client
func CreateComputeClient(ctx *context.Context, oauth string, ce string) (daisyCompute.Client, error) {
computeOptions := []option.ClientOption{option.WithCredentialsFile(oauth)}
if ce != "" {
computeOptions = append(computeOptions, option.WithEndpoint(ce))
}
computeClient, err := daisyCompute.NewClient(*ctx, computeOptions...)
if err != nil {
return nil, daisy.Errf("failed to create compute client: %v", err)
}
return computeClient, nil
}
var fullResourceURLPrefix = "https://www.googleapis.com/compute/[^/]*/"
var fullResourceURLRegex = regexp.MustCompile(fmt.Sprintf("^(%s)", fullResourceURLPrefix))
func getResourcePath(scope string, resourceType string, resourceName string) string {
// handle full URL: transform to relative URL
if prefix := fullResourceURLRegex.FindString(resourceName); prefix != "" {
return strings.TrimPrefix(resourceName, prefix)
}
// handle relative (partial) URL: use it as-is
if strings.Contains(resourceName, "/") {
return resourceName
}
// handle pure name: treat it as current project
return fmt.Sprintf("%v/%v/%v", scope, resourceType, resourceName)
}
// GetImageResourcePath gets the resource path for an image. It will panic if either
// projectID or imageName is invalid. To avoid panic, pre-validate using the
// functions in the `validation` package.
func GetImageResourcePath(projectID, imageName string) string {
if err := validation.ValidateImageName(imageName); err != nil {
panic(fmt.Sprintf("Invalid image name %q: %v", imageName, err))
}
if err := validation.ValidateProjectID(projectID); err != nil {
panic(fmt.Sprintf("Invalid projectID %q: %v", projectID, err))
}
return fmt.Sprintf("projects/%s/global/images/%s", projectID, imageName)
}
// GetGlobalResourcePath gets global resource path based on either a local resource name or a path
func GetGlobalResourcePath(resourceType string, resourceName string) string {
return getResourcePath("global", resourceType, resourceName)
}
// GetRegionalResourcePath gets regional resource path based on either a local resource name or a path
func GetRegionalResourcePath(region string, resourceType string, resourceName string) string {
return getResourcePath(fmt.Sprintf("regions/%v", region), resourceType, resourceName)
}
// GetZonalResourcePath gets zonal resource path based on either a local resource name or a path
func GetZonalResourcePath(zone string, resourceType string, resourceName string) string {
return getResourcePath(fmt.Sprintf("zones/%v", zone), resourceType, resourceName)
}