Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/dev/gp-gcloud/cmd/compute/instance-templates-create.go
2501 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package compute
6
7
import (
8
"io/ioutil"
9
"os"
10
"strconv"
11
"strings"
12
13
"github.com/gitpod-io/gitpod/common-go/log"
14
15
"encoding/json"
16
17
"github.com/spf13/cobra"
18
"golang.org/x/net/context"
19
"golang.org/x/oauth2/google"
20
"google.golang.org/api/compute/v1"
21
)
22
23
type LocalSSD struct {
24
DeviceName string
25
Interface string
26
}
27
28
func newInstanceTemplatesCreateCommand() *cobra.Command {
29
var (
30
region string
31
project string
32
machineType string
33
minCpuPlatform string
34
serviceAccount string
35
serviceAccountScopes string
36
bootDiskSize string
37
bootDiskType string
38
tags string
39
labels string
40
image string
41
networkInterface string
42
metadata string
43
metadataFromFile string
44
localSSDs []string
45
noRestartOnFailure bool
46
dryRun bool
47
)
48
49
cmd := &cobra.Command{
50
Use: "create",
51
Short: "create wrapper",
52
Example: " create instance-template-1",
53
Run: func(cmd *cobra.Command, args []string) {
54
if len(args) != 1 {
55
log.Fatal("no name was provided")
56
}
57
58
name := args[0]
59
60
ctx := context.Background()
61
62
c, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
63
if err != nil {
64
log.Fatal(err)
65
}
66
67
computeService, err := compute.New(c)
68
if err != nil {
69
log.Fatal(err)
70
}
71
72
networkInterfaceParsed := parseLabels(networkInterface)
73
automaticRestart := !noRestartOnFailure
74
metadata := parseMetadata(metadata)
75
metadata = append(metadata, parseMetadataFromFile(metadataFromFile)...)
76
localSSDDevices := parseLocalSSDs(localSSDs)
77
labels := parseLabels(labels)
78
79
// source: https://cloud.google.com/sdk/gcloud/reference/beta/compute/instances/set-scopes
80
scopesMapping := map[string][]string{
81
"bigquery": {"https://www.googleapis.com/auth/bigquery"},
82
"cloud-platform": {"https://www.googleapis.com/auth/cloud-platform"},
83
"cloud-source-repos": {"https://www.googleapis.com/auth/source.full_control"},
84
"cloud-source-repos-ro": {"https://www.googleapis.com/auth/source.read_only"},
85
"compute-ro": {"https://www.googleapis.com/auth/compute.readonly"},
86
"compute-rw": {"https://www.googleapis.com/auth/compute"},
87
"datastore": {"https://www.googleapis.com/auth/datastore"},
88
"default": {"https://www.googleapis.com/auth/devstorage.read_only",
89
"https://www.googleapis.com/auth/logging.write",
90
"https://www.googleapis.com/auth/monitoring.write",
91
"https://www.googleapis.com/auth/pubsub",
92
"https://www.googleapis.com/auth/service.management.readonly",
93
"https://www.googleapis.com/auth/servicecontrol",
94
"https://www.googleapis.com/auth/trace.append"},
95
"gke-default": {"https://www.googleapis.com/auth/devstorage.read_only",
96
"https://www.googleapis.com/auth/logging.write",
97
"https://www.googleapis.com/auth/monitoring",
98
"https://www.googleapis.com/auth/service.management.readonly",
99
"https://www.googleapis.com/auth/servicecontrol",
100
"https://www.googleapis.com/auth/trace.append"},
101
"logging-write": {"https://www.googleapis.com/auth/logging.write"},
102
"monitoring": {"https://www.googleapis.com/auth/monitoring"},
103
"monitoring-read": {"https://www.googleapis.com/auth/monitoring.read"},
104
"monitoring-write": {"https://www.googleapis.com/auth/monitoring.write"},
105
"pubsub": {"https://www.googleapis.com/auth/pubsub"},
106
"service-control": {"https://www.googleapis.com/auth/servicecontrol"},
107
"service-management": {"https://www.googleapis.com/auth/service.management.readonly"},
108
"sql-admin": {"https://www.googleapis.com/auth/sqlservice.admin"},
109
"storage-full": {"https://www.googleapis.com/auth/devstorage.full_control"},
110
"storage-ro": {"https://www.googleapis.com/auth/devstorage.read_only"},
111
"storage-rw": {"https://www.googleapis.com/auth/devstorage.read_write"},
112
"taskqueue": {"https://www.googleapis.com/auth/taskqueue"},
113
"trace": {"https://www.googleapis.com/auth/trace.append"},
114
"userinfo-email": {"https://www.googleapis.com/auth/userinfo.email"},
115
}
116
// map serviceAccountScopes to scopesMapping
117
scopes := []string{}
118
for _, scope := range strings.Split(serviceAccountScopes, ",") {
119
scopes = append(scopes, scopesMapping[scope]...)
120
}
121
122
rb := &compute.InstanceTemplate{
123
Name: name,
124
Properties: &compute.InstanceProperties{
125
MachineType: machineType,
126
MinCpuPlatform: minCpuPlatform,
127
ServiceAccounts: []*compute.ServiceAccount{
128
{
129
Email: serviceAccount,
130
Scopes: scopes,
131
},
132
},
133
Disks: []*compute.AttachedDisk{
134
{
135
AutoDelete: true,
136
Boot: true,
137
InitializeParams: &compute.AttachedDiskInitializeParams{
138
DiskSizeGb: parseDiskSize(bootDiskSize, 10),
139
DiskType: bootDiskType,
140
SourceImage: image,
141
Labels: labels,
142
},
143
},
144
},
145
Tags: &compute.Tags{
146
Items: strings.Split(tags, ","),
147
},
148
Labels: labels,
149
NetworkInterfaces: []*compute.NetworkInterface{
150
{
151
AccessConfigs: []*compute.AccessConfig{
152
{
153
NetworkTier: networkInterfaceParsed["network-tier"],
154
Type: "ONE_TO_ONE_NAT",
155
},
156
},
157
Network: networkInterfaceParsed["network"],
158
Subnetwork: networkInterfaceParsed["subnet"],
159
},
160
},
161
Metadata: &compute.Metadata{
162
Items: metadata,
163
},
164
Scheduling: &compute.Scheduling{
165
AutomaticRestart: &automaticRestart,
166
},
167
},
168
}
169
170
for _, localSSD := range localSSDDevices {
171
rb.Properties.Disks = append(rb.Properties.Disks, &compute.AttachedDisk{
172
AutoDelete: true,
173
Boot: false,
174
InitializeParams: &compute.AttachedDiskInitializeParams{
175
DiskType: "local-ssd",
176
// local ssd non persistent disks do not support labels
177
//Labels: labels,
178
},
179
Type: "SCRATCH",
180
Interface: localSSD.Interface,
181
})
182
}
183
184
if dryRun {
185
json, _ := json.MarshalIndent(rb, "", " ")
186
log.Infof("dry run: %s", string(json))
187
return
188
}
189
190
resp, err := computeService.InstanceTemplates.Insert(project, rb).Context(ctx).Do()
191
if err != nil {
192
log.Fatal(err)
193
}
194
195
if resp.Error != nil {
196
log.Fatal(resp.Error)
197
}
198
if resp.HttpErrorMessage != "" {
199
log.Fatal(resp.HttpErrorMessage)
200
}
201
202
log.Infof("created instance template %s", name)
203
},
204
}
205
206
cmd.Flags().StringVar(&region, "region", "", "gcp region")
207
cmd.MarkFlagRequired("region")
208
cmd.Flags().StringVar(&project, "project", "", "gcp project")
209
cmd.MarkFlagRequired("project")
210
cmd.Flags().StringVar(&machineType, "machine-type", "", "gcp machine type")
211
cmd.MarkFlagRequired("machine-type")
212
cmd.Flags().StringVar(&serviceAccount, "service-account", "", "gcp service account")
213
cmd.MarkFlagRequired("service-account")
214
cmd.Flags().StringVar(&serviceAccountScopes, "scopes", "", "gcp service account scopes")
215
cmd.MarkFlagRequired("scopes")
216
cmd.Flags().StringVar(&image, "image", "", "gcp image")
217
cmd.MarkFlagRequired("image")
218
cmd.Flags().StringVar(&networkInterface, "network-interface", "", "gcp network interface")
219
cmd.MarkFlagRequired("network-interface")
220
221
cmd.Flags().StringVar(&minCpuPlatform, "min-cpu-platform", "", "gcp min cpu platform")
222
cmd.Flags().StringVar(&bootDiskSize, "boot-disk-size", "10GB", "gcp boot disk size")
223
cmd.Flags().StringVar(&bootDiskType, "boot-disk-type", "pd-standard", "gcp boot disk type")
224
cmd.Flags().StringVar(&tags, "tags", "", "gcp tags")
225
cmd.Flags().StringVar(&labels, "labels", "", "gcp labels")
226
cmd.Flags().StringVar(&metadata, "metadata", "", "gcp metadata")
227
cmd.Flags().StringVar(&metadataFromFile, "metadata-from-file", "", "gcp metadata from file")
228
cmd.Flags().StringArrayVar(&localSSDs, "local-ssd", []string{}, "gcp local ssd")
229
cmd.Flags().BoolVar(&noRestartOnFailure, "no-restart-on-failure", false, "gcp no restart on failure")
230
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "gcp dry run")
231
232
return cmd
233
}
234
235
// simple disk size parser (gcloud accepts sizes other then GB but for our case GB is enough for now)
236
func parseDiskSize(s string, defaultSize int64) int64 {
237
if s == "" {
238
return defaultSize
239
}
240
if !strings.HasSuffix(s, "GB") {
241
log.Fatal("disk size must be in GB")
242
}
243
s = strings.TrimSuffix(s, "GB")
244
i, err := strconv.ParseInt(s, 10, 64)
245
if err != nil {
246
log.Fatalf("failed to parse disk size: %s", err)
247
}
248
return i
249
}
250
251
func parseLabels(s string) map[string]string {
252
if s == "" {
253
return nil
254
}
255
m := make(map[string]string)
256
for _, l := range strings.Split(s, ",") {
257
parts := strings.Split(l, "=")
258
if len(parts) != 2 {
259
log.Fatal("label must be in format key=value")
260
}
261
m[parts[0]] = parts[1]
262
}
263
return m
264
}
265
266
func parseMetadata(s string) []*compute.MetadataItems {
267
if s == "" {
268
return nil
269
}
270
m := make([]*compute.MetadataItems, 0)
271
for _, l := range strings.Split(s, ",") {
272
key, value, found := strings.Cut(l, "=")
273
if !found {
274
log.Fatalf("metadata must be in format key=value, got: %s", l)
275
}
276
m = append(m, &compute.MetadataItems{
277
Key: key,
278
Value: &value,
279
})
280
}
281
return m
282
}
283
284
func parseMetadataFromFile(s string) []*compute.MetadataItems {
285
if s == "" {
286
return nil
287
}
288
m := make([]*compute.MetadataItems, 0)
289
for _, l := range strings.Split(s, ",") {
290
key, value, found := strings.Cut(l, "=")
291
if !found {
292
log.Fatalf("metadata must be in format key=value, got: %s", l)
293
}
294
// open file from value
295
file, err := os.Open(value)
296
if err != nil {
297
log.Fatalf("failed to open file: %s", err)
298
}
299
defer file.Close()
300
// read file content
301
content, err := ioutil.ReadAll(file)
302
if err != nil {
303
log.Fatalf("failed to read file: %s", err)
304
}
305
file_content := string(content)
306
m = append(m, &compute.MetadataItems{
307
Key: key,
308
Value: &file_content,
309
})
310
}
311
return m
312
}
313
314
func parseLocalSSDs(s []string) []*LocalSSD {
315
if len(s) == 0 {
316
return nil
317
}
318
m := make([]*LocalSSD, 0)
319
for i, l := range s {
320
params := strings.Split(l, ",")
321
deviceName := "local-ssd-" + strconv.Itoa(i)
322
interfaceName := "SCSI"
323
for _, p := range params {
324
parts := strings.Split(p, "=")
325
if len(parts) != 2 {
326
log.Fatal("local ssd params must be in format key=value")
327
}
328
if parts[0] != "device-name" && parts[0] != "interface" {
329
log.Fatal("local ssd params must be in format device-name=value or interface=value")
330
}
331
if parts[0] == "device-name" {
332
deviceName = parts[1]
333
}
334
if parts[0] == "interface" {
335
interfaceName = parts[1]
336
}
337
}
338
m = append(m, &LocalSSD{
339
DeviceName: deviceName,
340
Interface: interfaceName,
341
})
342
}
343
return m
344
}
345
346