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(¶ms) 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(¶ms) 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 }