blob: 1bfbc1e4f4d8f99570312b71a16e259ee8347cfa [file] [log] [blame]
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main // import "golang.org/x/build/cmd/gcpinit"
import (
"bytes"
"context"
"crypto/rand"
"flag"
"fmt"
"log"
"strings"
"text/template"
"time"
monapi "cloud.google.com/go/monitoring/apiv3"
"golang.org/x/build/buildenv"
"golang.org/x/build/cmd/coordinator/metrics"
"golang.org/x/build/internal/buildgo"
dm "google.golang.org/api/deploymentmanager/v2"
"google.golang.org/api/option"
monpb "google.golang.org/genproto/googleapis/monitoring/v3"
)
var (
makeClusters = flag.String("make-clusters", "go,buildlets", "comma-separated list of clusters to create. Empty means none.")
makeMetrics = flag.Bool("make-metrics", false, "Create the Stackdriver metrics for buildlet monitoring.")
)
// Deployment Manager V2 manifest for creating a Google Container Engine
// cluster to run buildlets, as well as an autoscaler attached to the
// cluster's instance group to add capacity based on CPU utilization
const kubeConfig = `
resources:
- name: "{{ .Kube.Name }}"
type: container.v1.cluster
properties:
zone: "{{ .Env.Zone }}"
cluster:
initial_node_count: {{ .Kube.MinNodes }}
network: "default"
logging_service: "logging.googleapis.com"
monitoring_service: "none"
node_config:
machine_type: "{{ .Kube.MachineType }}"
oauth_scopes:
- "https://www.googleapis.com/auth/cloud-platform"
- "https://www.googleapis.com/auth/userinfo.email"
master_auth:
username: "admin"
password: "{{ .Password }}"`
// Old autoscaler part:
/*
`
- name: autoscaler
type: compute.v1.autoscaler
properties:
zone: "{{ .Zone }}"
name: "{{ .KubeName }}"
target: "$(ref.{{ .KubeName }}.instanceGroupUrls[0])"
autoscalingPolicy:
minNumReplicas: {{ .KubeMinNodes }}
maxNumReplicas: {{ .KubeMaxNodes }}
coolDownPeriodSec: 1200
cpuUtilization:
utilizationTarget: .6`
*/
func main() {
buildenv.RegisterStagingFlag()
flag.Parse()
buildenv.CheckUserCredentials()
buildEnv := buildenv.FromFlags()
ctx := context.Background()
bgc, err := buildgo.NewClient(ctx, buildEnv)
if err != nil {
log.Fatalf("could not create client: %v", err)
}
for _, c := range []*buildenv.KubeConfig{&buildEnv.KubeBuild, &buildEnv.KubeTools} {
err := createCluster(bgc, c)
if err != nil {
log.Fatalf("Error creating Kubernetes cluster %q: %v", c.Name, err)
}
}
if *makeMetrics {
if err := createMetrics(bgc); err != nil {
log.Fatalf("could not create metrics: %v", err)
}
}
}
type deploymentTemplateData struct {
Env *buildenv.Environment
Kube *buildenv.KubeConfig
Password string
}
func wantClusterCreate(name string) bool {
for _, want := range strings.Split(*makeClusters, ",") {
if want == name {
return true
}
}
return false
}
func createCluster(bgc *buildgo.Client, kube *buildenv.KubeConfig) error {
if !wantClusterCreate(kube.Name) {
log.Printf("skipping kubernetes cluster %q per flag", kube.Name)
return nil
}
log.Printf("Creating Kubernetes cluster: %v", kube.Name)
deploySvc, _ := dm.New(bgc.Client)
if kube.MaxNodes == 0 || kube.MinNodes == 0 {
return fmt.Errorf("MaxNodes/MinNodes values cannot be 0")
}
tpl, err := template.New("kube").Parse(kubeConfig)
if err != nil {
return fmt.Errorf("could not parse Deployment Manager template: %v", err)
}
var result bytes.Buffer
err = tpl.Execute(&result, deploymentTemplateData{
Env: bgc.Env,
Kube: kube,
Password: randomPassword(),
})
if err != nil {
return fmt.Errorf("could not execute Deployment Manager template: %v", err)
}
deployment := &dm.Deployment{
Name: kube.Name,
Target: &dm.TargetConfiguration{
Config: &dm.ConfigFile{
Content: result.String(),
},
},
}
op, err := deploySvc.Deployments.Insert(bgc.Env.ProjectName, deployment).Do()
if err != nil {
return fmt.Errorf("Failed to create cluster with Deployment Manager: %v", err)
}
opName := op.Name
log.Printf("Created. Waiting on operation %v", opName)
OpLoop:
for {
time.Sleep(2 * time.Second)
op, err := deploySvc.Operations.Get(bgc.Env.ProjectName, opName).Do()
if err != nil {
return fmt.Errorf("Failed to get op %s: %v", opName, err)
}
switch op.Status {
case "PENDING", "RUNNING":
log.Printf("Waiting on operation %v", opName)
continue
case "DONE":
// If no errors occurred, op.StatusMessage is empty.
if op.StatusMessage != "" {
log.Printf("Error: %+v", op.StatusMessage)
return fmt.Errorf("Failed to create.")
}
log.Printf("Success.")
break OpLoop
default:
return fmt.Errorf("Unknown status %q: %+v", op.Status, op)
}
}
return nil
}
func randomPassword() string {
buf := make([]byte, 10)
if _, err := rand.Read(buf); err != nil {
log.Fatalf("randomPassword: %v", err)
}
return fmt.Sprintf("%x", buf)
}
// createMetrics creates the Stackdriver metric types required to monitor
// buildlets on Stackdriver.
func createMetrics(bgc *buildgo.Client) error {
ctx := context.Background()
c, err := monapi.NewMetricClient(ctx, option.WithCredentials(bgc.Creds))
if err != nil {
return err
}
for _, m := range metrics.Metrics {
if _, err = c.CreateMetricDescriptor(ctx, &monpb.CreateMetricDescriptorRequest{
Name: m.DescriptorPath(bgc.Env.ProjectName),
MetricDescriptor: m.Descriptor,
}); err != nil {
return err
}
}
return nil
}