github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/service/batch.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package service 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 24 "github.com/freiheit-com/kuberpult/pkg/grpc" 25 "github.com/freiheit-com/kuberpult/pkg/valid" 26 27 api "github.com/freiheit-com/kuberpult/pkg/api/v1" 28 "github.com/freiheit-com/kuberpult/pkg/auth" 29 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/config" 30 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository" 31 "google.golang.org/grpc/codes" 32 "google.golang.org/grpc/status" 33 ) 34 35 type BatchServerConfig struct { 36 WriteCommitData bool 37 } 38 39 type BatchServer struct { 40 Repository repository.Repository 41 RBACConfig auth.RBACConfig 42 Config BatchServerConfig 43 } 44 45 // see maxBatchActions in store.tsx 46 const maxBatchActions int = 100 47 48 func ValidateEnvironmentLock( 49 actionType string, // "create" | "delete" 50 env string, 51 id string, 52 ) error { 53 if !valid.EnvironmentName(env) { 54 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot %s environment lock: invalid environment: '%s'", actionType, env)) 55 } 56 if !valid.LockId(id) { 57 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot %s environment lock: invalid lock id: '%s'", actionType, id)) 58 } 59 return nil 60 } 61 62 func ValidateEnvironmentApplicationLock( 63 actionType string, // "create" | "delete" 64 env string, 65 app string, 66 id string, 67 ) error { 68 if !valid.EnvironmentName(env) { 69 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot %s environment application lock: invalid environment: '%s'", actionType, env)) 70 } 71 if !valid.ApplicationName(app) { 72 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot %s environment application lock: invalid application: '%s'", actionType, app)) 73 } 74 if !valid.LockId(id) { 75 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot %s environment application lock: invalid lock id: '%s'", actionType, id)) 76 } 77 return nil 78 } 79 80 func ValidateDeployment( 81 env string, 82 app string, 83 ) error { 84 if !valid.EnvironmentName(env) { 85 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot deploy environment application lock: invalid environment: '%s'", env)) 86 } 87 if !valid.ApplicationName(app) { 88 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot deploy environment application lock: invalid application: '%s'", app)) 89 } 90 return nil 91 } 92 93 func ValidateApplication( 94 app string, 95 ) error { 96 if !valid.ApplicationName(app) { 97 return status.Error(codes.InvalidArgument, fmt.Sprintf("cannot create undeploy version: invalid application: '%s'", app)) 98 } 99 return nil 100 } 101 102 func (d *BatchServer) processAction( 103 batchAction *api.BatchAction, 104 ) (repository.Transformer, *api.BatchResult, error) { 105 switch action := batchAction.Action.(type) { 106 case *api.BatchAction_CreateEnvironmentLock: 107 act := action.CreateEnvironmentLock 108 if err := ValidateEnvironmentLock("create", act.Environment, act.LockId); err != nil { 109 return nil, nil, err 110 } 111 return &repository.CreateEnvironmentLock{ 112 Environment: act.Environment, 113 LockId: act.LockId, 114 Message: act.Message, 115 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 116 }, nil, nil 117 case *api.BatchAction_DeleteEnvironmentLock: 118 act := action.DeleteEnvironmentLock 119 if err := ValidateEnvironmentLock("delete", act.Environment, act.LockId); err != nil { 120 return nil, nil, err 121 } 122 return &repository.DeleteEnvironmentLock{ 123 Environment: act.Environment, 124 LockId: act.LockId, 125 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 126 }, nil, nil 127 case *api.BatchAction_CreateEnvironmentApplicationLock: 128 act := action.CreateEnvironmentApplicationLock 129 if err := ValidateEnvironmentApplicationLock("create", act.Environment, act.Application, act.LockId); err != nil { 130 return nil, nil, err 131 } 132 return &repository.CreateEnvironmentApplicationLock{ 133 Environment: act.Environment, 134 Application: act.Application, 135 LockId: act.LockId, 136 Message: act.Message, 137 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 138 }, nil, nil 139 case *api.BatchAction_DeleteEnvironmentApplicationLock: 140 act := action.DeleteEnvironmentApplicationLock 141 if err := ValidateEnvironmentApplicationLock("delete", act.Environment, act.Application, act.LockId); err != nil { 142 return nil, nil, err 143 } 144 return &repository.DeleteEnvironmentApplicationLock{ 145 Environment: act.Environment, 146 Application: act.Application, 147 LockId: act.LockId, 148 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 149 }, nil, nil 150 case *api.BatchAction_PrepareUndeploy: 151 act := action.PrepareUndeploy 152 if err := ValidateApplication(act.Application); err != nil { 153 return nil, nil, err 154 } 155 return &repository.CreateUndeployApplicationVersion{ 156 Application: act.Application, 157 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 158 WriteCommitData: d.Config.WriteCommitData, 159 }, nil, nil 160 case *api.BatchAction_Undeploy: 161 act := action.Undeploy 162 if err := ValidateApplication(act.Application); err != nil { 163 return nil, nil, err 164 } 165 return &repository.UndeployApplication{ 166 Application: act.Application, 167 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 168 }, nil, nil 169 case *api.BatchAction_Deploy: 170 act := action.Deploy 171 if err := ValidateDeployment(act.Environment, act.Application); err != nil { 172 return nil, nil, err 173 } 174 b := act.LockBehavior 175 if act.IgnoreAllLocks { //nolint: staticcheck 176 // the UI currently sets this to true, 177 // in that case, we still want to ignore locks (for emergency deployments) 178 b = api.LockBehavior_IGNORE 179 } 180 return &repository.DeployApplicationVersion{ 181 SourceTrain: nil, 182 Environment: act.Environment, 183 Application: act.Application, 184 Version: act.Version, 185 LockBehaviour: b, 186 WriteCommitData: d.Config.WriteCommitData, 187 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 188 }, nil, nil 189 case *api.BatchAction_DeleteEnvFromApp: 190 act := action.DeleteEnvFromApp 191 return &repository.DeleteEnvFromApp{ 192 Environment: act.Environment, 193 Application: act.Application, 194 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 195 }, nil, nil 196 case *api.BatchAction_ReleaseTrain: 197 in := action.ReleaseTrain 198 if !valid.EnvironmentName(in.Target) { 199 return nil, nil, status.Error(codes.InvalidArgument, "invalid environment") 200 } 201 if in.Team != "" && !valid.TeamName(in.Team) { 202 return nil, nil, status.Error(codes.InvalidArgument, "invalid Team name") 203 } 204 return &repository.ReleaseTrain{ 205 Repo: d.Repository, 206 Target: in.Target, 207 Team: in.Team, 208 CommitHash: in.CommitHash, 209 WriteCommitData: d.Config.WriteCommitData, 210 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 211 }, &api.BatchResult{ 212 Result: &api.BatchResult_ReleaseTrain{ 213 ReleaseTrain: &api.ReleaseTrainResponse{Target: in.Target, Team: in.Team}, 214 }, 215 }, nil 216 case *api.BatchAction_CreateRelease: 217 in := action.CreateRelease 218 response := api.CreateReleaseResponseSuccess{} 219 return &repository.CreateApplicationVersion{ 220 Version: in.Version, 221 Application: in.Application, 222 Manifests: in.Manifests, 223 SourceCommitId: in.SourceCommitId, 224 SourceAuthor: in.SourceAuthor, 225 SourceMessage: in.SourceMessage, 226 SourceRepoUrl: in.SourceRepoUrl, 227 PreviousCommit: in.PreviousCommitId, 228 NextCommit: in.NextCommitId, 229 Team: in.Team, 230 DisplayVersion: in.DisplayVersion, 231 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 232 WriteCommitData: d.Config.WriteCommitData, 233 }, &api.BatchResult{ 234 Result: &api.BatchResult_CreateReleaseResponse{ 235 CreateReleaseResponse: &api.CreateReleaseResponse{ 236 Response: &api.CreateReleaseResponse_Success{ 237 Success: &response, 238 }, 239 }, 240 }, 241 }, nil 242 case *api.BatchAction_CreateEnvironment: 243 in := action.CreateEnvironment 244 conf := in.Config 245 if conf == nil { 246 //exhaustruct:ignore 247 conf = &api.EnvironmentConfig{} 248 } 249 var argocd *config.EnvironmentConfigArgoCd 250 if conf.Argocd != nil { 251 syncWindows := transformSyncWindowsToConfig(conf.Argocd.SyncWindows) 252 clusterResourceWhitelist := transformAccessListToConfig(conf.Argocd.AccessList) 253 ignoreDifferences := transformIgnoreDifferencesToConfig(conf.Argocd.IgnoreDifferences) 254 argocd = &config.EnvironmentConfigArgoCd{ 255 Destination: transformDestinationToConfig(conf.Argocd.Destination), 256 SyncWindows: syncWindows, 257 ClusterResourceWhitelist: clusterResourceWhitelist, 258 ApplicationAnnotations: conf.Argocd.ApplicationAnnotations, 259 IgnoreDifferences: ignoreDifferences, 260 SyncOptions: conf.Argocd.SyncOptions, 261 } 262 } 263 upstream := transformUpstreamToConfig(conf.Upstream) 264 transformer := &repository.CreateEnvironment{ 265 Environment: in.Environment, 266 Config: config.EnvironmentConfig{ 267 Upstream: upstream, 268 ArgoCd: argocd, 269 EnvironmentGroup: conf.EnvironmentGroup, 270 }, 271 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 272 } 273 return transformer, nil, nil 274 case *api.BatchAction_CreateEnvironmentGroupLock: 275 act := action.CreateEnvironmentGroupLock 276 return &repository.CreateEnvironmentGroupLock{ 277 EnvironmentGroup: act.EnvironmentGroup, 278 LockId: act.LockId, 279 Message: act.Message, 280 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 281 }, nil, nil 282 case *api.BatchAction_DeleteEnvironmentGroupLock: 283 act := action.DeleteEnvironmentGroupLock 284 return &repository.DeleteEnvironmentGroupLock{ 285 EnvironmentGroup: act.EnvironmentGroup, 286 LockId: act.LockId, 287 Authentication: repository.Authentication{RBACConfig: d.RBACConfig}, 288 }, nil, nil 289 } 290 return nil, nil, status.Error(codes.InvalidArgument, "processAction: cannot process action: invalid action type") 291 } 292 293 func (d *BatchServer) ProcessBatch( 294 ctx context.Context, 295 in *api.BatchRequest, 296 ) (*api.BatchResponse, error) { 297 user, err := auth.ReadUserFromContext(ctx) 298 if err != nil { 299 return nil, grpc.AuthError(ctx, fmt.Errorf("batch requires user to be provided %v", err)) 300 } 301 ctx = auth.WriteUserToContext(ctx, *user) 302 if len(in.GetActions()) > maxBatchActions { 303 return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("cannot process batch: too many actions. limit is %d", maxBatchActions)) 304 } 305 306 results := make([]*api.BatchResult, 0, len(in.GetActions())) 307 transformers := make([]repository.Transformer, 0, maxBatchActions) 308 for _, batchAction := range in.GetActions() { 309 transformer, result, err := d.processAction(batchAction) 310 if err != nil { 311 // Validation error 312 return nil, err 313 } 314 transformers = append(transformers, transformer) 315 results = append(results, result) 316 } 317 err = d.Repository.Apply(ctx, transformers...) 318 if err != nil { 319 var applyErr *repository.TransformerBatchApplyError 320 if errors.Is(err, repository.ErrQueueFull) { 321 return nil, status.Error(codes.ResourceExhausted, fmt.Sprintf("Could not process ProcessBatch request. Err: %s", err.Error())) 322 } 323 324 if !errors.As(err, &applyErr) { 325 return nil, err 326 } 327 328 switch transformerError := applyErr.TransformerError.(type) { 329 case *repository.CreateReleaseError: 330 { 331 errorResults := make([]*api.BatchResult, 1) 332 errorResults[0] = &api.BatchResult{ 333 Result: &api.BatchResult_CreateReleaseResponse{ 334 CreateReleaseResponse: transformerError.Response(), 335 }, 336 } 337 return &api.BatchResponse{Results: errorResults}, nil 338 } 339 default: 340 return nil, err 341 } 342 } 343 return &api.BatchResponse{Results: results}, nil 344 } 345 346 var _ api.BatchServiceServer = (*BatchServer)(nil)