171 lines
5.3 KiB
Go
171 lines
5.3 KiB
Go
// Copyright 2019 Google LLC
|
|
//
|
|
// 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
|
|
//
|
|
// https://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 main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"google.golang.org/api/container/v1"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// project_cleanup tool provides support to cleanup different resources insides a GCP project.
|
|
// Types of resources to be cleaned up in a single execution of the tool is specified as YAML file
|
|
// (run-time parameter flag) to the tool. Each GCP resource is associated with a handler function
|
|
// that performs cleanup. Adding a new resource for cleanup involves - adding a helper function to
|
|
// ProjectCleaner type and updating the switch condition in the CleanupProject method.
|
|
|
|
var (
|
|
resourceSpec = flag.String("resource_spec", "", "resource spec yaml file")
|
|
)
|
|
|
|
// ResourceSpec is the top-level spec specifying GCP resources to be cleaned up in a project.
|
|
type ResourceSpec struct {
|
|
// ProjectId - GCP Project Id
|
|
ProjectId string `yaml:"project-id"`
|
|
// Resources - Spec for each resource to be cleaned up by the tool.
|
|
Resources []GCPResource `yaml:"resources"`
|
|
}
|
|
|
|
// GCPResource specifies an individual GCP resource to be cleaned up.
|
|
type GCPResource struct {
|
|
// Resource - name of the resource is used as key to the handler function.
|
|
Resource string `yaml:"resource"`
|
|
// Zones - Multiple zones support.
|
|
Zones []string `yaml:"zones"`
|
|
// NamePrefixes - Only support for pre-fixed based filtering as go-client doesn't support regex
|
|
// based filtering.
|
|
NamePrefixes []string `yaml:"name-prefixes"`
|
|
// TimeLapseInHours - Number of hours before the resource needs to be cleaned up.
|
|
TimeLapseInHours int `yaml:"time-lapse-hours"`
|
|
}
|
|
|
|
// ProjectCleaner - top level type providing handlers for different GCP resources.
|
|
type ProjectCleaner struct {
|
|
ProjectId string
|
|
Resources []GCPResource
|
|
}
|
|
|
|
// GKEClusterHandler provides support to cleanup GCP resources within the project.
|
|
func (p *ProjectCleaner) GKEClusterHandler(resource GCPResource) {
|
|
// See https://cloud.google.com/docs/authentication/.
|
|
// Use GOOGLE_APPLICATION_CREDENTIALS environment variable to specify a service account key file
|
|
// to authenticate to the API.
|
|
ctx := context.Background()
|
|
svc, err := container.NewService(ctx)
|
|
if err != nil {
|
|
log.Fatalf("Could not initialize gke client: %v", err)
|
|
}
|
|
|
|
elapsedTime := time.Now().Add(time.Hour * (-1) * time.Duration(resource.TimeLapseInHours))
|
|
for _, zone := range resource.Zones {
|
|
log.Printf("Listing gke clusters in zone: %v", zone)
|
|
listResponse, err := svc.Projects.Zones.Clusters.List(p.ProjectId, zone).Do()
|
|
if err != nil {
|
|
log.Printf("Failed listing gke clusters in zone %v: %v", zone, err)
|
|
continue
|
|
}
|
|
|
|
if listResponse == nil {
|
|
log.Printf("Encountered nil listResponse gke clusters listing in zone: %v", zone)
|
|
continue
|
|
}
|
|
|
|
for _, cluster := range listResponse.Clusters {
|
|
createdTime, err := time.Parse(time.RFC3339, cluster.CreateTime)
|
|
if err != nil {
|
|
log.Printf("Unable to parse created time for cluster: %s. Ignoring cluster",
|
|
cluster.Name)
|
|
continue
|
|
}
|
|
|
|
if p.checkForPrefix(cluster.Name, resource.NamePrefixes) && createdTime.Before(elapsedTime) {
|
|
log.Printf("Found cluster: %s for deletion", cluster.Name)
|
|
if op, err := svc.Projects.Zones.Clusters.Delete(p.ProjectId, zone, cluster.Name).Do();
|
|
err != nil {
|
|
log.Printf("Encountered error calling delete on cluster: %s Error: %v",
|
|
cluster.Name, err)
|
|
} else {
|
|
log.Printf("Kicked off cluster delete : %v", op.TargetLink)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkForPrefix - helper function to check if testStr string has any of the prefix specified in
|
|
// prefixes
|
|
func (p *ProjectCleaner) checkForPrefix(testStr string, prefixes []string) bool {
|
|
log.Printf("Performing prefix check for String: %s for Prefixes: %v", testStr, prefixes)
|
|
for _, prefix := range prefixes {
|
|
if strings.HasPrefix(testStr, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CleanupProject iterates over each resource specified in the spec and calls the corresponding
|
|
// cleanup handler method.
|
|
func (p *ProjectCleaner) CleanupProject() {
|
|
for _, resource := range p.Resources {
|
|
switch resource.Resource {
|
|
case "gke-cluster":
|
|
p.GKEClusterHandler(resource)
|
|
default:
|
|
log.Printf("Un-identified resource: %v found in spec. Ignoring", resource.Resource)
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
log.SetOutput(os.Stdout)
|
|
|
|
if *resourceSpec == "" {
|
|
fmt.Fprintln(os.Stderr, "missing resource_spec parameter")
|
|
flag.Usage()
|
|
os.Exit(2)
|
|
}
|
|
|
|
if _, err := os.Stat(*resourceSpec); err != nil {
|
|
log.Fatalf("missing resource spec file")
|
|
}
|
|
|
|
var r ResourceSpec
|
|
yamlFile, err := ioutil.ReadFile(*resourceSpec)
|
|
if err != nil {
|
|
log.Fatalf("Error reading yaml file")
|
|
}
|
|
|
|
err = yaml.Unmarshal(yamlFile, &r)
|
|
if err != nil {
|
|
log.Fatalf("Unmarshal: %v", err)
|
|
}
|
|
|
|
p := ProjectCleaner{
|
|
ProjectId: r.ProjectId,
|
|
Resources: r.Resources,
|
|
}
|
|
p.CleanupProject()
|
|
}
|