github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/tackle/main.go (about) 1 /* 2 Copyright 2018 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 18 19 import ( 20 "crypto/rand" 21 "errors" 22 "flag" 23 "fmt" 24 "io" 25 "net/url" 26 "os" 27 "os/exec" 28 "sort" 29 "strconv" 30 "strings" 31 "time" 32 33 "github.com/sirupsen/logrus" 34 corev1 "k8s.io/api/core/v1" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/client-go/kubernetes" 38 "k8s.io/client-go/rest" 39 "k8s.io/client-go/tools/clientcmd" 40 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 41 42 "k8s.io/test-infra/prow/config/secret" 43 "k8s.io/test-infra/prow/flagutil" 44 "k8s.io/test-infra/prow/github" 45 46 _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // for gcp auth provider 47 ) 48 49 // ensure will ensure binary is on path or return an error with install message. 50 func ensure(binary, install string) error { 51 if _, err := exec.LookPath(binary); err != nil { 52 return fmt.Errorf("%s: %s", binary, install) 53 } 54 return nil 55 } 56 57 // ensureKubectl ensures kubectl is on path or prints a note of how to install. 58 func ensureKubectl() error { 59 return ensure("kubectl", "gcloud components install kubectl") 60 } 61 62 // ensureGcloud ensures gcloud on path or prints a note of how to install. 63 func ensureGcloud() error { 64 return ensure("gcloud", "https://cloud.google.com/sdk/gcloud") 65 } 66 67 // output returns the trimmed output of running args, or an err on non-zero exit. 68 func output(args ...string) (string, error) { 69 cmd := exec.Command(args[0], args[1:]...) 70 cmd.Stderr = os.Stderr 71 cmd.Stdin = os.Stdin 72 b, err := cmd.Output() 73 return strings.TrimSpace(string(b)), err 74 } 75 76 // currentAccount returns the configured account for gcloud 77 func currentAccount() (string, error) { 78 return output("gcloud", "config", "get-value", "core/account") 79 } 80 81 // currentProject returns the configured project for gcloud 82 func currentProject() (string, error) { 83 return output("gcloud", "config", "get-value", "core/project") 84 } 85 86 // project holds info about a project 87 type project struct { 88 name string 89 id string 90 } 91 92 // projects returns the list of accessible gcp projects 93 func projects(max int) ([]string, error) { 94 out, err := output("gcloud", "projects", "list", fmt.Sprintf("--limit=%d", max), "--format=value(project_id)") 95 if err != nil { 96 return nil, err 97 } 98 return strings.Split(out, "\n"), nil 99 } 100 101 // selectProject returns the user-selected project, defaulting to the current gcloud one. 102 func selectProject(choice string) (string, error) { 103 fmt.Print("Getting active GCP account...") 104 who, err := currentAccount() 105 if err != nil { 106 logrus.Warn("Run gcloud auth login to initialize gcloud") 107 return "", err 108 } 109 fmt.Println(who) 110 111 var projs []string 112 113 if choice == "" { 114 fmt.Printf("Projects available to %s:", who) 115 fmt.Println() 116 const max = 20 117 projs, err = projects(max) 118 for _, proj := range projs { 119 fmt.Println(" *", proj) 120 } 121 if err != nil { 122 return "", fmt.Errorf("list projects: %v", err) 123 } 124 if len(projs) == 0 { 125 fmt.Println("Create a project at https://console.cloud.google.com/") 126 return "", errors.New("no projects") 127 } 128 if len(projs) == max { 129 fmt.Println(" ... Wow, that is a lot of projects!") 130 fmt.Println("Type the name of any project, including ones not in this truncated list") 131 } 132 133 def, err := currentProject() 134 if err != nil { 135 return "", fmt.Errorf("get current project: %v", err) 136 } 137 fmt.Printf("Select project [%s]: ", def) 138 fmt.Scanln(&choice) 139 140 // use default project 141 if choice == "" { 142 return def, nil 143 } 144 } 145 146 // is this a project from the list? 147 for _, p := range projs { 148 if p == choice { 149 return choice, nil 150 } 151 } 152 153 fmt.Printf("Ensuring %s has access to %s...", who, choice) 154 fmt.Println() 155 156 // no, make sure user has access to it 157 if err = exec.Command("gcloud", "projects", "describe", choice).Run(); err != nil { 158 return "", fmt.Errorf("%s cannot describe project: %v", who, err) 159 } 160 161 return choice, nil 162 } 163 164 // cluster holds info about a GKE cluster 165 type cluster struct { 166 name string 167 zone string 168 project string 169 } 170 171 func (c cluster) context() string { 172 return fmt.Sprintf("gke_%s_%s_%s", c.project, c.zone, c.name) 173 } 174 175 // currentClusters returns a {name: cluster} map. 176 func currentClusters(proj string) (map[string]cluster, error) { 177 clusters, err := output("gcloud", "container", "clusters", "list", "--project="+proj, "--format=value(name,zone)") 178 if err != nil { 179 return nil, fmt.Errorf("list clusters: %v", err) 180 } 181 options := map[string]cluster{} 182 for _, line := range strings.Split(clusters, "\n") { 183 if len(line) == 0 { 184 continue 185 } 186 parts := strings.Split(line, "\t") 187 if len(parts) != 2 { 188 return nil, fmt.Errorf("bad line: %q", line) 189 } 190 c := cluster{name: parts[0], zone: parts[1], project: proj} 191 options[c.name] = c 192 } 193 return options, nil 194 } 195 196 // createCluster causes gcloud to create a cluster in project, returning the context name 197 func createCluster(proj, choice string) (*cluster, error) { 198 const def = "prow" 199 if choice == "" { 200 fmt.Printf("Cluster name [%s]: ", def) 201 fmt.Scanln(&choice) 202 if choice == "" { 203 choice = def 204 } 205 } 206 207 cmd := exec.Command("gcloud", "container", "clusters", "create", choice) 208 cmd.Stdin = os.Stdin 209 cmd.Stdout = os.Stdout 210 cmd.Stderr = os.Stderr 211 if err := cmd.Run(); err != nil { 212 return nil, fmt.Errorf("create cluster: %v", err) 213 } 214 215 out, err := output("gcloud", "container", "clusters", "describe", choice, "--format=value(name,zone)") 216 if err != nil { 217 return nil, fmt.Errorf("describe cluster: %v", err) 218 } 219 parts := strings.Split(out, "\t") 220 if len(parts) != 2 { 221 return nil, fmt.Errorf("bad describe cluster output: %s", out) 222 } 223 224 return &cluster{name: parts[0], zone: parts[1], project: proj}, nil 225 } 226 227 // createContext has the user create a context. 228 func createContext(co contextOptions) (string, error) { 229 proj, err := selectProject(co.project) 230 if err != nil { 231 logrus.Info("Run gcloud auth login to initialize gcloud") 232 return "", fmt.Errorf("get current project: %v", err) 233 } 234 235 fmt.Printf("Existing GKE clusters in %s:", proj) 236 fmt.Println() 237 clusters, err := currentClusters(proj) 238 if err != nil { 239 return "", fmt.Errorf("list %s clusters: %v", proj, err) 240 } 241 for name := range clusters { 242 fmt.Println(" *", name) 243 } 244 if len(clusters) == 0 { 245 fmt.Println(" No clusters") 246 } 247 var choice string 248 create := co.create 249 reuse := co.reuse 250 switch { 251 case create != "" && reuse != "": 252 return "", errors.New("Cannot use both --create and --reuse") 253 case create != "": 254 fmt.Println("Creating new " + create + " cluster...") 255 choice = "new" 256 case reuse != "": 257 fmt.Println("Reusing existing " + reuse + " cluster...") 258 choice = reuse 259 default: 260 fmt.Print("Get credentials for existing cluster or [create new]: ") 261 fmt.Scanln(&choice) 262 } 263 264 if choice == "" || choice == "new" { 265 cluster, err := createCluster(proj, create) 266 if err != nil { 267 return "", fmt.Errorf("create cluster in %s: %v", proj, err) 268 } 269 return cluster.context(), nil 270 } 271 272 cluster, ok := clusters[choice] 273 if !ok { 274 return "", fmt.Errorf("cluster not found: %s", choice) 275 } 276 cmd := exec.Command("gcloud", "container", "clusters", "get-credentials", cluster.name, "--project="+cluster.project, "--zone="+cluster.zone) 277 cmd.Stdin = os.Stdin 278 cmd.Stdout = os.Stdout 279 cmd.Stderr = os.Stderr 280 if err := cmd.Run(); err != nil { 281 return "", fmt.Errorf("get credentials: %v", err) 282 } 283 return cluster.context(), nil 284 } 285 286 // contextConfig returns the loader and config, which can create a clientconfig. 287 func contextConfig() (clientcmd.ClientConfigLoader, *clientcmdapi.Config, error) { 288 if err := ensureKubectl(); err != nil { 289 fmt.Println("Prow's tackler requires kubectl, please install:") 290 fmt.Println(" *", err) 291 if gerr := ensureGcloud(); gerr != nil { 292 fmt.Println(" *", gerr) 293 } 294 return nil, nil, errors.New("missing kubectl") 295 } 296 297 l := clientcmd.NewDefaultClientConfigLoadingRules() 298 c, err := l.Load() 299 return l, c, err 300 } 301 302 // selectContext allows the user to choose a context 303 // This may involve creating a cluster 304 func selectContext(co contextOptions) (string, error) { 305 fmt.Println("Existing kubernetes contexts:") 306 // get cluster context 307 _, cfg, err := contextConfig() 308 if err != nil { 309 logrus.WithError(err).Fatal("Failed to load ~/.kube/config from any obvious location") 310 } 311 // list contexts and ask to user to choose a context 312 options := map[int]string{} 313 314 var ctxs []string 315 for ctx := range cfg.Contexts { 316 ctxs = append(ctxs, ctx) 317 } 318 sort.Strings(ctxs) 319 for idx, ctx := range ctxs { 320 options[idx] = ctx 321 if ctx == cfg.CurrentContext { 322 fmt.Printf("* %d: %s (current)", idx, ctx) 323 } else { 324 fmt.Printf(" %d: %s", idx, ctx) 325 } 326 fmt.Println() 327 } 328 fmt.Println() 329 choice := co.context 330 switch { 331 case choice != "": 332 fmt.Println("Reuse " + choice + " context...") 333 case co.create != "" || co.reuse != "": 334 choice = "create" 335 fmt.Println("Create new context...") 336 default: 337 fmt.Print("Choose context or [create new]: ") 338 fmt.Scanln(&choice) 339 } 340 341 if choice == "create" || choice == "" || choice == "create new" || choice == "new" { 342 ctx, err := createContext(co) 343 if err != nil { 344 return "", fmt.Errorf("create context: %v", err) 345 } 346 return ctx, nil 347 } 348 349 if _, ok := cfg.Contexts[choice]; ok { 350 return choice, nil 351 } 352 353 idx, err := strconv.Atoi(choice) 354 if err != nil { 355 return "", fmt.Errorf("invalid context: %q", choice) 356 } 357 358 if ctx, ok := options[idx]; ok { 359 return ctx, nil 360 } 361 362 return "", fmt.Errorf("invalid index: %d", idx) 363 } 364 365 // applyCreate will dry-run create and then pipe this to kubectl apply. 366 // 367 // If we use the create verb it will fail if the secret already exists. 368 // And kubectl will reject the apply verb with a secret. 369 func applyCreate(ctx string, args ...string) error { 370 create := exec.Command("kubectl", append([]string{"--dry-run=true", "--output=yaml", "create"}, args...)...) 371 create.Stderr = os.Stderr 372 obj, err := create.StdoutPipe() 373 if err != nil { 374 return fmt.Errorf("rolebinding pipe: %v", err) 375 } 376 377 if err := create.Start(); err != nil { 378 return fmt.Errorf("start create: %v", err) 379 } 380 if err := apply(ctx, obj); err != nil { 381 return fmt.Errorf("apply: %v", err) 382 } 383 if err := create.Wait(); err != nil { 384 return fmt.Errorf("create: %v", err) 385 } 386 return nil 387 } 388 389 func apply(ctx string, in io.Reader) error { 390 apply := exec.Command("kubectl", "--context="+ctx, "apply", "-f", "-") 391 apply.Stderr = os.Stderr 392 apply.Stdout = os.Stdout 393 apply.Stdin = in 394 if err := apply.Start(); err != nil { 395 return fmt.Errorf("start: %v", err) 396 } 397 return apply.Wait() 398 } 399 400 func applyRoleBinding(context string) error { 401 who, err := currentAccount() 402 if err != nil { 403 return fmt.Errorf("current account: %v", err) 404 } 405 return applyCreate(context, "clusterrolebinding", "prow-admin", "--clusterrole=cluster-admin", "--user="+who) 406 } 407 408 type options struct { 409 githubTokenPath string 410 starter string 411 repos flagutil.Strings 412 contextOptions 413 confirm bool 414 } 415 416 type contextOptions struct { 417 context string 418 create string 419 reuse string 420 project string 421 } 422 423 func addFlags(fs *flag.FlagSet) *options { 424 var o options 425 fs.StringVar(&o.githubTokenPath, "github-token-path", "", "Path to github token") 426 fs.StringVar(&o.starter, "starter", "", "Apply starter.yaml from the following path or URL (use upstream for latest)") 427 fs.Var(&o.repos, "repo", "Send prow webhooks for these orgs or org/repos (repeat as necessary)") 428 fs.StringVar(&o.context, "context", "", "Choose kubeconfig context to use") 429 fs.StringVar(&o.create, "create", "", "name of cluster to create in --project") 430 fs.StringVar(&o.reuse, "reuse", "", "Reuse existing cluster in --project") 431 fs.StringVar(&o.project, "project", "", "GCP project to get/create cluster") 432 fs.BoolVar(&o.confirm, "confirm", false, "Overwrite existing prow deployments without asking if set") 433 return &o 434 } 435 436 func githubToken(choice string) (string, error) { 437 if choice == "" { 438 fmt.Print("Input /path/to/github/token to upload into cluster: ") 439 fmt.Scanln(&choice) 440 } 441 path := os.ExpandEnv(choice) 442 if _, err := os.Stat(path); err != nil { 443 return "", fmt.Errorf("open %s: %v", path, err) 444 } 445 return path, nil 446 } 447 448 func githubClient(tokenPath string, dry bool) (*github.Client, error) { 449 secretAgent := &secret.Agent{} 450 if err := secretAgent.Start([]string{tokenPath}); err != nil { 451 return nil, fmt.Errorf("start agent: %v", err) 452 } 453 454 gen := secretAgent.GetTokenGenerator(tokenPath) 455 if dry { 456 return github.NewDryRunClient(gen, "https://api.github.com"), nil 457 } 458 return github.NewClient(gen, "https://api.github.com"), nil 459 } 460 461 func applySecret(ctx, name, key, path string) error { 462 return applyCreate(ctx, "secret", "generic", name, "--from-file="+key+"="+path) 463 } 464 465 func applyStarter(kc *kubernetes.Clientset, ns, choice, ctx string, overwrite bool) error { 466 if choice == "" { 467 fmt.Print("Apply starter.yaml from [github upstream]: ") 468 fmt.Scanln(&choice) 469 } 470 if choice == "" || choice == "github" || choice == "upstream" || choice == "github upstream" { 471 choice = "https://raw.githubusercontent.com/kubernetes/test-infra/master/prow/cluster/starter.yaml" 472 fmt.Println("Loading from", choice) 473 } 474 _, err := kc.AppsV1().Deployments(ns).Get("plank", metav1.GetOptions{}) 475 switch { 476 case err != nil && apierrors.IsNotFound(err): 477 // Great, new clean namespace to deploy! 478 case err != nil: // unexpected error 479 return fmt.Errorf("get plank: %v", err) 480 case !overwrite: // already a plank, confirm overwrite 481 fmt.Printf("Prow is already deployed to %s in %s, overwrite? [no]: ", ns, ctx) 482 var choice string 483 fmt.Scanln(&choice) 484 switch choice { 485 case "y", "Y", "yes": 486 // carry on, then 487 default: 488 return errors.New("prow already deployed") 489 } 490 } 491 apply := exec.Command("kubectl", "--context="+ctx, "apply", "-f", choice) 492 apply.Stderr = os.Stderr 493 apply.Stdout = os.Stdout 494 return apply.Run() 495 } 496 497 func clientConfig(context string) (*rest.Config, error) { 498 loader, cfg, err := contextConfig() 499 if err != nil { 500 return nil, fmt.Errorf("load contexts: %v", err) 501 } 502 503 return clientcmd.NewNonInteractiveClientConfig(*cfg, context, &clientcmd.ConfigOverrides{}, loader).ClientConfig() 504 } 505 506 func ingress(kc *kubernetes.Clientset, ns, service string) (url.URL, error) { 507 for { 508 ings, err := kc.Extensions().Ingresses(ns).List(metav1.ListOptions{}) 509 if err != nil { 510 logrus.WithError(err).Fatal("Could not get ingresses") 511 time.Sleep(5 * time.Second) 512 } 513 var best url.URL 514 points := 0 515 for _, ing := range ings.Items { 516 // does this ingress route to the hook service? 517 cur := -1 518 var maybe url.URL 519 for _, r := range ing.Spec.Rules { 520 h := r.IngressRuleValue.HTTP 521 if h == nil { 522 continue 523 } 524 for _, p := range h.Paths { 525 if p.Backend.ServiceName != service { 526 continue 527 } 528 maybe.Scheme = "http" 529 maybe.Host = r.Host 530 maybe.Path = p.Path 531 cur = 0 532 break 533 } 534 } 535 if cur < 0 { 536 continue // no 537 } 538 539 // does it have an ip or hostname? 540 for _, tls := range ing.Spec.TLS { 541 for _, h := range tls.Hosts { 542 if h == maybe.Host { 543 cur = 3 544 maybe.Scheme = "https" 545 break 546 } 547 } 548 } 549 550 if cur == 0 { 551 for _, i := range ing.Status.LoadBalancer.Ingress { 552 if maybe.Host != "" && maybe.Host == i.Hostname { 553 cur = 2 554 break 555 } 556 if i.IP != "" { 557 cur = 1 558 if maybe.Host == "" { 559 maybe.Host = i.IP 560 } 561 break 562 } 563 } 564 } 565 if cur > points { 566 best = maybe 567 points = cur 568 } 569 } 570 if points > 0 { 571 return best, nil 572 } 573 fmt.Print(".") 574 time.Sleep(1 * time.Second) 575 } 576 } 577 578 func hmacSecret() string { 579 buf := make([]byte, 20) 580 rand.Read(buf) 581 return fmt.Sprintf("%x", buf) 582 } 583 584 func findHook(client *github.Client, org, repo string, loc url.URL) (*github.Hook, error) { 585 loc.Scheme = "" 586 goal := loc.String() 587 var hooks []github.Hook 588 var err error 589 if repo == "" { 590 hooks, err = client.ListOrgHooks(org) 591 } else { 592 hooks, err = client.ListRepoHooks(org, repo) 593 } 594 if err != nil { 595 return nil, fmt.Errorf("list hooks: %v", err) 596 } 597 598 for _, h := range hooks { 599 u, err := url.Parse(h.Config.URL) 600 if err != nil { 601 logrus.WithError(err).Warnf("Invalid %s/%s hook url %s", org, repo, h.Config.URL) 602 continue 603 } 604 u.Scheme = "" 605 if u.String() == goal { 606 return &h, nil 607 } 608 } 609 return nil, nil 610 } 611 612 func orgRepo(in string) (string, string) { 613 parts := strings.SplitN(in, "/", 2) 614 org := parts[0] 615 var repo string 616 if len(parts) == 2 { 617 repo = parts[1] 618 } 619 return org, repo 620 } 621 622 func ensureHmac(kc *kubernetes.Clientset, ns string) (string, error) { 623 secret, err := kc.CoreV1().Secrets(ns).Get("hmac-token", metav1.GetOptions{}) 624 if err != nil && !apierrors.IsNotFound(err) { 625 return "", fmt.Errorf("get: %v", err) 626 } 627 if err == nil { 628 buf, ok := secret.Data["hmac"] 629 if ok { 630 return string(buf), nil 631 } 632 logrus.Warn("hmac-token secret does not contain an hmac key, replacing secret with new random data...") 633 } else { 634 logrus.Info("Creating new hmac-token secret with random data...") 635 } 636 hmac := hmacSecret() 637 secret = &corev1.Secret{} 638 secret.Name = "hmac-token" 639 secret.Namespace = ns 640 secret.StringData = map[string]string{"hmac": hmac} 641 if err == nil { 642 if _, err = kc.CoreV1().Secrets(ns).Update(secret); err != nil { 643 return "", fmt.Errorf("update: %v", err) 644 } 645 } else { 646 if _, err = kc.CoreV1().Secrets(ns).Create(secret); err != nil { 647 return "", fmt.Errorf("create: %v", err) 648 } 649 } 650 return hmac, nil 651 } 652 653 func enableHooks(client *github.Client, loc url.URL, secret string, repos ...string) ([]string, error) { 654 var enabled []string 655 locStr := loc.String() 656 hasFlagValues := len(repos) > 0 657 for { 658 var choice string 659 switch { 660 case !hasFlagValues: 661 if len(enabled) > 0 { 662 fmt.Println("Enabled so far:", strings.Join(enabled, ", ")) 663 } 664 fmt.Print("Enable which org or org/repo [quit]: ") 665 fmt.Scanln(&choice) 666 case len(repos) > 0: 667 choice = repos[0] 668 repos = repos[1:] 669 default: 670 choice = "" 671 } 672 if choice == "" || choice == "quit" { 673 return enabled, nil 674 } 675 org, repo := orgRepo(choice) 676 hook, err := findHook(client, org, repo, loc) 677 if err != nil { 678 return enabled, fmt.Errorf("find %s hook in %s: %v", locStr, choice, err) 679 } 680 yes := true 681 j := "json" 682 req := github.HookRequest{ 683 Name: "web", 684 Active: &yes, 685 Events: github.AllHookEvents, 686 Config: &github.HookConfig{ 687 URL: locStr, 688 ContentType: &j, 689 Secret: &secret, 690 }, 691 } 692 if hook == nil { 693 var id int 694 if repo == "" { 695 id, err = client.CreateOrgHook(org, req) 696 } else { 697 id, err = client.CreateRepoHook(org, repo, req) 698 } 699 if err != nil { 700 return enabled, fmt.Errorf("create %s hook in %s: %v", locStr, choice, err) 701 } 702 fmt.Printf("Created hook %d to %s in %s", id, locStr, choice) 703 fmt.Println() 704 } else { 705 if repo == "" { 706 err = client.EditOrgHook(org, hook.ID, req) 707 } else { 708 err = client.EditRepoHook(org, repo, hook.ID, req) 709 } 710 if err != nil { 711 return enabled, fmt.Errorf("edit %s hook %d in %s: %v", locStr, hook.ID, choice, err) 712 } 713 } 714 enabled = append(enabled, choice) 715 } 716 } 717 718 func ensureConfigMap(kc *kubernetes.Clientset, ns, name, key string) error { 719 cm, err := kc.CoreV1().ConfigMaps(ns).Get(name, metav1.GetOptions{}) 720 if err != nil { 721 if !apierrors.IsNotFound(err) { 722 return fmt.Errorf("get: %v", err) 723 } 724 cm = &corev1.ConfigMap{ 725 Data: map[string]string{key: ""}, 726 } 727 cm.Name = name 728 cm.Namespace = ns 729 _, err := kc.CoreV1().ConfigMaps(ns).Create(cm) 730 if err != nil { 731 return fmt.Errorf("create: %v", err) 732 } 733 } 734 735 if _, ok := cm.Data[key]; ok { 736 return nil 737 } 738 logrus.Warnf("%s/%s missing key %s, adding...", ns, name, key) 739 if cm.Data == nil { 740 cm.Data = map[string]string{} 741 } 742 cm.Data[key] = "" 743 if _, err := kc.CoreV1().ConfigMaps(ns).Update(cm); err != nil { 744 return fmt.Errorf("update: %v", err) 745 } 746 return nil 747 } 748 749 func main() { 750 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 751 skipGithub := fs.Bool("skip-github", false, "Do not add github webhooks if set") 752 opt := addFlags(fs) 753 fs.Parse(os.Args[1:]) 754 755 ctx, err := selectContext(opt.contextOptions) 756 if err != nil { 757 logrus.WithError(err).Fatal("Failed to select context") 758 } 759 760 // get kubernetes client 761 clientCfg, err := clientConfig(ctx) 762 if err != nil { 763 logrus.WithError(err).Fatal("Failed to reload ~/.kube/config from any obvious location") 764 } 765 kc, err := kubernetes.NewForConfig(clientCfg) 766 if err != nil { 767 logrus.WithError(err).Fatal("Failed to create kubernetes client") 768 } 769 770 fmt.Println("Applying admin role bindings (to create RBAC rules)...") 771 if err := applyRoleBinding(ctx); err != nil { 772 logrus.WithError(err).Fatalf("Failed to apply cluster role binding to %s", ctx) 773 } 774 775 const ns = "default" 776 // configure plugins.yaml and config.yaml 777 // TODO(fejta): throw up an editor 778 if err = ensureConfigMap(kc, ns, "config", "config.yaml"); err != nil { 779 logrus.WithError(err).Fatal("Failed to ensure config.yaml exists") 780 } 781 if err = ensureConfigMap(kc, ns, "plugins", "plugins.yaml"); err != nil { 782 logrus.WithError(err).Fatal("Failed to ensure plugins.yaml exists") 783 } 784 785 fmt.Println("Deploying prow...") 786 if err := applyStarter(kc, ns, opt.starter, ctx, opt.confirm); err != nil { 787 logrus.WithError(err).Fatal("Could not deploy prow") 788 } 789 790 if !*skipGithub { 791 fmt.Println("Checking github credentials...") 792 // create github client 793 token, err := githubToken(opt.githubTokenPath) 794 if err != nil { 795 logrus.WithError(err).Fatal("Failed to get github token") 796 } 797 client, err := githubClient(token, false) 798 if err != nil { 799 logrus.WithError(err).Fatal("Failed to create github client") 800 } 801 who, err := client.BotName() 802 if err != nil { 803 logrus.WithError(err).Fatal("Cannot access github account name") 804 } 805 fmt.Println("Prow will act as", who, "on github") 806 807 // create github secrets 808 fmt.Print("Applying github token into oauth-token secret...") 809 if err := applySecret(ctx, "oauth-token", "oauth", token); err != nil { 810 logrus.WithError(err).Fatal("Could not apply github oauth token secret") 811 } 812 813 fmt.Print("Ensuring hmac secret exists at hmac-token...") 814 hmac, err := ensureHmac(kc, ns) 815 if err != nil { 816 logrus.WithError(err).Fatal("Failed to ensure hmac-token exists") 817 } 818 fmt.Println("exists") 819 820 fmt.Print("Looking for prow's hook ingress URL... ") 821 url, err := ingress(kc, ns, "hook") 822 if err != nil { 823 logrus.WithError(err).Fatal("Could not determine webhook ingress URL") 824 } 825 fmt.Println(url.String()) 826 827 // TODO(fejta): ensure plugins are enabled for all these repos 828 _, err = enableHooks(client, url, hmac, opt.repos.Strings()...) 829 if err != nil { 830 logrus.WithError(err).Fatalf("Could not configure repos to send %s webhooks.", url.String()) 831 } 832 } 833 834 deck, err := ingress(kc, ns, "deck") 835 if err != nil { 836 logrus.WithError(err).Fatalf("Could not find deck URL") 837 } 838 deck.Path = strings.TrimRight(deck.Path, "*") 839 fmt.Printf("Enjoy your %s prow instance at: %s!", ctx, deck.String()) 840 fmt.Println() 841 }