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  }