Path: blob/main/dev/gp-gcloud/cmd/compute/instance-templates-create.go
2501 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package compute56import (7"io/ioutil"8"os"9"strconv"10"strings"1112"github.com/gitpod-io/gitpod/common-go/log"1314"encoding/json"1516"github.com/spf13/cobra"17"golang.org/x/net/context"18"golang.org/x/oauth2/google"19"google.golang.org/api/compute/v1"20)2122type LocalSSD struct {23DeviceName string24Interface string25}2627func newInstanceTemplatesCreateCommand() *cobra.Command {28var (29region string30project string31machineType string32minCpuPlatform string33serviceAccount string34serviceAccountScopes string35bootDiskSize string36bootDiskType string37tags string38labels string39image string40networkInterface string41metadata string42metadataFromFile string43localSSDs []string44noRestartOnFailure bool45dryRun bool46)4748cmd := &cobra.Command{49Use: "create",50Short: "create wrapper",51Example: " create instance-template-1",52Run: func(cmd *cobra.Command, args []string) {53if len(args) != 1 {54log.Fatal("no name was provided")55}5657name := args[0]5859ctx := context.Background()6061c, err := google.DefaultClient(ctx, compute.CloudPlatformScope)62if err != nil {63log.Fatal(err)64}6566computeService, err := compute.New(c)67if err != nil {68log.Fatal(err)69}7071networkInterfaceParsed := parseLabels(networkInterface)72automaticRestart := !noRestartOnFailure73metadata := parseMetadata(metadata)74metadata = append(metadata, parseMetadataFromFile(metadataFromFile)...)75localSSDDevices := parseLocalSSDs(localSSDs)76labels := parseLabels(labels)7778// source: https://cloud.google.com/sdk/gcloud/reference/beta/compute/instances/set-scopes79scopesMapping := map[string][]string{80"bigquery": {"https://www.googleapis.com/auth/bigquery"},81"cloud-platform": {"https://www.googleapis.com/auth/cloud-platform"},82"cloud-source-repos": {"https://www.googleapis.com/auth/source.full_control"},83"cloud-source-repos-ro": {"https://www.googleapis.com/auth/source.read_only"},84"compute-ro": {"https://www.googleapis.com/auth/compute.readonly"},85"compute-rw": {"https://www.googleapis.com/auth/compute"},86"datastore": {"https://www.googleapis.com/auth/datastore"},87"default": {"https://www.googleapis.com/auth/devstorage.read_only",88"https://www.googleapis.com/auth/logging.write",89"https://www.googleapis.com/auth/monitoring.write",90"https://www.googleapis.com/auth/pubsub",91"https://www.googleapis.com/auth/service.management.readonly",92"https://www.googleapis.com/auth/servicecontrol",93"https://www.googleapis.com/auth/trace.append"},94"gke-default": {"https://www.googleapis.com/auth/devstorage.read_only",95"https://www.googleapis.com/auth/logging.write",96"https://www.googleapis.com/auth/monitoring",97"https://www.googleapis.com/auth/service.management.readonly",98"https://www.googleapis.com/auth/servicecontrol",99"https://www.googleapis.com/auth/trace.append"},100"logging-write": {"https://www.googleapis.com/auth/logging.write"},101"monitoring": {"https://www.googleapis.com/auth/monitoring"},102"monitoring-read": {"https://www.googleapis.com/auth/monitoring.read"},103"monitoring-write": {"https://www.googleapis.com/auth/monitoring.write"},104"pubsub": {"https://www.googleapis.com/auth/pubsub"},105"service-control": {"https://www.googleapis.com/auth/servicecontrol"},106"service-management": {"https://www.googleapis.com/auth/service.management.readonly"},107"sql-admin": {"https://www.googleapis.com/auth/sqlservice.admin"},108"storage-full": {"https://www.googleapis.com/auth/devstorage.full_control"},109"storage-ro": {"https://www.googleapis.com/auth/devstorage.read_only"},110"storage-rw": {"https://www.googleapis.com/auth/devstorage.read_write"},111"taskqueue": {"https://www.googleapis.com/auth/taskqueue"},112"trace": {"https://www.googleapis.com/auth/trace.append"},113"userinfo-email": {"https://www.googleapis.com/auth/userinfo.email"},114}115// map serviceAccountScopes to scopesMapping116scopes := []string{}117for _, scope := range strings.Split(serviceAccountScopes, ",") {118scopes = append(scopes, scopesMapping[scope]...)119}120121rb := &compute.InstanceTemplate{122Name: name,123Properties: &compute.InstanceProperties{124MachineType: machineType,125MinCpuPlatform: minCpuPlatform,126ServiceAccounts: []*compute.ServiceAccount{127{128Email: serviceAccount,129Scopes: scopes,130},131},132Disks: []*compute.AttachedDisk{133{134AutoDelete: true,135Boot: true,136InitializeParams: &compute.AttachedDiskInitializeParams{137DiskSizeGb: parseDiskSize(bootDiskSize, 10),138DiskType: bootDiskType,139SourceImage: image,140Labels: labels,141},142},143},144Tags: &compute.Tags{145Items: strings.Split(tags, ","),146},147Labels: labels,148NetworkInterfaces: []*compute.NetworkInterface{149{150AccessConfigs: []*compute.AccessConfig{151{152NetworkTier: networkInterfaceParsed["network-tier"],153Type: "ONE_TO_ONE_NAT",154},155},156Network: networkInterfaceParsed["network"],157Subnetwork: networkInterfaceParsed["subnet"],158},159},160Metadata: &compute.Metadata{161Items: metadata,162},163Scheduling: &compute.Scheduling{164AutomaticRestart: &automaticRestart,165},166},167}168169for _, localSSD := range localSSDDevices {170rb.Properties.Disks = append(rb.Properties.Disks, &compute.AttachedDisk{171AutoDelete: true,172Boot: false,173InitializeParams: &compute.AttachedDiskInitializeParams{174DiskType: "local-ssd",175// local ssd non persistent disks do not support labels176//Labels: labels,177},178Type: "SCRATCH",179Interface: localSSD.Interface,180})181}182183if dryRun {184json, _ := json.MarshalIndent(rb, "", " ")185log.Infof("dry run: %s", string(json))186return187}188189resp, err := computeService.InstanceTemplates.Insert(project, rb).Context(ctx).Do()190if err != nil {191log.Fatal(err)192}193194if resp.Error != nil {195log.Fatal(resp.Error)196}197if resp.HttpErrorMessage != "" {198log.Fatal(resp.HttpErrorMessage)199}200201log.Infof("created instance template %s", name)202},203}204205cmd.Flags().StringVar(®ion, "region", "", "gcp region")206cmd.MarkFlagRequired("region")207cmd.Flags().StringVar(&project, "project", "", "gcp project")208cmd.MarkFlagRequired("project")209cmd.Flags().StringVar(&machineType, "machine-type", "", "gcp machine type")210cmd.MarkFlagRequired("machine-type")211cmd.Flags().StringVar(&serviceAccount, "service-account", "", "gcp service account")212cmd.MarkFlagRequired("service-account")213cmd.Flags().StringVar(&serviceAccountScopes, "scopes", "", "gcp service account scopes")214cmd.MarkFlagRequired("scopes")215cmd.Flags().StringVar(&image, "image", "", "gcp image")216cmd.MarkFlagRequired("image")217cmd.Flags().StringVar(&networkInterface, "network-interface", "", "gcp network interface")218cmd.MarkFlagRequired("network-interface")219220cmd.Flags().StringVar(&minCpuPlatform, "min-cpu-platform", "", "gcp min cpu platform")221cmd.Flags().StringVar(&bootDiskSize, "boot-disk-size", "10GB", "gcp boot disk size")222cmd.Flags().StringVar(&bootDiskType, "boot-disk-type", "pd-standard", "gcp boot disk type")223cmd.Flags().StringVar(&tags, "tags", "", "gcp tags")224cmd.Flags().StringVar(&labels, "labels", "", "gcp labels")225cmd.Flags().StringVar(&metadata, "metadata", "", "gcp metadata")226cmd.Flags().StringVar(&metadataFromFile, "metadata-from-file", "", "gcp metadata from file")227cmd.Flags().StringArrayVar(&localSSDs, "local-ssd", []string{}, "gcp local ssd")228cmd.Flags().BoolVar(&noRestartOnFailure, "no-restart-on-failure", false, "gcp no restart on failure")229cmd.Flags().BoolVar(&dryRun, "dry-run", false, "gcp dry run")230231return cmd232}233234// simple disk size parser (gcloud accepts sizes other then GB but for our case GB is enough for now)235func parseDiskSize(s string, defaultSize int64) int64 {236if s == "" {237return defaultSize238}239if !strings.HasSuffix(s, "GB") {240log.Fatal("disk size must be in GB")241}242s = strings.TrimSuffix(s, "GB")243i, err := strconv.ParseInt(s, 10, 64)244if err != nil {245log.Fatalf("failed to parse disk size: %s", err)246}247return i248}249250func parseLabels(s string) map[string]string {251if s == "" {252return nil253}254m := make(map[string]string)255for _, l := range strings.Split(s, ",") {256parts := strings.Split(l, "=")257if len(parts) != 2 {258log.Fatal("label must be in format key=value")259}260m[parts[0]] = parts[1]261}262return m263}264265func parseMetadata(s string) []*compute.MetadataItems {266if s == "" {267return nil268}269m := make([]*compute.MetadataItems, 0)270for _, l := range strings.Split(s, ",") {271key, value, found := strings.Cut(l, "=")272if !found {273log.Fatalf("metadata must be in format key=value, got: %s", l)274}275m = append(m, &compute.MetadataItems{276Key: key,277Value: &value,278})279}280return m281}282283func parseMetadataFromFile(s string) []*compute.MetadataItems {284if s == "" {285return nil286}287m := make([]*compute.MetadataItems, 0)288for _, l := range strings.Split(s, ",") {289key, value, found := strings.Cut(l, "=")290if !found {291log.Fatalf("metadata must be in format key=value, got: %s", l)292}293// open file from value294file, err := os.Open(value)295if err != nil {296log.Fatalf("failed to open file: %s", err)297}298defer file.Close()299// read file content300content, err := ioutil.ReadAll(file)301if err != nil {302log.Fatalf("failed to read file: %s", err)303}304file_content := string(content)305m = append(m, &compute.MetadataItems{306Key: key,307Value: &file_content,308})309}310return m311}312313func parseLocalSSDs(s []string) []*LocalSSD {314if len(s) == 0 {315return nil316}317m := make([]*LocalSSD, 0)318for i, l := range s {319params := strings.Split(l, ",")320deviceName := "local-ssd-" + strconv.Itoa(i)321interfaceName := "SCSI"322for _, p := range params {323parts := strings.Split(p, "=")324if len(parts) != 2 {325log.Fatal("local ssd params must be in format key=value")326}327if parts[0] != "device-name" && parts[0] != "interface" {328log.Fatal("local ssd params must be in format device-name=value or interface=value")329}330if parts[0] == "device-name" {331deviceName = parts[1]332}333if parts[0] == "interface" {334interfaceName = parts[1]335}336}337m = append(m, &LocalSSD{338DeviceName: deviceName,339Interface: interfaceName,340})341}342return m343}344345346