github.com/kyma-project/kyma-environment-broker@v0.0.1/cmd/deprovisionretrigger/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "os" 8 9 "github.com/kyma-project/kyma-environment-broker/internal" 10 11 "github.com/kyma-project/control-plane/components/schema-migrator/cleaner" 12 "github.com/kyma-project/kyma-environment-broker/internal/broker" 13 "github.com/kyma-project/kyma-environment-broker/internal/events" 14 "github.com/kyma-project/kyma-environment-broker/internal/storage" 15 "github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel" 16 log "github.com/sirupsen/logrus" 17 "github.com/vrischmann/envconfig" 18 ) 19 20 type BrokerClient interface { 21 Deprovision(instance internal.Instance) (string, error) 22 GetInstanceRequest(instanceID string) (*http.Response, error) 23 } 24 25 type Config struct { 26 Database storage.Config 27 Broker broker.ClientConfig 28 DryRun bool `envconfig:"default=true"` 29 } 30 31 type DeprovisionRetriggerService struct { 32 cfg Config 33 filter dbmodel.InstanceFilter 34 instanceStorage storage.Instances 35 brokerClient BrokerClient 36 } 37 38 func main() { 39 log.SetFormatter(&log.JSONFormatter{}) 40 log.Info("Starting deprovision retrigger job!") 41 42 // create and fill config 43 var cfg Config 44 err := envconfig.InitWithPrefix(&cfg, "APP") 45 fatalOnError(err) 46 47 if cfg.DryRun { 48 log.Info("Dry run only - no changes") 49 } 50 51 ctx := context.Background() 52 brokerClient := broker.NewClient(ctx, cfg.Broker) 53 54 // create storage connection 55 cipher := storage.NewEncrypter(cfg.Database.SecretKey) 56 db, conn, err := storage.NewFromConfig(cfg.Database, events.Config{}, cipher, log.WithField("service", "storage")) 57 fatalOnError(err) 58 svc := newDeprovisionRetriggerService(cfg, brokerClient, db.Instances()) 59 60 err = svc.PerformCleanup() 61 62 fatalOnError(err) 63 64 log.Info("Deprovision retrigger job finished successfully!") 65 err = conn.Close() 66 if err != nil { 67 fatalOnError(err) 68 } 69 70 cleaner.HaltIstioSidecar() 71 // do not use defer, close must be done before halting 72 err = cleaner.Halt() 73 fatalOnError(err) 74 } 75 76 func newDeprovisionRetriggerService(cfg Config, brokerClient BrokerClient, instances storage.Instances) *DeprovisionRetriggerService { 77 return &DeprovisionRetriggerService{ 78 cfg: cfg, 79 instanceStorage: instances, 80 brokerClient: brokerClient, 81 } 82 } 83 84 func (s *DeprovisionRetriggerService) PerformCleanup() error { 85 notCompletelyDeletedFilter := dbmodel.InstanceFilter{DeletionAttempted: &[]bool{true}[0]} 86 instancesToDeprovisionAgain, _, _, err := s.instanceStorage.List(notCompletelyDeletedFilter) 87 88 if err != nil { 89 log.Error(fmt.Sprintf("while getting not completely deprovisioned instances: %s", err)) 90 return err 91 } 92 93 if s.cfg.DryRun { 94 s.logInstances(instancesToDeprovisionAgain) 95 log.Infof("Instances to retrigger deprovisioning: %d", len(instancesToDeprovisionAgain)) 96 } else { 97 failuresCount, sanityFailedCount := s.retriggerDeprovisioningForInstances(instancesToDeprovisionAgain) 98 deprovisioningAccepted := len(instancesToDeprovisionAgain) - failuresCount - sanityFailedCount 99 log.Infof("Out of %d instances to retrigger deprovisioning: accepted requests = %d, skipped due to sanity failed = %d, failed requests = %d", 100 len(instancesToDeprovisionAgain), deprovisioningAccepted, sanityFailedCount, failuresCount) 101 } 102 103 return nil 104 } 105 106 func (s *DeprovisionRetriggerService) retriggerDeprovisioningForInstances(instances []internal.Instance) (int, int) { 107 var failuresCount int 108 var sanityFailedCount int 109 for _, instance := range instances { 110 // sanity check - if the instance is visible we shall not trigger deprovisioning 111 notFound := s.getInstanceReturned404(instance.InstanceID) 112 if notFound { 113 err := s.deprovisionInstance(instance) 114 if err != nil { 115 // just counting, logging and ignoring errors 116 failuresCount += 1 117 } 118 } else { 119 sanityFailedCount += 1 120 } 121 } 122 return failuresCount, sanityFailedCount 123 } 124 125 func (s *DeprovisionRetriggerService) deprovisionInstance(instance internal.Instance) (err error) { 126 log.Infof("About to deprovision instance for instanceId: %+v", instance.InstanceID) 127 operationId, err := s.brokerClient.Deprovision(instance) 128 if err != nil { 129 log.Error(fmt.Sprintf("while sending deprovision request for instance ID %s: %s", instance.InstanceID, err)) 130 return err 131 } 132 log.Infof("Deprovision instance for instanceId: %s accepted, operationId: %s", instance.InstanceID, operationId) 133 return nil 134 } 135 136 // Sanity check - instance is supposed to be not visible via API. Call should return 404 - NotFound 137 func (s *DeprovisionRetriggerService) getInstanceReturned404(instanceID string) bool { 138 response, err := s.brokerClient.GetInstanceRequest(instanceID) 139 if err != nil || response == nil { 140 log.Error(fmt.Sprintf("while trying to GET instance resource for %s: %s", instanceID, err)) 141 return false 142 } 143 if response.StatusCode != http.StatusNotFound { 144 log.Error(fmt.Sprintf("unexpextedly GET instance resource for %s: returned %s", instanceID, http.StatusText(response.StatusCode))) 145 return false 146 } 147 return true 148 } 149 150 func (s *DeprovisionRetriggerService) logInstances(instances []internal.Instance) { 151 for _, instance := range instances { 152 notFound := s.getInstanceReturned404(instance.InstanceID) 153 if notFound { 154 log.Infof("instanceId: %s, createdAt: %+v, deletedAt %+v, GET retured: NotFound", instance.InstanceID, instance.CreatedAt, instance.DeletedAt) 155 } else { 156 log.Infof("instanceId: %s, createdAt: %+v, deletedAt %+v, GET retured: unexpected result", instance.InstanceID, instance.CreatedAt, instance.DeletedAt) 157 } 158 } 159 } 160 161 func fatalOnError(err error) { 162 if err != nil { 163 // exit with 0 to avoid any side effects - we ignore all errors only logging those 164 log.Error(err) 165 os.Exit(0) 166 } 167 }