github.com/GoogleContainerTools/skaffold@v1.39.18/webhook/webhook.go (about)

     1  /*
     2  Copyright 2019 The Skaffold 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  	"encoding/json"
    21  	"flag"
    22  	"fmt"
    23  	"log"
    24  	"net/http"
    25  
    26  	"github.com/google/go-github/github"
    27  	appsv1 "k8s.io/api/apps/v1"
    28  
    29  	"github.com/GoogleContainerTools/skaffold/pkg/webhook/constants"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/webhook/gcs"
    31  	pkggithub "github.com/GoogleContainerTools/skaffold/pkg/webhook/github"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/webhook/kubernetes"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/webhook/labels"
    34  )
    35  
    36  const (
    37  	port = ":8080"
    38  )
    39  
    40  func main() {
    41  	// Setup the serve route to receive github events
    42  	http.HandleFunc("/receive", handleGithubEvent)
    43  	flag.Parse()
    44  	// Start the server
    45  	log.Println("Listening...")
    46  	log.Fatal(http.ListenAndServe(port, nil))
    47  }
    48  
    49  func handleGithubEvent(w http.ResponseWriter, r *http.Request) {
    50  	eventType := r.Header.Get(constants.GithubEventHeader)
    51  	if eventType != constants.PullRequestEvent {
    52  		return
    53  	}
    54  	event := new(github.PullRequestEvent)
    55  	if err := json.NewDecoder(r.Body).Decode(event); err != nil {
    56  		log.Printf("error decoding pr event: %v", err)
    57  	}
    58  	if err := handlePullRequestEvent(event); err != nil {
    59  		commentOnGithub(event, "Error creating deployment, please see controller logs for details.")
    60  		log.Printf("error handling pr event: %v", err)
    61  	}
    62  }
    63  
    64  func handlePullRequestEvent(event *github.PullRequestEvent) error {
    65  	log.Printf("handling pull request event: %+v", event)
    66  	// Cleanup any deployments if PR was merged or closed
    67  	if event.GetAction() == constants.ClosedAction {
    68  		return kubernetes.CleanupDeployment(event)
    69  	}
    70  
    71  	// Only continue if the docs-modifications label was added
    72  	if event.GetAction() != constants.LabeledAction {
    73  		return nil
    74  	}
    75  
    76  	prNumber := event.GetNumber()
    77  
    78  	if event.PullRequest.GetState() != constants.OpenState {
    79  		log.Printf("Pull request %d is either merged or closed, skipping docs deployment", prNumber)
    80  		return nil
    81  	}
    82  
    83  	if !labels.DocsLabelExists(event.GetPullRequest().Labels) {
    84  		log.Printf("Label %s not found on PR %d", constants.DocsLabel, prNumber)
    85  		return nil
    86  	}
    87  
    88  	// If a PR was relabeled, we need to first cleanup preexisting deployments
    89  	if err := kubernetes.CleanupDeployment(event); err != nil {
    90  		return fmt.Errorf("cleaning up deployment: %w", err)
    91  	}
    92  
    93  	// Create service for the PR and get the associated external IP
    94  	log.Printf("Label %s found on PR %d, creating service", constants.DocsLabel, prNumber)
    95  	svc, err := kubernetes.CreateService(event)
    96  	if err != nil {
    97  		return fmt.Errorf("creating service: %w", err)
    98  	}
    99  
   100  	ip, err := kubernetes.GetExternalIP(svc)
   101  	if err != nil {
   102  		return fmt.Errorf("getting external IP: %w", err)
   103  	}
   104  
   105  	// Create a deployment which maps to the service
   106  	log.Printf("Creating deployment for pull request %d", prNumber)
   107  	deployment, err := kubernetes.CreateDeployment(event, svc, ip)
   108  	if err != nil {
   109  		return fmt.Errorf("creating deployment for PR %d: %w", prNumber, err)
   110  	}
   111  	response := succeeded
   112  	if err := kubernetes.WaitForDeploymentToStabilize(deployment, ip); err != nil {
   113  		log.Printf("Deployment didn't stabilize, commenting with failure message...")
   114  		response = failed
   115  	}
   116  
   117  	msg, err := response(deployment, event, ip)
   118  	if err != nil {
   119  		return fmt.Errorf("getting github message: %w", err)
   120  	}
   121  
   122  	if err := commentOnGithub(event, msg); err != nil {
   123  		return fmt.Errorf("commenting on github: %w", err)
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func succeeded(d *appsv1.Deployment, event *github.PullRequestEvent, ip string) (string, error) {
   130  	baseURL := kubernetes.BaseURL(ip)
   131  	return fmt.Sprintf("Please visit [%s](%s) to view changes to the docs.", baseURL, baseURL), nil
   132  }
   133  
   134  func failed(d *appsv1.Deployment, event *github.PullRequestEvent, ip string) (string, error) {
   135  	name, err := gcs.UploadDeploymentLogsToBucket(d, event.GetNumber())
   136  	if err != nil {
   137  		return "", fmt.Errorf("uploading logs to bucket: %w", err)
   138  	}
   139  	url := fmt.Sprintf("https://storage.googleapis.com/%s/%s", constants.LogsGCSBucket, name)
   140  	return fmt.Sprintf("Error creating deployment %s, please visit %s to view logs.", d.Name, url), nil
   141  }
   142  
   143  func commentOnGithub(event *github.PullRequestEvent, msg string) error {
   144  	githubClient := pkggithub.NewClient()
   145  	if err := githubClient.CommentOnPR(event, msg); err != nil {
   146  		return fmt.Errorf("commenting on PR %d: %w", event.GetNumber(), err)
   147  	}
   148  	if err := githubClient.RemoveLabelFromPR(event, constants.DocsLabel); err != nil {
   149  		return fmt.Errorf("removing %s label from PR %d: %w", constants.DocsLabel, event.GetNumber(), err)
   150  	}
   151  	return nil
   152  }