-
Notifications
You must be signed in to change notification settings - Fork 145
/
param_utils.go
184 lines (158 loc) · 7.09 KB
/
param_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// 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)
}