github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/packagetobundles/handler.go (about) 1 /* 2 * Copyright 2020 The Compass 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 // TODO: Delete after bundles are adopted 18 package packagetobundles 19 20 import ( 21 "context" 22 "crypto/sha256" 23 "encoding/json" 24 "io" 25 "net/http" 26 "net/http/httptest" 27 "regexp" 28 "strings" 29 30 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 31 "github.com/pkg/errors" 32 33 "github.com/kyma-incubator/compass/components/director/pkg/consumer" 34 35 gqlgen "github.com/99designs/gqlgen/graphql" 36 "github.com/kyma-incubator/compass/components/director/internal/domain/label" 37 "github.com/kyma-incubator/compass/components/director/internal/domain/labeldef" 38 "github.com/kyma-incubator/compass/components/director/internal/model" 39 "github.com/kyma-incubator/compass/components/director/internal/uid" 40 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 41 "github.com/kyma-incubator/compass/components/director/pkg/log" 42 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 43 "github.com/vektah/gqlparser/v2/gqlerror" 44 ) 45 46 const usesBundlesLabel = "useBundles" 47 48 // LabelUpsertService missing godoc 49 //go:generate mockery --name=LabelUpsertService --output=automock --outpkg=automock --case=underscore --disable-version-string 50 type LabelUpsertService interface { 51 UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error 52 } 53 54 // Handler missing godoc 55 type Handler struct { 56 transact persistence.Transactioner 57 labelUpsertService LabelUpsertService 58 } 59 60 // NewHandler missing godoc 61 func NewHandler(transact persistence.Transactioner) *Handler { 62 labelRepo := label.NewRepository(label.NewConverter()) 63 labelDefRepo := labeldef.NewRepository(labeldef.NewConverter()) 64 65 uidSvc := uid.NewService() 66 labelSvc := label.NewLabelService(labelRepo, labelDefRepo, uidSvc) 67 68 return &Handler{ 69 transact: transact, 70 labelUpsertService: labelSvc, 71 } 72 } 73 74 // Handler missing godoc 75 func (h *Handler) Handler() func(next http.Handler) http.Handler { 76 return func(next http.Handler) http.Handler { 77 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 78 ctx := r.Context() 79 80 reqBody, err := io.ReadAll(r.Body) 81 if err != nil { 82 log.C(ctx).WithError(err).Errorf("Error reading request body: %v", err) 83 appErr := apperrors.InternalErrorFrom(err, "while reading request body") 84 writeAppError(ctx, w, appErr) 85 return 86 } 87 88 body := string(reqBody) 89 unmodifiedBody := body 90 91 // rewrite Query/Mutation names 92 body = strings.ReplaceAll(body, "packageByInstanceAuth", "bundleByInstanceAuth") 93 body = strings.ReplaceAll(body, "packageInstanceAuth", "bundleInstanceAuth") 94 body = strings.ReplaceAll(body, "addPackage", "addBundle") 95 body = strings.ReplaceAll(body, "updatePackage", "updateBundle") 96 body = strings.ReplaceAll(body, "deletePackage", "deleteBundle") 97 body = strings.ReplaceAll(body, "addAPIDefinitionToPackage", "addAPIDefinitionToBundle") 98 body = strings.ReplaceAll(body, "addEventDefinitionToPackage", "addEventDefinitionToBundle") 99 body = strings.ReplaceAll(body, "addDocumentToPackage", "addDocumentToBundle") 100 body = strings.ReplaceAll(body, "setPackageInstanceAuth", "setBundleInstanceAuth") 101 body = strings.ReplaceAll(body, "deletePackageInstanceAuth", "deleteBundleInstanceAuth") 102 body = strings.ReplaceAll(body, "requestPackageInstanceAuthCreation", "requestBundleInstanceAuthCreation") 103 body = strings.ReplaceAll(body, "requestPackageInstanceAuthDeletion", "requestBundleInstanceAuthDeletion") 104 105 // rewrite Query/Mutation arguments 106 body = strings.ReplaceAll(body, "packageID", "bundleID") 107 body = strings.ReplaceAll(body, "PackageCreateInput", "BundleCreateInput") 108 body = strings.ReplaceAll(body, "PackageInstanceAuthRequestInput", "BundleInstanceAuthRequestInput") 109 body = strings.ReplaceAll(body, "PackageInstanceAuthSetInput", "BundleInstanceAuthSetInput") 110 body = strings.ReplaceAll(body, "PackageInstanceAuthStatusInput", "BundleInstanceAuthStatusInput") 111 body = strings.ReplaceAll(body, "PackageUpdateInput", "BundleUpdateInput") 112 113 // rewrite JSON input 114 reqPackagesJSONPattern := regexp.MustCompile(`([\s\\n]*)packages([\s\\n]*:[\s\\n]*\[)`) // matches ` packages: [` 115 body = reqPackagesJSONPattern.ReplaceAllString(body, "${1}bundles${2}") 116 117 // rewrite GQL output 118 reqPackagesGraphQLPattern := regexp.MustCompile(`([\s\\n]*)packages([\s\\n]*\{)`) // matches ` packages {` 119 body = reqPackagesGraphQLPattern.ReplaceAllString(body, "${1}bundles${2}") 120 121 reqPackageGraphQLPattern := regexp.MustCompile(`([\s\\n]*)package([\s\\n]*\([\s\\n]*id[\s\\n]*:[\s\\n]*)`) // matches ` package ( id : ` 122 body = reqPackageGraphQLPattern.ReplaceAllString(body, "${1}bundle${2}") 123 124 reqPackageModeGraphQLPattern := regexp.MustCompile(`([\s\\n]*)mode([\s\\n]*):([\s\\n]*)PACKAGE([\s\\n]*)`) // matches ` mode: PACKAGE ` 125 body = reqPackageModeGraphQLPattern.ReplaceAllString(body, "${1}mode${2}:${3}BUNDLE${4}") 126 127 r.Body = io.NopCloser(strings.NewReader(body)) 128 r.ContentLength = int64(len(body)) 129 130 usingBundles := unmodifiedBody == body 131 if usingBundles { 132 consumerInfo, err := consumer.LoadFromContext(ctx) 133 if err != nil { 134 log.C(ctx).WithError(err).Errorf("Error determining request consumer: %v", err) 135 appErr := apperrors.InternalErrorFrom(err, "while determining request consumer") 136 writeAppError(ctx, w, appErr) 137 return 138 } 139 140 log.C(ctx).Infof("Will proceed without rewriting the request body. Bundles are adopted for consumer with ID REDACTED_%x and type %q", sha256.Sum256([]byte(consumerInfo.ConsumerID)), consumerInfo.ConsumerType) 141 142 next.ServeHTTP(w, r) 143 144 if strings.Contains(strings.ToLower(body), "bundle") && 145 (consumerInfo.ConsumerType == consumer.Runtime || consumerInfo.ConsumerType == consumer.ExternalCertificate) { 146 if err := h.labelRuntimeWithBundlesParam(ctx, consumerInfo); err != nil { 147 log.C(ctx).WithError(err).Errorf("Error labelling runtime with %q: %v", usesBundlesLabel, err) 148 } 149 } 150 151 return 152 } 153 log.C(ctx).Info("Will rewrite the request body. Bundles are still not adopted") 154 155 recorder := httptest.NewRecorder() 156 next.ServeHTTP(recorder, r) 157 158 for key, values := range recorder.Header() { 159 for _, v := range values { 160 w.Header().Add(key, v) 161 } 162 } 163 164 respBody, err := io.ReadAll(recorder.Body) 165 if err != nil { 166 log.C(ctx).WithError(err).Errorf("Error reading response body: %v", err) 167 appErr := apperrors.InternalErrorFrom(err, "while reading response body") 168 writeAppError(ctx, w, appErr) 169 return 170 } 171 172 body = string(respBody) 173 // rewrite Query/Mutation names 174 body = strings.ReplaceAll(body, "bundleByInstanceAuth", "packageByInstanceAuth") 175 body = strings.ReplaceAll(body, "bundleInstanceAuth", "packageInstanceAuth") 176 body = strings.ReplaceAll(body, "addBundle", "addPackage") 177 body = strings.ReplaceAll(body, "updateBundle", "updatePackage") 178 body = strings.ReplaceAll(body, "deleteBundle", "deletePackage") 179 body = strings.ReplaceAll(body, "addAPIDefinitionToBundle", "addAPIDefinitionToPackage") 180 body = strings.ReplaceAll(body, "addEventDefinitionToBundle", "addEventDefinitionToPackage") 181 body = strings.ReplaceAll(body, "addDocumentToBundle", "addDocumentToPackage") 182 body = strings.ReplaceAll(body, "setBundleInstanceAuth", "setPackageInstanceAuth") 183 body = strings.ReplaceAll(body, "deleteBundleInstanceAuth", "deletePackageInstanceAuth") 184 body = strings.ReplaceAll(body, "requestBundleInstanceAuthCreation", "requestPackageInstanceAuthCreation") 185 body = strings.ReplaceAll(body, "requestBundleInstanceAuthDeletion", "requestPackageInstanceAuthDeletion") 186 187 respPackagesJSONPattern := regexp.MustCompile(`([\s\\n]*\")bundles(\"[\s\\n]*:[\s\\n]*\{)`) // matches ` "bundles": {` 188 body = respPackagesJSONPattern.ReplaceAllString(body, "${1}packages${2}") 189 190 respPackageJSONPattern := regexp.MustCompile(`([\s\\n]*\")bundle(\"[\s\\n]*:[\s\\n]*\{)`) // matches ` "bundle": {` 191 body = respPackageJSONPattern.ReplaceAllString(body, "${1}package${2}") 192 193 respPackageModeGraphQLPattern := regexp.MustCompile(`([\s\\n]*\")mode(\"[\s\\n]*):([\s\\n]*\")BUNDLE(\"[\s\\n]*)`) // matches ` "mode": "BUNDLE" ` 194 body = respPackageModeGraphQLPattern.ReplaceAllString(body, "${1}mode${2}:${3}PACKAGE${4}") 195 196 w.WriteHeader(recorder.Code) 197 if _, err := w.Write([]byte(body)); err != nil { 198 log.C(ctx).WithError(err).Errorf("Error writing response body: %v", err) 199 appErr := apperrors.InternalErrorFrom(err, "while writing response body") 200 writeAppError(ctx, w, appErr) 201 return 202 } 203 }) 204 } 205 } 206 207 func (h *Handler) labelRuntimeWithBundlesParam(ctx context.Context, consumerInfo consumer.Consumer) error { 208 tenantID, err := tenant.LoadFromContext(ctx) 209 if err != nil { 210 return errors.Wrap(err, "while determining request tenant") 211 } 212 213 tx, err := h.transact.Begin() 214 if err != nil { 215 return errors.Wrap(err, "while opening db transaction") 216 } 217 defer h.transact.RollbackUnlessCommitted(ctx, tx) 218 219 ctx = persistence.SaveToContext(ctx, tx) 220 221 log.C(ctx).Infof("Proceeding with labeling runtime with ID %q with label %q", consumerInfo.ConsumerID, usesBundlesLabel) 222 if err := h.labelUpsertService.UpsertLabel(ctx, tenantID, &model.LabelInput{ 223 Key: usesBundlesLabel, 224 Value: "true", 225 ObjectID: consumerInfo.ConsumerID, 226 ObjectType: model.LabelableObject(consumerInfo.ConsumerType), 227 }); err != nil { 228 return errors.Wrapf(err, "while upserting %q label", usesBundlesLabel) 229 } 230 231 if err = tx.Commit(); err != nil { 232 return errors.Wrap(err, "while committing database transaction") 233 } 234 235 return nil 236 } 237 238 func writeAppError(ctx context.Context, w http.ResponseWriter, appErr error) { 239 errCode := apperrors.ErrorCode(appErr) 240 if errCode == apperrors.UnknownError || errCode == apperrors.InternalError { 241 errCode = apperrors.InternalError 242 } 243 244 w.WriteHeader(http.StatusInternalServerError) 245 w.Header().Set("Content-Type", "application/json") 246 resp := gqlgen.Response{Errors: []*gqlerror.Error{{ 247 Message: appErr.Error(), 248 Extensions: map[string]interface{}{"error_code": errCode, "error": errCode.String()}}}} 249 err := json.NewEncoder(w).Encode(resp) 250 if err != nil { 251 log.C(ctx).WithError(err).Errorf("An error occurred while encoding data: %v", err) 252 } 253 }