github.com/abayer/test-infra@v0.0.5/kubetest/gke.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package main / gke.go provides the Google Container Engine (GKE) 18 // kubetest deployer via newGKE(). 19 // 20 // TODO(zmerlynn): Pull this out to a separate package? 21 package main 22 23 import ( 24 "encoding/json" 25 "flag" 26 "fmt" 27 "io/ioutil" 28 "log" 29 "os" 30 "os/exec" 31 "regexp" 32 "sort" 33 "strconv" 34 "strings" 35 "time" 36 37 "k8s.io/test-infra/kubetest/util" 38 ) 39 40 const ( 41 defaultPool = "default" 42 e2eAllow = "tcp:22,tcp:80,tcp:8080,tcp:30000-32767,udp:30000-32767" 43 defaultCreate = "container clusters create --quiet" 44 ) 45 46 var ( 47 gkeAdditionalZones = flag.String("gke-additional-zones", "", "(gke only) List of additional Google Compute Engine zones to use. Clusters are created symmetrically across zones by default, see --gke-shape for details.") 48 gkeNodeLocations = flag.String("gke-node-locations", "", "(gke only) List of Google Compute Engine zones to use.") 49 gkeEnvironment = flag.String("gke-environment", "", "(gke only) Container API endpoint to use, one of 'test', 'staging', 'prod', or a custom https:// URL") 50 gkeShape = flag.String("gke-shape", `{"default":{"Nodes":3,"MachineType":"n1-standard-2"}}`, `(gke only) A JSON description of node pools to create. The node pool 'default' is required and used for initial cluster creation. All node pools are symmetric across zones, so the cluster total node count is {total nodes in --gke-shape} * {1 + (length of --gke-additional-zones)}. Example: '{"default":{"Nodes":999,"MachineType:":"n1-standard-1"},"heapster":{"Nodes":1, "MachineType":"n1-standard-8"}}`) 51 gkeCreateArgs = flag.String("gke-create-args", "", "(gke only) (deprecated, use a modified --gke-create-command') Additional arguments passed directly to 'gcloud container clusters create'") 52 gkeCommandGroup = flag.String("gke-command-group", "", "(gke only) Use a different gcloud track (e.g. 'alpha') for all 'gcloud container' commands. Note: This is added to --gke-create-command on create. You should only use --gke-command-group if you need to change the gcloud track for *every* gcloud container command.") 53 gkeCreateCommand = flag.String("gke-create-command", defaultCreate, "(gke only) gcloud subcommand used to create a cluster. Modify if you need to pass arbitrary arguments to create.") 54 gkeCustomSubnet = flag.String("gke-custom-subnet", "", "(gke only) if specified, we create a custom subnet with the specified options and use it for the gke cluster. The format should be '<subnet-name> --region=<subnet-gcp-region> --range=<subnet-cidr> <any other optional params>'.") 55 gkeSingleZoneNodeInstanceGroup = flag.Bool("gke-single-zone-node-instance-group", true, "(gke only) Add instance groups from a single zone to the NODE_INSTANCE_GROUP env variable.") 56 57 // poolRe matches instance group URLs of the form `https://www.googleapis.com/compute/v1/projects/some-project/zones/a-zone/instanceGroupManagers/gke-some-cluster-some-pool-90fcb815-grp`. Match meaning: 58 // m[0]: path starting with zones/ 59 // m[1]: zone 60 // m[2]: pool name (passed to e2es) 61 // m[3]: unique hash (used as nonce for firewall rules) 62 poolRe = regexp.MustCompile(`zones/([^/]+)/instanceGroupManagers/(gke-.*-([0-9a-f]{8})-grp)$`) 63 64 urlRe = regexp.MustCompile(`https://.*/`) 65 ) 66 67 type gkeNodePool struct { 68 Nodes int 69 MachineType string 70 } 71 72 type gkeDeployer struct { 73 project string 74 zone string 75 region string 76 location string 77 additionalZones string 78 nodeLocations string 79 cluster string 80 shape map[string]gkeNodePool 81 network string 82 subnetwork string 83 subnetworkRegion string 84 image string 85 imageFamily string 86 imageProject string 87 commandGroup []string 88 createCommand []string 89 singleZoneNodeInstanceGroup bool 90 91 setup bool 92 kubecfg string 93 instanceGroups []*ig 94 } 95 96 type ig struct { 97 path string 98 zone string 99 name string 100 uniq string 101 } 102 103 var _ deployer = &gkeDeployer{} 104 105 func newGKE(provider, project, zone, region, network, image, imageFamily, imageProject, cluster string, testArgs *string, upgradeArgs *string) (*gkeDeployer, error) { 106 if provider != "gke" { 107 return nil, fmt.Errorf("--provider must be 'gke' for GKE deployment, found %q", provider) 108 } 109 g := &gkeDeployer{} 110 111 if cluster == "" { 112 return nil, fmt.Errorf("--cluster must be set for GKE deployment") 113 } 114 g.cluster = cluster 115 116 if project == "" { 117 return nil, fmt.Errorf("--gcp-project must be set for GKE deployment") 118 } 119 g.project = project 120 121 if zone == "" && region == "" { 122 return nil, fmt.Errorf("--gcp-zone or --gcp-region must be set for GKE deployment") 123 } else if zone != "" && region != "" { 124 return nil, fmt.Errorf("--gcp-zone and --gcp-region cannot both be set") 125 } 126 if zone != "" { 127 g.zone = zone 128 g.location = "--zone=" + zone 129 } else if region != "" { 130 g.region = region 131 g.location = "--region=" + region 132 } 133 134 if network == "" { 135 return nil, fmt.Errorf("--gcp-network must be set for GKE deployment") 136 } 137 g.network = network 138 139 if image == "" { 140 return nil, fmt.Errorf("--gcp-node-image must be set for GKE deployment") 141 } 142 if strings.ToUpper(image) == "CUSTOM" { 143 if imageFamily == "" || imageProject == "" { 144 return nil, fmt.Errorf("--image-family and --image-project must be set for GKE deployment if --gcp-node-image=CUSTOM") 145 } 146 } 147 g.imageFamily = imageFamily 148 g.imageProject = imageProject 149 g.image = image 150 151 g.additionalZones = *gkeAdditionalZones 152 g.nodeLocations = *gkeNodeLocations 153 154 err := json.Unmarshal([]byte(*gkeShape), &g.shape) 155 if err != nil { 156 return nil, fmt.Errorf("--gke-shape must be valid JSON, unmarshal error: %v, JSON: %q", err, *gkeShape) 157 } 158 if _, ok := g.shape[defaultPool]; !ok { 159 return nil, fmt.Errorf("--gke-shape must include a node pool named 'default', found %q", *gkeShape) 160 } 161 162 g.commandGroup = strings.Fields(*gkeCommandGroup) 163 164 g.createCommand = append([]string{}, g.commandGroup...) 165 g.createCommand = append(g.createCommand, strings.Fields(*gkeCreateCommand)...) 166 createArgs := strings.Fields(*gkeCreateArgs) 167 if len(createArgs) > 0 { 168 log.Printf("--gke-create-args is deprecated, please use '--gke-create-command=%s %s'", defaultCreate, *gkeCreateArgs) 169 } 170 g.createCommand = append(g.createCommand, createArgs...) 171 172 if err := util.MigrateOptions([]util.MigratedOption{{ 173 Env: "CLOUDSDK_API_ENDPOINT_OVERRIDES_CONTAINER", 174 Option: gkeEnvironment, 175 Name: "--gke-environment", 176 }}); err != nil { 177 return nil, err 178 } 179 180 var endpoint string 181 switch env := *gkeEnvironment; { 182 case env == "test": 183 endpoint = "https://test-container.sandbox.googleapis.com/" 184 case env == "staging": 185 endpoint = "https://staging-container.sandbox.googleapis.com/" 186 case env == "prod": 187 endpoint = "https://container.googleapis.com/" 188 case urlRe.MatchString(env): 189 endpoint = env 190 default: 191 return nil, fmt.Errorf("--gke-environment must be one of {test,staging,prod} or match %v, found %q", urlRe, env) 192 } 193 if err := os.Setenv("CLOUDSDK_API_ENDPOINT_OVERRIDES_CONTAINER", endpoint); err != nil { 194 return nil, err 195 } 196 197 // Override kubecfg to a temporary file rather than trashing the user's. 198 f, err := ioutil.TempFile("", "gke-kubecfg") 199 if err != nil { 200 return nil, err 201 } 202 defer f.Close() 203 kubecfg := f.Name() 204 if err := f.Chmod(0600); err != nil { 205 return nil, err 206 } 207 g.kubecfg = kubecfg 208 209 // We want no KUBERNETES_PROVIDER, but to set 210 // KUBERNETES_CONFORMANCE_PROVIDER and 211 // KUBERNETES_CONFORMANCE_TEST. This prevents ginkgo-e2e.sh from 212 // using the cluster/gke functions. 213 // 214 // We do this in the deployer constructor so that 215 // cluster/gce/list-resources.sh outputs the same provider for the 216 // extent of the binary. (It seems like it belongs in TestSetup, 217 // but that way leads to madness.) 218 // 219 // TODO(zmerlynn): This is gross. 220 if err := os.Unsetenv("KUBERNETES_PROVIDER"); err != nil { 221 return nil, err 222 } 223 if err := os.Setenv("KUBERNETES_CONFORMANCE_TEST", "yes"); err != nil { 224 return nil, err 225 } 226 if err := os.Setenv("KUBERNETES_CONFORMANCE_PROVIDER", "gke"); err != nil { 227 return nil, err 228 } 229 230 // TODO(zmerlynn): Another snafu of cluster/gke/list-resources.sh: 231 // Set KUBE_GCE_INSTANCE_PREFIX so that we don't accidentally pick 232 // up CLUSTER_NAME later. 233 if err := os.Setenv("KUBE_GCE_INSTANCE_PREFIX", "gke-"+g.cluster); err != nil { 234 return nil, err 235 } 236 237 // set --num-nodes flag for ginkgo, since NUM_NODES is not set for gke deployer. 238 numNodes := strconv.Itoa(g.shape[defaultPool].Nodes) 239 // testArgs can be empty, and we need to support this case 240 *testArgs = strings.Join(util.SetFieldDefault(strings.Fields(*testArgs), "--num-nodes", numNodes), " ") 241 242 if *upgradeArgs != "" { 243 // --upgrade-target will be passed to e2e upgrade framework to get a valid update version. 244 // See usage from https://github.com/kubernetes/kubernetes/blob/master/hack/get-build.sh for supported targets. 245 // Here we special case for gke-latest and will extract an actual valid gke version. 246 // - gke-latest will be resolved to the latest gke version, and 247 // - gke-latest-1.7 will be resolved to the latest 1.7 patch version supported on gke. 248 fields, val, exist := util.ExtractField(strings.Fields(*upgradeArgs), "--upgrade-target") 249 if exist { 250 if strings.HasPrefix(val, "gke-latest") { 251 releasePrefix := "" 252 if strings.HasPrefix(val, "gke-latest-") { 253 releasePrefix = strings.TrimPrefix(val, "gke-latest-") 254 } 255 if val, err = getLatestGKEVersion(project, zone, region, releasePrefix); err != nil { 256 return nil, fmt.Errorf("fail to get latest gke version : %v", err) 257 } 258 } 259 fields = util.SetFieldDefault(fields, "--upgrade-target", val) 260 } 261 *upgradeArgs = strings.Join(util.SetFieldDefault(fields, "--num-nodes", numNodes), " ") 262 } 263 264 g.singleZoneNodeInstanceGroup = *gkeSingleZoneNodeInstanceGroup 265 266 return g, nil 267 } 268 269 func (g *gkeDeployer) Up() error { 270 // Create network if it doesn't exist. 271 if control.NoOutput(exec.Command("gcloud", "compute", "networks", "describe", g.network, 272 "--project="+g.project, 273 "--format=value(name)")) != nil { 274 // Assume error implies non-existent. 275 log.Printf("Couldn't describe network '%s', assuming it doesn't exist and creating it", g.network) 276 if err := control.FinishRunning(exec.Command("gcloud", "compute", "networks", "create", g.network, 277 "--project="+g.project, 278 "--subnet-mode=auto")); err != nil { 279 return err 280 } 281 } 282 // Create a custom subnet in that network if it was asked for. 283 if *gkeCustomSubnet != "" { 284 customSubnetFields := strings.Fields(*gkeCustomSubnet) 285 createSubnetCommand := []string{"compute", "networks", "subnets", "create"} 286 createSubnetCommand = append(createSubnetCommand, "--project="+g.project, "--network="+g.network) 287 createSubnetCommand = append(createSubnetCommand, customSubnetFields...) 288 if err := control.FinishRunning(exec.Command("gcloud", createSubnetCommand...)); err != nil { 289 return err 290 } 291 g.subnetwork = customSubnetFields[0] 292 g.subnetworkRegion = customSubnetFields[1] 293 } 294 295 def := g.shape[defaultPool] 296 args := make([]string, len(g.createCommand)) 297 copy(args, g.createCommand) 298 args = append(args, 299 "--project="+g.project, 300 g.location, 301 "--machine-type="+def.MachineType, 302 "--image-type="+g.image, 303 "--num-nodes="+strconv.Itoa(def.Nodes), 304 "--network="+g.network, 305 ) 306 if strings.ToUpper(g.image) == "CUSTOM" { 307 args = append(args, "--image-family="+g.imageFamily) 308 args = append(args, "--image-project="+g.imageProject) 309 } 310 if g.subnetwork != "" { 311 args = append(args, "--subnetwork="+g.subnetwork) 312 } 313 if g.additionalZones != "" { 314 args = append(args, "--additional-zones="+g.additionalZones) 315 if err := os.Setenv("MULTIZONE", "true"); err != nil { 316 return fmt.Errorf("error setting MULTIZONE env variable: %v", err) 317 } 318 319 } 320 if g.nodeLocations != "" { 321 args = append(args, "--node-locations="+g.nodeLocations) 322 numNodeLocations := strings.Split(g.nodeLocations, ",") 323 if len(numNodeLocations) > 1 { 324 if err := os.Setenv("MULTIZONE", "true"); err != nil { 325 return fmt.Errorf("error setting MULTIZONE env variable: %v", err) 326 } 327 } 328 } 329 // TODO(zmerlynn): The version should be plumbed through Extract 330 // or a separate flag rather than magic env variables. 331 if v := os.Getenv("CLUSTER_API_VERSION"); v != "" { 332 args = append(args, "--cluster-version="+v) 333 } 334 args = append(args, g.cluster) 335 if err := control.FinishRunning(exec.Command("gcloud", args...)); err != nil { 336 return fmt.Errorf("error creating cluster: %v", err) 337 } 338 for poolName, pool := range g.shape { 339 if poolName == defaultPool { 340 continue 341 } 342 if err := control.FinishRunning(exec.Command("gcloud", g.containerArgs( 343 "node-pools", "create", poolName, 344 "--cluster="+g.cluster, 345 "--project="+g.project, 346 g.location, 347 "--machine-type="+pool.MachineType, 348 "--num-nodes="+strconv.Itoa(pool.Nodes))...)); err != nil { 349 return fmt.Errorf("error creating node pool %q: %v", poolName, err) 350 } 351 } 352 return nil 353 } 354 355 func (g *gkeDeployer) IsUp() error { 356 return isUp(g) 357 } 358 359 // DumpClusterLogs for GKE generates a small script that wraps 360 // log-dump.sh with the appropriate shell-fu to get the cluster 361 // dumped. 362 // 363 // TODO(zmerlynn): This whole path is really gross, but this seemed 364 // the least gross hack to get this done. 365 // 366 // TODO(shyamjvs): Make this work with multizonal and regional clusters. 367 func (g *gkeDeployer) DumpClusterLogs(localPath, gcsPath string) error { 368 // gkeLogDumpTemplate is a template of a shell script where 369 // - %[1]s is the project 370 // - %[2]s is the zone 371 // - %[3]s is a filter composed of the instance groups 372 // - %[4]s is the log-dump.sh command line 373 const gkeLogDumpTemplate = ` 374 function log_dump_custom_get_instances() { 375 if [[ $1 == "master" ]]; then 376 return 0 377 fi 378 379 gcloud compute instances list '--project=%[1]s' '--filter=%[4]s' '--format=get(name)' 380 } 381 export -f log_dump_custom_get_instances 382 # Set below vars that log-dump.sh expects in order to use scp with gcloud. 383 export PROJECT=%[1]s 384 export ZONE='%[2]s' 385 export KUBERNETES_PROVIDER=gke 386 export KUBE_NODE_OS_DISTRIBUTION='%[3]s' 387 %[5]s 388 ` 389 // Prevent an obvious injection. 390 if strings.Contains(localPath, "'") || strings.Contains(gcsPath, "'") { 391 return fmt.Errorf("%q or %q contain single quotes - nice try", localPath, gcsPath) 392 } 393 394 // Generate a slice of filters to be OR'd together below 395 if err := g.getInstanceGroups(); err != nil { 396 return err 397 } 398 var filters []string 399 for _, ig := range g.instanceGroups { 400 filters = append(filters, fmt.Sprintf("(metadata.created-by:*%s)", ig.path)) 401 } 402 403 // Generate the log-dump.sh command-line 404 var dumpCmd string 405 if gcsPath == "" { 406 dumpCmd = fmt.Sprintf("./cluster/log-dump/log-dump.sh '%s'", localPath) 407 } else { 408 dumpCmd = fmt.Sprintf("./cluster/log-dump/log-dump.sh '%s' '%s'", localPath, gcsPath) 409 } 410 return control.FinishRunning(exec.Command("bash", "-c", fmt.Sprintf(gkeLogDumpTemplate, 411 g.project, 412 g.zone, 413 os.Getenv("NODE_OS_DISTRIBUTION"), 414 strings.Join(filters, " OR "), 415 dumpCmd))) 416 } 417 418 func (g *gkeDeployer) TestSetup() error { 419 if g.setup { 420 // Ensure setup is a singleton. 421 return nil 422 } 423 if err := g.getKubeConfig(); err != nil { 424 return err 425 } 426 if err := g.getInstanceGroups(); err != nil { 427 return err 428 } 429 if err := g.ensureFirewall(); err != nil { 430 return err 431 } 432 if err := g.setupEnv(); err != nil { 433 return err 434 } 435 g.setup = true 436 return nil 437 } 438 439 func (g *gkeDeployer) getKubeConfig() error { 440 info, err := os.Stat(g.kubecfg) 441 if err != nil { 442 return err 443 } 444 if info.Size() > 0 { 445 // Assume that if we already have it, it's good. 446 return nil 447 } 448 if err := os.Setenv("KUBECONFIG", g.kubecfg); err != nil { 449 return err 450 } 451 if err := control.FinishRunning(exec.Command("gcloud", g.containerArgs("clusters", "get-credentials", g.cluster, 452 "--project="+g.project, 453 g.location)...)); err != nil { 454 return fmt.Errorf("error executing get-credentials: %v", err) 455 } 456 return nil 457 } 458 459 // setupEnv is to appease ginkgo-e2e.sh and other pieces of the e2e infrastructure. It 460 // would be nice to handle this elsewhere, and not with env 461 // variables. c.f. kubernetes/test-infra#3330. 462 func (g *gkeDeployer) setupEnv() error { 463 // If singleZoneNodeInstanceGroup is true, set NODE_INSTANCE_GROUP to the 464 // names of instance groups that are in the same zone as the lexically first 465 // instance group. Otherwise set NODE_INSTANCE_GROUP to the names of all 466 // instance groups. 467 var filt []string 468 zone := g.instanceGroups[0].zone 469 for _, ig := range g.instanceGroups { 470 if !g.singleZoneNodeInstanceGroup || ig.zone == zone { 471 filt = append(filt, ig.name) 472 } 473 } 474 if err := os.Setenv("NODE_INSTANCE_GROUP", strings.Join(filt, ",")); err != nil { 475 return fmt.Errorf("error setting NODE_INSTANCE_GROUP: %v", err) 476 } 477 return nil 478 } 479 480 func (g *gkeDeployer) ensureFirewall() error { 481 firewall, err := g.getClusterFirewall() 482 if err != nil { 483 return fmt.Errorf("error getting unique firewall: %v", err) 484 } 485 if control.NoOutput(exec.Command("gcloud", "compute", "firewall-rules", "describe", firewall, 486 "--project="+g.project, 487 "--format=value(name)")) == nil { 488 // Assume that if this unique firewall exists, it's good to go. 489 return nil 490 } 491 log.Printf("Couldn't describe firewall '%s', assuming it doesn't exist and creating it", firewall) 492 493 tagOut, err := exec.Command("gcloud", "compute", "instances", "list", 494 "--project="+g.project, 495 "--filter=metadata.created-by:*"+g.instanceGroups[0].path, 496 "--limit=1", 497 "--format=get(tags.items)").Output() 498 if err != nil { 499 return fmt.Errorf("instances list failed: %s", util.ExecError(err)) 500 } 501 tag := strings.TrimSpace(string(tagOut)) 502 if tag == "" { 503 return fmt.Errorf("instances list returned no instances (or instance has no tags)") 504 } 505 506 if err := control.FinishRunning(exec.Command("gcloud", "compute", "firewall-rules", "create", firewall, 507 "--project="+g.project, 508 "--network="+g.network, 509 "--allow="+e2eAllow, 510 "--target-tags="+tag)); err != nil { 511 return fmt.Errorf("error creating e2e firewall: %v", err) 512 } 513 return nil 514 } 515 516 func (g *gkeDeployer) getInstanceGroups() error { 517 if len(g.instanceGroups) > 0 { 518 return nil 519 } 520 igs, err := exec.Command("gcloud", g.containerArgs("clusters", "describe", g.cluster, 521 "--format=value(instanceGroupUrls)", 522 "--project="+g.project, 523 g.location)...).Output() 524 if err != nil { 525 return fmt.Errorf("instance group URL fetch failed: %s", util.ExecError(err)) 526 } 527 igURLs := strings.Split(strings.TrimSpace(string(igs)), ";") 528 if len(igURLs) == 0 { 529 return fmt.Errorf("no instance group URLs returned by gcloud, output %q", string(igs)) 530 } 531 sort.Strings(igURLs) 532 for _, igURL := range igURLs { 533 m := poolRe.FindStringSubmatch(igURL) 534 if len(m) == 0 { 535 return fmt.Errorf("instanceGroupUrl %q did not match regex %v", igURL, poolRe) 536 } 537 g.instanceGroups = append(g.instanceGroups, &ig{path: m[0], zone: m[1], name: m[2], uniq: m[3]}) 538 } 539 return nil 540 } 541 542 func (g *gkeDeployer) getClusterFirewall() (string, error) { 543 if err := g.getInstanceGroups(); err != nil { 544 return "", err 545 } 546 // We want to ensure that there's an e2e-ports-* firewall rule 547 // that maps to the cluster nodes, but the target tag for the 548 // nodes can be slow to get. Use the hash from the lexically first 549 // node pool instead. 550 return "e2e-ports-" + g.instanceGroups[0].uniq, nil 551 } 552 553 // This function ensures that all firewall-rules are deleted from specific network. 554 // We also want to keep in logs that there were some resources leaking. 555 func (g *gkeDeployer) cleanupNetworkFirewalls() (int, error) { 556 fws, err := exec.Command("gcloud", "compute", "firewall-rules", "list", 557 "--format=value(name)", 558 "--project="+g.project, 559 "--filter=network:"+g.network).Output() 560 if err != nil { 561 return 0, fmt.Errorf("firewall rules list failed: %s", util.ExecError(err)) 562 } 563 if len(fws) > 0 { 564 fwList := strings.Split(strings.TrimSpace(string(fws)), "\n") 565 log.Printf("Network %s has %v undeleted firewall rules %v", g.network, len(fwList), fwList) 566 commandArgs := []string{"compute", "firewall-rules", "delete", "-q"} 567 commandArgs = append(commandArgs, fwList...) 568 commandArgs = append(commandArgs, "--project="+g.project) 569 errFirewall := control.FinishRunning(exec.Command("gcloud", commandArgs...)) 570 if errFirewall != nil { 571 return 0, fmt.Errorf("error deleting firewall: %v", errFirewall) 572 } 573 return len(fwList), nil 574 } 575 return 0, nil 576 } 577 578 func (g *gkeDeployer) Down() error { 579 firewall, err := g.getClusterFirewall() 580 if err != nil { 581 // This is expected if the cluster doesn't exist. 582 return nil 583 } 584 g.instanceGroups = nil 585 586 // We best-effort try all of these and report errors as appropriate. 587 errCluster := control.FinishRunning(exec.Command( 588 "gcloud", g.containerArgs("clusters", "delete", "-q", g.cluster, 589 "--project="+g.project, 590 g.location)...)) 591 var errFirewall error 592 if control.NoOutput(exec.Command("gcloud", "compute", "firewall-rules", "describe", firewall, 593 "--project="+g.project, 594 "--format=value(name)")) == nil { 595 log.Printf("Found rules for firewall '%s', deleting them", firewall) 596 errFirewall = control.FinishRunning(exec.Command("gcloud", "compute", "firewall-rules", "delete", "-q", firewall, 597 "--project="+g.project)) 598 } else { 599 log.Printf("Found no rules for firewall '%s', assuming resources are clean", firewall) 600 } 601 numLeakedFWRules, errCleanFirewalls := g.cleanupNetworkFirewalls() 602 var errSubnet error 603 if g.subnetwork != "" { 604 errSubnet = control.FinishRunning(exec.Command("gcloud", "compute", "networks", "subnets", "delete", "-q", g.subnetwork, 605 g.subnetworkRegion, "--project="+g.project)) 606 } 607 errNetwork := control.FinishRunning(exec.Command("gcloud", "compute", "networks", "delete", "-q", g.network, 608 "--project="+g.project)) 609 if errCluster != nil { 610 return fmt.Errorf("error deleting cluster: %v", errCluster) 611 } 612 if errFirewall != nil { 613 return fmt.Errorf("error deleting firewall: %v", errFirewall) 614 } 615 if errCleanFirewalls != nil { 616 return fmt.Errorf("error cleaning-up firewalls: %v", errCleanFirewalls) 617 } 618 if errSubnet != nil { 619 return fmt.Errorf("error deleting subnetwork: %v", errSubnet) 620 } 621 if errNetwork != nil { 622 return fmt.Errorf("error deleting network: %v", errNetwork) 623 } 624 if numLeakedFWRules > 0 { 625 return fmt.Errorf("leaked firewall rules") 626 } 627 return nil 628 } 629 630 func (g *gkeDeployer) containerArgs(args ...string) []string { 631 return append(append(append([]string{}, g.commandGroup...), "container"), args...) 632 } 633 634 func (g *gkeDeployer) GetClusterCreated(gcpProject string) (time.Time, error) { 635 res, err := control.Output(exec.Command( 636 "gcloud", 637 "compute", 638 "instance-groups", 639 "list", 640 "--project="+gcpProject, 641 "--format=json(name,creationTimestamp)")) 642 if err != nil { 643 return time.Time{}, fmt.Errorf("list instance-group failed : %v", err) 644 } 645 646 created, err := getLatestClusterUpTime(string(res)) 647 if err != nil { 648 return time.Time{}, fmt.Errorf("parse time failed : got gcloud res %s, err %v", string(res), err) 649 } 650 return created, nil 651 }