github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/cmd/operations-manager/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"net/http"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationconstraint/operators"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/internal/domain/api"
    13  	"github.com/kyma-incubator/compass/components/director/internal/domain/application"
    14  	"github.com/kyma-incubator/compass/components/director/internal/domain/apptemplate"
    15  	"github.com/kyma-incubator/compass/components/director/internal/domain/auth"
    16  	bundleutil "github.com/kyma-incubator/compass/components/director/internal/domain/bundle"
    17  	"github.com/kyma-incubator/compass/components/director/internal/domain/bundlereferences"
    18  	"github.com/kyma-incubator/compass/components/director/internal/domain/document"
    19  	"github.com/kyma-incubator/compass/components/director/internal/domain/eventdef"
    20  	"github.com/kyma-incubator/compass/components/director/internal/domain/fetchrequest"
    21  	"github.com/kyma-incubator/compass/components/director/internal/domain/formation"
    22  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationassignment"
    23  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationconstraint"
    24  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationtemplate"
    25  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationtemplateconstraintreferences"
    26  	"github.com/kyma-incubator/compass/components/director/internal/domain/integrationsystem"
    27  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    28  	"github.com/kyma-incubator/compass/components/director/internal/domain/labeldef"
    29  	"github.com/kyma-incubator/compass/components/director/internal/domain/operation"
    30  	"github.com/kyma-incubator/compass/components/director/internal/domain/runtime"
    31  	runtimectx "github.com/kyma-incubator/compass/components/director/internal/domain/runtime_context"
    32  	"github.com/kyma-incubator/compass/components/director/internal/domain/scenarioassignment"
    33  	"github.com/kyma-incubator/compass/components/director/internal/domain/spec"
    34  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    35  	"github.com/kyma-incubator/compass/components/director/internal/domain/version"
    36  	"github.com/kyma-incubator/compass/components/director/internal/domain/webhook"
    37  	databuilder "github.com/kyma-incubator/compass/components/director/internal/domain/webhook/datainputbuilder"
    38  	"github.com/kyma-incubator/compass/components/director/internal/features"
    39  	operationsmanager "github.com/kyma-incubator/compass/components/director/internal/operations_manager"
    40  	"github.com/kyma-incubator/compass/components/director/internal/uid"
    41  	"github.com/kyma-incubator/compass/components/director/pkg/accessstrategy"
    42  	httputil "github.com/kyma-incubator/compass/components/director/pkg/auth"
    43  	"github.com/kyma-incubator/compass/components/director/pkg/certloader"
    44  	configprovider "github.com/kyma-incubator/compass/components/director/pkg/config"
    45  	"github.com/kyma-incubator/compass/components/director/pkg/cronjob"
    46  	"github.com/kyma-incubator/compass/components/director/pkg/executor"
    47  	"github.com/kyma-incubator/compass/components/director/pkg/normalizer"
    48  	"github.com/kyma-incubator/compass/components/director/pkg/retry"
    49  	webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client"
    50  
    51  	"github.com/kyma-incubator/compass/components/director/internal/domain/schema"
    52  	"github.com/kyma-incubator/compass/components/director/internal/healthz"
    53  
    54  	"github.com/gorilla/mux"
    55  	"github.com/kyma-incubator/compass/components/director/pkg/correlation"
    56  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    57  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    58  	"github.com/kyma-incubator/compass/components/director/pkg/signal"
    59  	"github.com/pkg/errors"
    60  	"github.com/vrischmann/envconfig"
    61  )
    62  
    63  type config struct {
    64  	Address            string        `envconfig:"default=127.0.0.1:8080"`
    65  	ShutdownTimeout    time.Duration `envconfig:"default=10s"`
    66  	ReadHeadersTimeout time.Duration `envconfig:"APP_READ_REQUEST_HEADERS_TIMEOUT,default=30s"`
    67  	ClientTimeout      time.Duration `envconfig:"APP_CLIENT_TIMEOUT,default=30s"`
    68  
    69  	ORDOpCreationJobSchedulePeriod  time.Duration `envconfig:"APP_ORD_OPERATIONS_CREATION_JOB_SCHEDULE_PERIOD,default=168h"`
    70  	ORDOpDeletionJobSchedulePeriod  time.Duration `envconfig:"APP_ORD_OPERATIONS_DELETION_JOB_SCHEDULE_PERIOD,default=24h"`
    71  	DeleteCompletedOpsOlderThanDays int           `envconfig:"APP_DELETE_COMPLETED_OPERATIONS_OLDER_THAN_DAYS,default=5"`
    72  	DeleteFailedOpsOlderThanDays    int           `envconfig:"APP_DELETE_FAILED_OPERATIONS_OLDER_THAN_DAYS,default=10"`
    73  
    74  	SkipSSLValidation               bool          `envconfig:"default=false"`
    75  	ConfigurationFileReload         time.Duration `envconfig:"default=1m"`
    76  	SelfRegisterDistinguishLabelKey string        `envconfig:"APP_SELF_REGISTER_DISTINGUISH_LABEL_KEY"`
    77  	RuntimeTypeLabelKey             string        `envconfig:"APP_RUNTIME_TYPE_LABEL_KEY,default=runtimeType"`
    78  	ApplicationTypeLabelKey         string        `envconfig:"APP_APPLICATION_TYPE_LABEL_KEY,default=applicationType"`
    79  
    80  	ORDWebhookMappings       string `envconfig:"APP_ORD_WEBHOOK_MAPPINGS"`
    81  	TenantMappingConfigPath  string `envconfig:"APP_TENANT_MAPPING_CONFIG_PATH"`
    82  	TenantMappingCallbackURL string `envconfig:"APP_TENANT_MAPPING_CALLBACK_URL"`
    83  
    84  	ExternalClientCertSecretName string `envconfig:"APP_EXTERNAL_CLIENT_CERT_SECRET_NAME"`
    85  	ExtSvcClientCertSecretName   string `envconfig:"APP_EXT_SVC_CLIENT_CERT_SECRET_NAME"`
    86  
    87  	Log               *log.Config
    88  	Database          persistence.DatabaseConfig
    89  	CertLoaderConfig  certloader.Config
    90  	ReadyConfig       healthz.ReadyConfig
    91  	RetryConfig       retry.Config
    92  	ConfigurationFile string
    93  	Features          features.Config
    94  	ElectionConfig    cronjob.ElectionConfig
    95  }
    96  
    97  func main() {
    98  	ctx, cancel := context.WithCancel(context.Background())
    99  	defer cancel()
   100  
   101  	term := make(chan os.Signal)
   102  	signal.HandleInterrupts(ctx, cancel, term)
   103  
   104  	conf := config{}
   105  	err := envconfig.InitWithPrefix(&conf, "APP")
   106  	exitOnError(err, "while reading operations-manager configuration")
   107  
   108  	ctx, err = log.Configure(ctx, conf.Log)
   109  	exitOnError(err, "while configuring logger")
   110  
   111  	transact, closeDBConn, err := persistence.Configure(ctx, conf.Database)
   112  	exitOnError(err, "Error while establishing the connection to the database")
   113  	defer func() {
   114  		err := closeDBConn()
   115  		exitOnError(err, "Error while closing the connection to the database")
   116  	}()
   117  
   118  	router := mux.NewRouter()
   119  
   120  	log.C(ctx).Info("Registering health endpoint...")
   121  	router.Use(correlation.AttachCorrelationIDToContext())
   122  	router.HandleFunc("/healthz", func(writer http.ResponseWriter, request *http.Request) {
   123  		writer.WriteHeader(http.StatusOK)
   124  	})
   125  
   126  	log.C(ctx).Info("Registering readiness endpoint...")
   127  	schemaRepo := schema.NewRepository()
   128  	ready := healthz.NewReady(transact, conf.ReadyConfig, schemaRepo)
   129  	router.HandleFunc("/readyz", healthz.NewReadinessHandler(ready))
   130  
   131  	cfgProvider := createAndRunConfigProvider(ctx, conf)
   132  
   133  	ordWebhookMapping, err := application.UnmarshalMappings(conf.ORDWebhookMappings)
   134  	exitOnError(err, "failed while unmarshalling ord webhook mappings")
   135  
   136  	tenantMappingConfig, err := apptemplate.UnmarshalTenantMappingConfig(conf.TenantMappingConfigPath)
   137  	exitOnError(err, "Error while loading Tenant mapping config")
   138  
   139  	svc, err := createOperationsManagerService(ctx, cfgProvider, transact, ordWebhookMapping, conf, tenantMappingConfig, conf.TenantMappingCallbackURL)
   140  	if err != nil {
   141  		exitOnError(err, "failed while creating operations manager service")
   142  	}
   143  
   144  	runMainSrv, shutdownMainSrv := createServer(ctx, conf, router, "main")
   145  
   146  	go func() {
   147  		<-ctx.Done()
   148  		// Interrupt signal received - shut down the servers
   149  		shutdownMainSrv()
   150  	}()
   151  
   152  	go func() {
   153  		if err := startCreateORDOperationsJob(ctx, svc, conf); err != nil {
   154  			log.C(ctx).WithError(err).Error("Failed to start create ORD operations cronjob. Stopping app...")
   155  		}
   156  		cancel()
   157  	}()
   158  
   159  	go func() {
   160  		if err := startDeleteOldORDOperationsJob(ctx, svc, conf); err != nil {
   161  			log.C(ctx).WithError(err).Error("Failed to start delete old ORD operations cronjob. Stopping app...")
   162  		}
   163  		cancel()
   164  	}()
   165  
   166  	log.C(ctx).Infof("Operations Manager has started")
   167  	runMainSrv()
   168  }
   169  
   170  func startCreateORDOperationsJob(ctx context.Context, opManager *operationsmanager.Service, cfg config) error {
   171  	job := cronjob.CronJob{
   172  		Name: "CreateORDOperations",
   173  		Fn: func(jobCtx context.Context) {
   174  			log.C(jobCtx).Infof("Starting creation of ORD operations...")
   175  			if err := opManager.CreateORDOperations(ctx); err != nil {
   176  				log.C(jobCtx).WithError(err).Errorf("error occurred while creating Open Resource Discovery operations")
   177  			}
   178  			log.C(jobCtx).Infof("Creation of ORD operations finished.")
   179  		},
   180  		SchedulePeriod: cfg.ORDOpCreationJobSchedulePeriod,
   181  	}
   182  	return cronjob.RunCronJob(ctx, cfg.ElectionConfig, job)
   183  }
   184  
   185  func startDeleteOldORDOperationsJob(ctx context.Context, opManager *operationsmanager.Service, cfg config) error {
   186  	job := cronjob.CronJob{
   187  		Name: "DeleteOldORDOperations",
   188  		Fn: func(jobCtx context.Context) {
   189  			log.C(jobCtx).Infof("Starting deletion of old ORD operations...")
   190  			if err := opManager.DeleteOldOperations(ctx, operationsmanager.OrdAggregationOpType, cfg.DeleteCompletedOpsOlderThanDays, cfg.DeleteFailedOpsOlderThanDays); err != nil {
   191  				log.C(jobCtx).WithError(err).Errorf("error occurred while deleting old Open Resource Discovery operations")
   192  			}
   193  			log.C(jobCtx).Infof("Deletion of old ORD operations finished.")
   194  		},
   195  		SchedulePeriod: cfg.ORDOpDeletionJobSchedulePeriod,
   196  	}
   197  	return cronjob.RunCronJob(ctx, cfg.ElectionConfig, job)
   198  }
   199  
   200  func createOperationsManagerService(ctx context.Context, cfgProvider *configprovider.Provider, transact persistence.Transactioner, ordWebhookMapping []application.ORDWebhookMapping, conf config, tenantMappingConfig map[string]interface{}, callbackURL string) (*operationsmanager.Service, error) {
   201  	retryHTTPExecutor := retry.NewHTTPExecutor(&conf.RetryConfig)
   202  
   203  	httpClient := &http.Client{
   204  		Timeout: conf.ClientTimeout,
   205  		Transport: &http.Transport{
   206  			TLSClientConfig: &tls.Config{
   207  				InsecureSkipVerify: conf.SkipSSLValidation,
   208  			},
   209  		},
   210  	}
   211  
   212  	certCache, err := certloader.StartCertLoader(ctx, conf.CertLoaderConfig)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	accessStrategyExecutorProviderWithoutTenant := accessstrategy.NewDefaultExecutorProvider(certCache, conf.ExternalClientCertSecretName, conf.ExtSvcClientCertSecretName)
   217  
   218  	securedHTTPClient := httputil.PrepareHTTPClientWithSSLValidation(conf.ClientTimeout, conf.SkipSSLValidation)
   219  	mtlsClient := httputil.PrepareMTLSClient(conf.ClientTimeout, certCache, conf.ExternalClientCertSecretName)
   220  	extSvcMtlsClient := httputil.PrepareMTLSClient(conf.ClientTimeout, certCache, conf.ExtSvcClientCertSecretName)
   221  	webhookClient := webhookclient.NewClient(securedHTTPClient, mtlsClient, extSvcMtlsClient)
   222  
   223  	opConv := operation.NewConverter()
   224  	tenantConverter := tenant.NewConverter()
   225  	authConverter := auth.NewConverter()
   226  	frConverter := fetchrequest.NewConverter(authConverter)
   227  	versionConverter := version.NewConverter()
   228  	docConverter := document.NewConverter(frConverter)
   229  	webhookConverter := webhook.NewConverter(authConverter)
   230  	specConverter := spec.NewConverter(frConverter)
   231  	apiConverter := api.NewConverter(versionConverter, specConverter)
   232  	eventAPIConverter := eventdef.NewConverter(versionConverter, specConverter)
   233  	bundleConverter := bundleutil.NewConverter(authConverter, apiConverter, eventAPIConverter, docConverter)
   234  	appConverter := application.NewConverter(webhookConverter, bundleConverter)
   235  	runtimeConverter := runtime.NewConverter(webhookConverter)
   236  	labelConverter := label.NewConverter()
   237  	intSysConverter := integrationsystem.NewConverter()
   238  	labelDefConverter := labeldef.NewConverter()
   239  	formationTemplateConverter := formationtemplate.NewConverter(webhookConverter)
   240  	formationConstraintConverter := formationconstraint.NewConverter()
   241  	appTemplateConverter := apptemplate.NewConverter(appConverter, webhookConverter)
   242  	formationConv := formation.NewConverter()
   243  	runtimeContextConv := runtimectx.NewConverter()
   244  	bundleReferenceConv := bundlereferences.NewConverter()
   245  	assignmentConv := scenarioassignment.NewConverter()
   246  	formationAssignmentConv := formationassignment.NewConverter()
   247  	formationTemplateConstraintReferencesConverter := formationtemplateconstraintreferences.NewConverter()
   248  
   249  	opRepo := operation.NewRepository(opConv)
   250  	applicationRepo := application.NewRepository(appConverter)
   251  	webhookRepo := webhook.NewRepository(webhookConverter)
   252  	tenantRepo := tenant.NewRepository(tenantConverter)
   253  	runtimeRepo := runtime.NewRepository(runtimeConverter)
   254  	labelRepo := label.NewRepository(labelConverter)
   255  	labelDefRepo := labeldef.NewRepository(labelDefConverter)
   256  	formationRepo := formation.NewRepository(formationConv)
   257  	formationTemplateRepo := formationtemplate.NewRepository(formationTemplateConverter)
   258  	intSysRepo := integrationsystem.NewRepository(intSysConverter)
   259  	apiRepo := api.NewRepository(apiConverter)
   260  	specRepo := spec.NewRepository(specConverter)
   261  	docRepo := document.NewRepository(docConverter)
   262  	fetchRequestRepo := fetchrequest.NewRepository(frConverter)
   263  	bundleRepo := bundleutil.NewRepository(bundleConverter)
   264  	bundleReferenceRepo := bundlereferences.NewRepository(bundleReferenceConv)
   265  	runtimeContextRepo := runtimectx.NewRepository(runtimeContextConv)
   266  	eventAPIRepo := eventdef.NewRepository(eventAPIConverter)
   267  	scenarioAssignmentRepo := scenarioassignment.NewRepository(assignmentConv)
   268  	formationAssignmentRepo := formationassignment.NewRepository(formationAssignmentConv)
   269  	formationConstraintRepo := formationconstraint.NewRepository(formationConstraintConverter)
   270  	formationTemplateConstraintReferencesRepo := formationtemplateconstraintreferences.NewRepository(formationTemplateConstraintReferencesConverter)
   271  	appTemplateRepo := apptemplate.NewRepository(appTemplateConverter)
   272  
   273  	uidSvc := uid.NewService()
   274  	opSvc := operation.NewService(opRepo, uidSvc)
   275  	tenantSvc := tenant.NewService(tenantRepo, uidSvc, tenantConverter)
   276  	webhookSvc := webhook.NewService(webhookRepo, applicationRepo, uidSvc, tenantSvc, tenantMappingConfig, callbackURL)
   277  	labelSvc := label.NewLabelService(labelRepo, labelDefRepo, uidSvc)
   278  	fetchRequestSvc := fetchrequest.NewServiceWithRetry(fetchRequestRepo, httpClient, accessStrategyExecutorProviderWithoutTenant, retryHTTPExecutor)
   279  	bundleReferenceSvc := bundlereferences.NewService(bundleReferenceRepo, uidSvc)
   280  	specSvc := spec.NewService(specRepo, fetchRequestRepo, uidSvc, fetchRequestSvc)
   281  	apiSvc := api.NewService(apiRepo, uidSvc, specSvc, bundleReferenceSvc)
   282  	docSvc := document.NewService(docRepo, fetchRequestRepo, uidSvc)
   283  	eventAPISvc := eventdef.NewService(eventAPIRepo, uidSvc, specSvc, bundleReferenceSvc)
   284  	scenariosSvc := labeldef.NewService(labelDefRepo, labelRepo, scenarioAssignmentRepo, tenantRepo, uidSvc)
   285  	scenarioAssignmentSvc := scenarioassignment.NewService(scenarioAssignmentRepo, scenariosSvc)
   286  	bundleSvc := bundleutil.NewService(bundleRepo, apiSvc, eventAPISvc, docSvc, uidSvc)
   287  	tntSvc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelSvc, tenantConverter)
   288  	formationConstraintSvc := formationconstraint.NewService(formationConstraintRepo, formationTemplateConstraintReferencesRepo, uidSvc, formationConstraintConverter)
   289  	constraintEngine := operators.NewConstraintEngine(transact, formationConstraintSvc, tenantSvc, scenarioAssignmentSvc, nil, formationRepo, labelRepo, labelSvc, applicationRepo, runtimeContextRepo, formationTemplateRepo, formationAssignmentRepo, conf.RuntimeTypeLabelKey, conf.ApplicationTypeLabelKey)
   290  	webhookDataInputBuilder := databuilder.NewWebhookDataInputBuilder(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo)
   291  	notificationsBuilder := formation.NewNotificationsBuilder(webhookConverter, constraintEngine, conf.Features.RuntimeTypeLabelKey, conf.Features.ApplicationTypeLabelKey)
   292  	notificationsGenerator := formation.NewNotificationsGenerator(applicationRepo, appTemplateRepo, runtimeRepo, runtimeContextRepo, labelRepo, webhookRepo, webhookDataInputBuilder, notificationsBuilder)
   293  	notificationSvc := formation.NewNotificationService(tenantRepo, webhookClient, notificationsGenerator, constraintEngine, webhookConverter, formationTemplateRepo)
   294  	faNotificationSvc := formationassignment.NewFormationAssignmentNotificationService(formationAssignmentRepo, webhookConverter, webhookRepo, tenantRepo, webhookDataInputBuilder, formationRepo, notificationsBuilder, runtimeContextRepo, labelSvc, conf.Features.RuntimeTypeLabelKey, conf.Features.ApplicationTypeLabelKey)
   295  	formationAssignmentStatusSvc := formationassignment.NewFormationAssignmentStatusService(formationAssignmentRepo, constraintEngine, faNotificationSvc)
   296  	formationAssignmentSvc := formationassignment.NewService(formationAssignmentRepo, uidSvc, applicationRepo, runtimeRepo, runtimeContextRepo, notificationSvc, faNotificationSvc, labelSvc, formationRepo, formationAssignmentStatusSvc, conf.Features.RuntimeTypeLabelKey, conf.Features.ApplicationTypeLabelKey)
   297  	formationStatusSvc := formation.NewFormationStatusService(formationRepo, labelDefRepo, scenariosSvc, notificationSvc, constraintEngine)
   298  	formationSvc := formation.NewService(transact, applicationRepo, labelDefRepo, labelRepo, formationRepo, formationTemplateRepo, labelSvc, uidSvc, scenariosSvc, scenarioAssignmentRepo, scenarioAssignmentSvc, tntSvc, runtimeRepo, runtimeContextRepo, formationAssignmentSvc, faNotificationSvc, notificationSvc, constraintEngine, webhookRepo, formationStatusSvc, conf.Features.RuntimeTypeLabelKey, conf.Features.ApplicationTypeLabelKey)
   299  	appSvc := application.NewService(&normalizer.DefaultNormalizator{}, cfgProvider, applicationRepo, webhookRepo, runtimeRepo, labelRepo, intSysRepo, labelSvc, bundleSvc, uidSvc, formationSvc, conf.SelfRegisterDistinguishLabelKey, ordWebhookMapping)
   300  
   301  	ordOpCreator := operationsmanager.NewOperationCreator(operationsmanager.OrdCreatorType, transact, opSvc, webhookSvc, appSvc)
   302  	return operationsmanager.NewOperationService(transact, opSvc, ordOpCreator), nil
   303  }
   304  
   305  func exitOnError(err error, context string) {
   306  	if err != nil {
   307  		wrappedError := errors.Wrap(err, context)
   308  		log.D().Fatal(wrappedError)
   309  	}
   310  }
   311  
   312  func createAndRunConfigProvider(ctx context.Context, cfg config) *configprovider.Provider {
   313  	provider := configprovider.NewProvider(cfg.ConfigurationFile)
   314  	err := provider.Load()
   315  	exitOnError(err, "Error on loading configuration file")
   316  	executor.NewPeriodic(cfg.ConfigurationFileReload, func(ctx context.Context) {
   317  		if err := provider.Load(); err != nil {
   318  			exitOnError(err, "Error from Reloader watch")
   319  		}
   320  		log.C(ctx).Infof("Successfully reloaded configuration file.")
   321  	}).Run(ctx)
   322  
   323  	return provider
   324  }
   325  
   326  func createServer(ctx context.Context, cfg config, handler http.Handler, name string) (func(), func()) {
   327  	server := &http.Server{
   328  		Addr:              cfg.Address,
   329  		Handler:           handler,
   330  		ReadHeaderTimeout: cfg.ReadHeadersTimeout,
   331  	}
   332  
   333  	runFn := func() {
   334  		log.C(ctx).Infof("Running %s server on %s...", name, cfg.Address)
   335  		if err := server.ListenAndServe(); err != http.ErrServerClosed {
   336  			log.C(ctx).Errorf("%s HTTP server ListenAndServe: %v", name, err)
   337  		}
   338  	}
   339  
   340  	shutdownFn := func() {
   341  		ctx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
   342  		defer cancel()
   343  
   344  		log.C(ctx).Infof("Shutting down %s server...", name)
   345  		if err := server.Shutdown(ctx); err != nil {
   346  			log.C(ctx).Errorf("%s HTTP server Shutdown: %v", name, err)
   347  		}
   348  	}
   349  
   350  	return runFn, shutdownFn
   351  }