github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/orchestration/handlers/kyma_handler.go (about)

     1  package handlers
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/kyma-project/kyma-environment-broker/internal"
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/mod/semver"
    15  
    16  	"github.com/google/uuid"
    17  	"github.com/gorilla/mux"
    18  	"github.com/kyma-project/kyma-environment-broker/common/orchestration"
    19  	"github.com/kyma-project/kyma-environment-broker/internal/httputil"
    20  	"github.com/kyma-project/kyma-environment-broker/internal/process"
    21  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    22  	"github.com/sirupsen/logrus"
    23  
    24  	"github.com/google/go-github/github"
    25  )
    26  
    27  type kymaHandler struct {
    28  	orchestrations storage.Orchestrations
    29  	queue          *process.Queue
    30  	converter      Converter
    31  	gitClient      *github.Client
    32  	log            logrus.FieldLogger
    33  }
    34  
    35  func NewKymaHandler(orchestrations storage.Orchestrations, q *process.Queue, log logrus.FieldLogger) *kymaHandler {
    36  	return &kymaHandler{
    37  		orchestrations: orchestrations,
    38  		queue:          q,
    39  		log:            log,
    40  		converter:      Converter{},
    41  		gitClient:      github.NewClient(nil),
    42  	}
    43  }
    44  
    45  func (h *kymaHandler) AttachRoutes(router *mux.Router) {
    46  	router.HandleFunc("/upgrade/kyma", h.createOrchestration).Methods(http.MethodPost)
    47  }
    48  
    49  func (h *kymaHandler) createOrchestration(w http.ResponseWriter, r *http.Request) {
    50  	// validate request body
    51  	params := orchestration.Parameters{}
    52  	if r.Body != nil {
    53  		err := json.NewDecoder(r.Body).Decode(&params)
    54  		if err != nil {
    55  			h.log.Errorf("while decoding request body: %v", err)
    56  			httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while decoding request body: %v", err))
    57  			return
    58  		}
    59  	}
    60  
    61  	// validate target
    62  	err := validateTarget(params.Targets)
    63  	if err != nil {
    64  		h.log.Errorf("while validating target: %v", err)
    65  		httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while validating target: %w", err))
    66  		return
    67  	}
    68  
    69  	// validate Kyma version
    70  	err = h.ValidateKymaVersion(params.Kyma.Version)
    71  	if err != nil {
    72  		h.log.Errorf("while validating kyma version: %v", err)
    73  		httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while validating kyma version: %w", err))
    74  		return
    75  	}
    76  
    77  	// validate deprecated parameteter `maintenanceWindow`
    78  	err = ValidateDeprecatedParameters(params)
    79  	if err != nil {
    80  		h.log.Errorf("found deprecated value: %v", err)
    81  		httputil.WriteErrorResponse(w, http.StatusBadRequest, errors.Wrapf(err, "found deprecated value"))
    82  		return
    83  	}
    84  
    85  	// validate `schedule` field
    86  	err = ValidateScheduleParameter(&params)
    87  	if err != nil {
    88  		h.log.Errorf("found deprecated value: %v", err)
    89  		httputil.WriteErrorResponse(w, http.StatusBadRequest, errors.Wrapf(err, "found deprecated value"))
    90  		return
    91  	}
    92  
    93  	now := time.Now()
    94  	o := internal.Orchestration{
    95  		OrchestrationID: uuid.New().String(),
    96  		Type:            orchestration.UpgradeKymaOrchestration,
    97  		State:           orchestration.Pending,
    98  		Description:     "queued for processing",
    99  		Parameters:      params,
   100  		CreatedAt:       now,
   101  		UpdatedAt:       now,
   102  	}
   103  
   104  	err = h.orchestrations.Insert(o)
   105  	if err != nil {
   106  		h.log.Errorf("while inserting orchestration to storage: %v", err)
   107  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while inserting orchestration to storage: %w", err))
   108  		return
   109  	}
   110  
   111  	h.queue.Add(o.OrchestrationID)
   112  
   113  	response := orchestration.UpgradeResponse{OrchestrationID: o.OrchestrationID}
   114  
   115  	httputil.WriteResponse(w, http.StatusAccepted, response)
   116  }
   117  
   118  // ValidateKymaVersion validates provided version. Supports three types of versioning:
   119  // semantic version, PR-<number>, and <branch name>-<commit hash>.
   120  // Validates version iff GitHub responded with 4xx code. If GitHub API does not work
   121  // (e.g. due to API RATE limit), returns version as valid.
   122  func (h *kymaHandler) ValidateKymaVersion(version string) error {
   123  	var (
   124  		err          error
   125  		resp         *github.Response
   126  		shouldHandle = func(resp *github.Response) bool {
   127  			return resp != nil &&
   128  				resp.StatusCode >= 400 && resp.StatusCode < 500 &&
   129  				resp.StatusCode != http.StatusForbidden
   130  		}
   131  	)
   132  
   133  	switch {
   134  	// handle semantic version
   135  	case semver.IsValid(fmt.Sprintf("v%s", version)):
   136  		_, resp, err = h.gitClient.Repositories.GetReleaseByTag(context.Background(), internal.GitKymaProject, internal.GitKymaRepo, version)
   137  	// handle PR-<number>
   138  	case strings.HasPrefix(version, "PR-"):
   139  		prID, _ := strconv.Atoi(strings.TrimPrefix(version, "PR-"))
   140  		_, resp, err = h.gitClient.PullRequests.Get(context.Background(), internal.GitKymaProject, internal.GitKymaRepo, prID)
   141  	// handle <branch name>-<commit hash>
   142  	case strings.Contains(version, "-"):
   143  		chunks := strings.Split(version, "-")
   144  		branch, commit := strings.Join(chunks[:len(chunks)-1], "-"), chunks[len(chunks)-1]
   145  
   146  		// get diff from the branch head to commit
   147  		var diff *github.CommitsComparison
   148  		diff, resp, err = h.gitClient.Repositories.CompareCommits(context.Background(), internal.GitKymaProject, internal.GitKymaRepo, branch, commit)
   149  
   150  		// if diff contains commits, the searched commit is not on the given branch
   151  		if diff != nil && len(diff.Commits) > 0 || shouldHandle(resp) {
   152  			return fmt.Errorf("invalid Kyma version, commit %s not present on branch %s", commit, branch)
   153  		}
   154  	}
   155  
   156  	// handle iff GitHub API responded
   157  	if shouldHandle(resp) {
   158  		return fmt.Errorf("invalid Kyma version, version %s not found: %w", version, err)
   159  	}
   160  
   161  	return nil
   162  }