github.com/kyma-project/kyma-environment-broker@v0.0.1/cmd/trialcleanup/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "time" 8 9 "github.com/kyma-project/control-plane/components/schema-migrator/cleaner" 10 "github.com/kyma-project/kyma-environment-broker/internal" 11 "github.com/kyma-project/kyma-environment-broker/internal/broker" 12 "github.com/kyma-project/kyma-environment-broker/internal/events" 13 "github.com/kyma-project/kyma-environment-broker/internal/storage" 14 "github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel" 15 log "github.com/sirupsen/logrus" 16 "github.com/vrischmann/envconfig" 17 ) 18 19 const ( 20 trialPlanID = broker.TrialPlanID 21 ) 22 23 type BrokerClient interface { 24 SendExpirationRequest(instance internal.Instance) (bool, error) 25 } 26 27 type Config struct { 28 Database storage.Config 29 Broker broker.ClientConfig 30 DryRun bool `envconfig:"default=true"` 31 ExpirationPeriod time.Duration `envconfig:"default=336h"` 32 } 33 34 type TrialCleanupService struct { 35 cfg Config 36 filter dbmodel.InstanceFilter 37 instanceStorage storage.Instances 38 brokerClient BrokerClient 39 } 40 41 type instancePredicate func(internal.Instance) bool 42 43 func main() { 44 log.SetFormatter(&log.JSONFormatter{}) 45 log.Info("Starting trial cleanup job") 46 47 // create and fill config 48 var cfg Config 49 err := envconfig.InitWithPrefix(&cfg, "APP") 50 fatalOnError(err) 51 52 if cfg.DryRun { 53 log.Info("Dry run only - no changes") 54 } 55 56 log.Infof("Expiration period: %+v", cfg.ExpirationPeriod) 57 58 ctx := context.Background() 59 brokerClient := broker.NewClient(ctx, cfg.Broker) 60 61 // create storage connection 62 cipher := storage.NewEncrypter(cfg.Database.SecretKey) 63 db, conn, err := storage.NewFromConfig(cfg.Database, events.Config{}, cipher, log.WithField("service", "storage")) 64 fatalOnError(err) 65 svc := newTrialCleanupService(cfg, brokerClient, db.Instances()) 66 67 err = svc.PerformCleanup() 68 69 fatalOnError(err) 70 71 log.Info("Trial cleanup job finished successfully!") 72 73 err = conn.Close() 74 if err != nil { 75 fatalOnError(err) 76 } 77 78 cleaner.HaltIstioSidecar() 79 // do not use defer, close must be done before halting 80 err = cleaner.Halt() 81 fatalOnError(err) 82 } 83 84 func newTrialCleanupService(cfg Config, brokerClient BrokerClient, instances storage.Instances) *TrialCleanupService { 85 return &TrialCleanupService{ 86 cfg: cfg, 87 instanceStorage: instances, 88 brokerClient: brokerClient, 89 } 90 } 91 92 func (s *TrialCleanupService) PerformCleanup() error { 93 94 trialInstancesFilter := dbmodel.InstanceFilter{PlanIDs: []string{trialPlanID}} 95 trialInstances, trialInstancesCount, err := s.getInstances(trialInstancesFilter) 96 97 if err != nil { 98 log.Error(fmt.Sprintf("while getting trial instances: %s", err)) 99 return err 100 } 101 102 instancesToExpire, instancesToExpireCount := s.filterInstances( 103 trialInstances, 104 func(instance internal.Instance) bool { return time.Since(instance.CreatedAt) >= s.cfg.ExpirationPeriod }, 105 ) 106 107 instancesToBeLeftCount := trialInstancesCount - instancesToExpireCount 108 109 if s.cfg.DryRun { 110 s.logInstances(instancesToExpire) 111 log.Infof("Trials: %+v, to expire now: %+v, to be left non-expired: %+v", trialInstancesCount, instancesToExpireCount, instancesToBeLeftCount) 112 } else { 113 suspensionsAcceptedCount, onlyMarkedAsExpiredCount, failuresCount := s.cleanupInstances(instancesToExpire) 114 log.Infof("Trials: %+v, to expire: %+v, left non-expired: %+v, suspension under way: %+v just marked expired: %+v, failures: %+v", trialInstancesCount, instancesToExpireCount, instancesToBeLeftCount, suspensionsAcceptedCount, onlyMarkedAsExpiredCount, failuresCount) 115 } 116 return nil 117 } 118 119 func (s *TrialCleanupService) getInstances(filter dbmodel.InstanceFilter) ([]internal.Instance, int, error) { 120 121 instances, _, totalCount, err := s.instanceStorage.List(filter) 122 if err != nil { 123 return []internal.Instance{}, 0, err 124 } 125 126 return instances, totalCount, nil 127 } 128 129 func (s *TrialCleanupService) filterInstances(instances []internal.Instance, filter instancePredicate) ([]internal.Instance, int) { 130 var filteredInstances []internal.Instance 131 for _, instance := range instances { 132 if filter(instance) { 133 filteredInstances = append(filteredInstances, instance) 134 } 135 } 136 return filteredInstances, len(filteredInstances) 137 } 138 139 func (s *TrialCleanupService) cleanupInstances(instances []internal.Instance) (int, int, int) { 140 var suspensionAccepted int 141 var onlyExpirationMarked int 142 totalInstances := len(instances) 143 for _, instance := range instances { 144 suspensionUnderWay, err := s.expireInstance(instance) 145 if err != nil { 146 // ignoring errors - only logging 147 log.Error(fmt.Sprintf("while sending expiration request for instanceID: %s, error: %s", instance.InstanceID, err)) 148 continue 149 } 150 if suspensionUnderWay { 151 suspensionAccepted += 1 152 } else { 153 onlyExpirationMarked += 1 154 } 155 } 156 failures := totalInstances - suspensionAccepted - onlyExpirationMarked 157 return suspensionAccepted, onlyExpirationMarked, failures 158 } 159 160 func (s *TrialCleanupService) logInstances(instances []internal.Instance) { 161 for _, instance := range instances { 162 log.Infof("instanceId: %+v createdAt: %+v (%.0f days ago) servicePlanID: %+v servicePlanName: %+v", 163 instance.InstanceID, instance.CreatedAt, time.Since(instance.CreatedAt).Hours()/24, instance.ServicePlanID, instance.ServicePlanName) 164 } 165 } 166 167 func (s *TrialCleanupService) expireInstance(instance internal.Instance) (processed bool, err error) { 168 log.Infof("About to make instance expired for instanceID: %+v", instance.InstanceID) 169 suspensionUnderWay, err := s.brokerClient.SendExpirationRequest(instance) 170 if err != nil { 171 log.Error(fmt.Sprintf("while sending expiration request for instanceID %q: %s", instance.InstanceID, err)) 172 return suspensionUnderWay, err 173 } 174 return suspensionUnderWay, nil 175 } 176 177 func fatalOnError(err error) { 178 if err != nil { 179 // temporarily we exit with 0 to avoid any side effects - we ignore all errors only logging those 180 //log.Fatal(err) 181 log.Error(err) 182 os.Exit(0) 183 } 184 }