github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/process/input/builder.go (about)

     1  package input
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema"
     8  	"github.com/kyma-project/kyma-environment-broker/internal"
     9  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    10  	cloudProvider "github.com/kyma-project/kyma-environment-broker/internal/provider"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/runtime"
    12  )
    13  
    14  //go:generate mockery --name=ComponentListProvider --output=automock --outpkg=automock --case=underscore
    15  //go:generate mockery --name=CreatorForPlan --output=automock --outpkg=automock --case=underscore
    16  //go:generate mockery --name=ComponentsDisabler --output=automock --outpkg=automock --case=underscore
    17  //go:generate mockery --name=OptionalComponentService --output=automock --outpkg=automock --case=underscore
    18  
    19  type (
    20  	OptionalComponentService interface {
    21  		ExecuteDisablers(components internal.ComponentConfigurationInputList, names ...string) (internal.ComponentConfigurationInputList, error)
    22  		ComputeComponentsToDisable(optComponentsToKeep []string) []string
    23  		AddComponentToDisable(name string, disabler runtime.ComponentDisabler)
    24  	}
    25  
    26  	ComponentsDisabler interface {
    27  		DisableComponents(components internal.ComponentConfigurationInputList) (internal.ComponentConfigurationInputList, error)
    28  	}
    29  
    30  	DisabledComponentsProvider interface {
    31  		DisabledComponentsPerPlan(planID string) (map[string]struct{}, error)
    32  		DisabledForAll() map[string]struct{}
    33  	}
    34  
    35  	HyperscalerInputProvider interface {
    36  		Defaults() *gqlschema.ClusterConfigInput
    37  		ApplyParameters(input *gqlschema.ClusterConfigInput, params internal.ProvisioningParameters)
    38  		Profile() gqlschema.KymaProfile
    39  		Provider() internal.CloudProvider
    40  	}
    41  
    42  	CreatorForPlan interface {
    43  		IsPlanSupport(planID string) bool
    44  		CreateProvisionInput(parameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error)
    45  		CreateUpgradeInput(parameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error)
    46  		CreateUpgradeShootInput(parameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error)
    47  		GetPlanDefaults(planID string, platformProvider internal.CloudProvider, parametersProvider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error)
    48  	}
    49  
    50  	ComponentListProvider interface {
    51  		AllComponents(kymaVersion internal.RuntimeVersionData, config *internal.ConfigForPlan) ([]internal.KymaComponent, error)
    52  	}
    53  
    54  	ConfigurationProvider interface {
    55  		ProvideForGivenVersionAndPlan(kymaVersion, planName string) (*internal.ConfigForPlan, error)
    56  	}
    57  )
    58  
    59  type InputBuilderFactory struct {
    60  	kymaVersion                string
    61  	config                     Config
    62  	optComponentsSvc           OptionalComponentService
    63  	componentsProvider         ComponentListProvider
    64  	disabledComponentsProvider DisabledComponentsProvider
    65  	configProvider             ConfigurationProvider
    66  	trialPlatformRegionMapping map[string]string
    67  	enabledFreemiumProviders   map[string]struct{}
    68  	oidcDefaultValues          internal.OIDCConfigDTO
    69  }
    70  
    71  func NewInputBuilderFactory(optComponentsSvc OptionalComponentService, disabledComponentsProvider DisabledComponentsProvider,
    72  	componentsListProvider ComponentListProvider, configProvider ConfigurationProvider,
    73  	config Config, defaultKymaVersion string, trialPlatformRegionMapping map[string]string,
    74  	enabledFreemiumProviders []string, oidcValues internal.OIDCConfigDTO) (CreatorForPlan, error) {
    75  
    76  	freemiumProviders := map[string]struct{}{}
    77  	for _, p := range enabledFreemiumProviders {
    78  		freemiumProviders[strings.ToLower(p)] = struct{}{}
    79  	}
    80  
    81  	return &InputBuilderFactory{
    82  		kymaVersion:                defaultKymaVersion,
    83  		config:                     config,
    84  		optComponentsSvc:           optComponentsSvc,
    85  		componentsProvider:         componentsListProvider,
    86  		disabledComponentsProvider: disabledComponentsProvider,
    87  		configProvider:             configProvider,
    88  		trialPlatformRegionMapping: trialPlatformRegionMapping,
    89  		enabledFreemiumProviders:   freemiumProviders,
    90  		oidcDefaultValues:          oidcValues,
    91  	}, nil
    92  }
    93  
    94  // SetDefaultTrialProvider is used for testing scenario, when the default trial provider is being changed
    95  func (f *InputBuilderFactory) SetDefaultTrialProvider(p internal.CloudProvider) {
    96  	f.config.DefaultTrialProvider = p
    97  }
    98  
    99  func (f *InputBuilderFactory) IsPlanSupport(planID string) bool {
   100  	switch planID {
   101  	case broker.AWSPlanID, broker.GCPPlanID, broker.AzurePlanID, broker.FreemiumPlanID,
   102  		broker.AzureLitePlanID, broker.TrialPlanID, broker.OpenStackPlanID, broker.OwnClusterPlanID, broker.PreviewPlanID:
   103  		return true
   104  	default:
   105  		return false
   106  	}
   107  }
   108  
   109  func (f *InputBuilderFactory) GetPlanDefaults(planID string, platformProvider internal.CloudProvider, parametersProvider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) {
   110  	h, err := f.getHyperscalerProviderForPlanID(planID, platformProvider, parametersProvider)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	return h.Defaults(), nil
   115  }
   116  
   117  func (f *InputBuilderFactory) getHyperscalerProviderForPlanID(planID string, platformProvider internal.CloudProvider, parametersProvider *internal.CloudProvider) (HyperscalerInputProvider, error) {
   118  	var provider HyperscalerInputProvider
   119  	switch planID {
   120  	case broker.GCPPlanID:
   121  		provider = &cloudProvider.GcpInput{
   122  			MultiZone:                    f.config.MultiZoneCluster,
   123  			ControlPlaneFailureTolerance: f.config.ControlPlaneFailureTolerance,
   124  		}
   125  	case broker.FreemiumPlanID:
   126  		return f.forFreemiumPlan(platformProvider)
   127  	case broker.OpenStackPlanID:
   128  		provider = &cloudProvider.OpenStackInput{
   129  			FloatingPoolName: f.config.OpenstackFloatingPoolName,
   130  		}
   131  	case broker.AzurePlanID:
   132  		provider = &cloudProvider.AzureInput{
   133  			MultiZone:                    f.config.MultiZoneCluster,
   134  			ControlPlaneFailureTolerance: f.config.ControlPlaneFailureTolerance,
   135  		}
   136  	case broker.AzureLitePlanID:
   137  		provider = &cloudProvider.AzureLiteInput{}
   138  	case broker.TrialPlanID:
   139  		provider = f.forTrialPlan(parametersProvider)
   140  	case broker.AWSPlanID:
   141  		provider = &cloudProvider.AWSInput{
   142  			MultiZone:                    f.config.MultiZoneCluster,
   143  			ControlPlaneFailureTolerance: f.config.ControlPlaneFailureTolerance,
   144  		}
   145  	case broker.OwnClusterPlanID:
   146  		provider = &cloudProvider.NoHyperscalerInput{}
   147  		// insert cases for other providers like AWS or GCP
   148  	case broker.PreviewPlanID:
   149  		provider = &cloudProvider.AWSInput{
   150  			MultiZone:                    f.config.MultiZoneCluster,
   151  			ControlPlaneFailureTolerance: f.config.ControlPlaneFailureTolerance,
   152  		}
   153  	default:
   154  		return nil, fmt.Errorf("case with plan %s is not supported", planID)
   155  	}
   156  	return provider, nil
   157  }
   158  
   159  func (f *InputBuilderFactory) CreateProvisionInput(provisioningParameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error) {
   160  	if !f.IsPlanSupport(provisioningParameters.PlanID) {
   161  		return nil, fmt.Errorf("plan %s in not supported", provisioningParameters.PlanID)
   162  	}
   163  
   164  	planName := broker.PlanNamesMapping[provisioningParameters.PlanID]
   165  
   166  	cfg, err := f.configProvider.ProvideForGivenVersionAndPlan(version.Version, planName)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("while getting configuration for given version and plan: %w", err)
   169  	}
   170  
   171  	provider, err := f.getHyperscalerProviderForPlanID(provisioningParameters.PlanID, provisioningParameters.PlatformProvider, provisioningParameters.Parameters.Provider)
   172  	if err != nil {
   173  		return nil, fmt.Errorf("during creating provision input: %w", err)
   174  	}
   175  
   176  	initInput, err := f.initProvisionRuntimeInput(provider, version, cfg)
   177  	if err != nil {
   178  		return nil, fmt.Errorf("while initializing ProvisionRuntimeInput: %w", err)
   179  	}
   180  
   181  	disabledForPlan, err := f.disabledComponentsProvider.DisabledComponentsPerPlan(provisioningParameters.PlanID)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("while getting disabled components for plan %s: %w", provisioningParameters.PlanID, err)
   184  	}
   185  	disabledComponents := mergeMaps(disabledForPlan, f.disabledComponentsProvider.DisabledForAll())
   186  
   187  	return &RuntimeInput{
   188  		provisionRuntimeInput:     initInput,
   189  		overrides:                 make(map[string][]*gqlschema.ConfigEntryInput, 0),
   190  		labels:                    make(map[string]string),
   191  		globalOverrides:           make([]*gqlschema.ConfigEntryInput, 0),
   192  		config:                    cfg,
   193  		hyperscalerInputProvider:  provider,
   194  		optionalComponentsService: f.optComponentsSvc,
   195  		provisioningParameters:    provisioningParameters,
   196  		componentsDisabler:        runtime.NewDisabledComponentsService(disabledComponents),
   197  		enabledOptionalComponents: map[string]struct{}{},
   198  		oidcDefaultValues:         f.oidcDefaultValues,
   199  		trialNodesNumber:          f.config.TrialNodesNumber,
   200  	}, nil
   201  }
   202  
   203  func (f *InputBuilderFactory) forTrialPlan(provider *internal.CloudProvider) HyperscalerInputProvider {
   204  	var trialProvider internal.CloudProvider
   205  	if provider == nil {
   206  		trialProvider = f.config.DefaultTrialProvider
   207  	} else {
   208  		trialProvider = *provider
   209  	}
   210  
   211  	switch trialProvider {
   212  	case internal.GCP:
   213  		return &cloudProvider.GcpTrialInput{
   214  			PlatformRegionMapping: f.trialPlatformRegionMapping,
   215  		}
   216  	case internal.AWS:
   217  		return &cloudProvider.AWSTrialInput{
   218  			PlatformRegionMapping: f.trialPlatformRegionMapping,
   219  		}
   220  	default:
   221  		return &cloudProvider.AzureTrialInput{
   222  			PlatformRegionMapping: f.trialPlatformRegionMapping,
   223  		}
   224  	}
   225  
   226  }
   227  
   228  func (f *InputBuilderFactory) provideComponentList(version internal.RuntimeVersionData, config *internal.ConfigForPlan) (internal.ComponentConfigurationInputList, error) {
   229  	allComponents, err := f.componentsProvider.AllComponents(version, config)
   230  	if err != nil {
   231  		return internal.ComponentConfigurationInputList{}, fmt.Errorf("while fetching components for %s Kyma version: %w", version.Version, err)
   232  	}
   233  
   234  	return mapToGQLComponentConfigurationInput(allComponents), nil
   235  }
   236  
   237  func (f *InputBuilderFactory) initProvisionRuntimeInput(provider HyperscalerInputProvider, version internal.RuntimeVersionData, config *internal.ConfigForPlan) (gqlschema.ProvisionRuntimeInput, error) {
   238  	components, err := f.provideComponentList(version, config)
   239  	if err != nil {
   240  		return gqlschema.ProvisionRuntimeInput{}, err
   241  	}
   242  
   243  	kymaProfile := provider.Profile()
   244  
   245  	provisionInput := gqlschema.ProvisionRuntimeInput{
   246  		RuntimeInput:  &gqlschema.RuntimeInput{},
   247  		ClusterConfig: provider.Defaults(),
   248  		KymaConfig: &gqlschema.KymaConfigInput{
   249  			Profile:    &kymaProfile,
   250  			Version:    version.Version,
   251  			Components: components.DeepCopy(),
   252  		},
   253  	}
   254  
   255  	if provisionInput.ClusterConfig.GardenerConfig == nil {
   256  		provisionInput.ClusterConfig.GardenerConfig = &gqlschema.GardenerConfigInput{}
   257  	}
   258  
   259  	provisionInput.ClusterConfig.GardenerConfig.KubernetesVersion = f.config.KubernetesVersion
   260  	provisionInput.ClusterConfig.GardenerConfig.EnableKubernetesVersionAutoUpdate = &f.config.AutoUpdateKubernetesVersion
   261  	provisionInput.ClusterConfig.GardenerConfig.EnableMachineImageVersionAutoUpdate = &f.config.AutoUpdateMachineImageVersion
   262  	if provisionInput.ClusterConfig.GardenerConfig.Purpose == nil {
   263  		provisionInput.ClusterConfig.GardenerConfig.Purpose = &f.config.DefaultGardenerShootPurpose
   264  	}
   265  	if f.config.MachineImage != "" {
   266  		provisionInput.ClusterConfig.GardenerConfig.MachineImage = &f.config.MachineImage
   267  	}
   268  	if f.config.MachineImageVersion != "" {
   269  		provisionInput.ClusterConfig.GardenerConfig.MachineImageVersion = &f.config.MachineImageVersion
   270  	}
   271  
   272  	return provisionInput, nil
   273  }
   274  
   275  func (f *InputBuilderFactory) CreateUpgradeInput(provisioningParameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error) {
   276  	if !f.IsPlanSupport(provisioningParameters.PlanID) {
   277  		return nil, fmt.Errorf("plan %s in not supported", provisioningParameters.PlanID)
   278  	}
   279  
   280  	planName := broker.PlanNamesMapping[provisioningParameters.PlanID]
   281  
   282  	cfg, err := f.configProvider.ProvideForGivenVersionAndPlan(version.Version, planName)
   283  	if err != nil {
   284  		return nil, fmt.Errorf("while getting configuration for given version and plan: %w", err)
   285  	}
   286  
   287  	provider, err := f.getHyperscalerProviderForPlanID(provisioningParameters.PlanID, provisioningParameters.PlatformProvider, provisioningParameters.Parameters.Provider)
   288  	if err != nil {
   289  		return nil, fmt.Errorf("during createing provision input: %w", err)
   290  	}
   291  
   292  	upgradeKymaInput, err := f.initUpgradeRuntimeInput(version, provider, cfg)
   293  	if err != nil {
   294  		return nil, fmt.Errorf("while initializing UpgradeRuntimeInput: %w", err)
   295  	}
   296  
   297  	kymaInput, err := f.initProvisionRuntimeInput(provider, version, cfg)
   298  	if err != nil {
   299  		return nil, fmt.Errorf("while initializing RuntimeInput: %w", err)
   300  	}
   301  
   302  	disabledForPlan, err := f.disabledComponentsProvider.DisabledComponentsPerPlan(provisioningParameters.PlanID)
   303  	if err != nil {
   304  		return nil, fmt.Errorf("every supported plan should be specified in the disabled components map: %w", err)
   305  	}
   306  	disabledComponents := mergeMaps(disabledForPlan, f.disabledComponentsProvider.DisabledForAll())
   307  
   308  	return &RuntimeInput{
   309  		provisionRuntimeInput:     kymaInput,
   310  		upgradeRuntimeInput:       upgradeKymaInput,
   311  		overrides:                 make(map[string][]*gqlschema.ConfigEntryInput, 0),
   312  		globalOverrides:           make([]*gqlschema.ConfigEntryInput, 0),
   313  		optionalComponentsService: f.optComponentsSvc,
   314  		componentsDisabler:        runtime.NewDisabledComponentsService(disabledComponents),
   315  		enabledOptionalComponents: map[string]struct{}{},
   316  		trialNodesNumber:          f.config.TrialNodesNumber,
   317  		oidcDefaultValues:         f.oidcDefaultValues,
   318  		hyperscalerInputProvider:  provider,
   319  		config:                    cfg,
   320  	}, nil
   321  }
   322  
   323  func (f *InputBuilderFactory) initUpgradeRuntimeInput(version internal.RuntimeVersionData, provider HyperscalerInputProvider, config *internal.ConfigForPlan) (gqlschema.UpgradeRuntimeInput, error) {
   324  	if version.Version == "" {
   325  		return gqlschema.UpgradeRuntimeInput{}, fmt.Errorf("desired runtime version cannot be empty")
   326  	}
   327  
   328  	kymaProfile := provider.Profile()
   329  	components, err := f.provideComponentList(version, config)
   330  	if err != nil {
   331  		return gqlschema.UpgradeRuntimeInput{}, err
   332  	}
   333  
   334  	return gqlschema.UpgradeRuntimeInput{
   335  		KymaConfig: &gqlschema.KymaConfigInput{
   336  			Profile:    &kymaProfile,
   337  			Version:    version.Version,
   338  			Components: components.DeepCopy(),
   339  		},
   340  	}, nil
   341  }
   342  
   343  func mapToGQLComponentConfigurationInput(kymaComponents []internal.KymaComponent) internal.ComponentConfigurationInputList {
   344  	var input internal.ComponentConfigurationInputList
   345  	for _, component := range kymaComponents {
   346  		var sourceURL *string
   347  		if component.Source != nil {
   348  			sourceURL = &component.Source.URL
   349  		}
   350  
   351  		input = append(input, &gqlschema.ComponentConfigurationInput{
   352  			Component: component.Name,
   353  			Namespace: component.Namespace,
   354  			SourceURL: sourceURL,
   355  		})
   356  	}
   357  	return input
   358  }
   359  
   360  func mergeMaps(maps ...map[string]struct{}) map[string]struct{} {
   361  	res := map[string]struct{}{}
   362  	for _, m := range maps {
   363  		for k, v := range m {
   364  			res[k] = v
   365  		}
   366  	}
   367  	return res
   368  }
   369  
   370  func (f *InputBuilderFactory) CreateUpgradeShootInput(provisioningParameters internal.ProvisioningParameters, version internal.RuntimeVersionData) (internal.ProvisionerInputCreator, error) {
   371  	if !f.IsPlanSupport(provisioningParameters.PlanID) {
   372  		return nil, fmt.Errorf("plan %s in not supported", provisioningParameters.PlanID)
   373  	}
   374  
   375  	planName := broker.PlanNamesMapping[provisioningParameters.PlanID]
   376  
   377  	cfg, err := f.configProvider.ProvideForGivenVersionAndPlan(version.Version, planName)
   378  	if err != nil {
   379  		return nil, fmt.Errorf("while getting configuration for given version and plan: %w", err)
   380  	}
   381  
   382  	provider, err := f.getHyperscalerProviderForPlanID(provisioningParameters.PlanID, provisioningParameters.PlatformProvider, provisioningParameters.Parameters.Provider)
   383  	if err != nil {
   384  		return nil, fmt.Errorf("during createing provision input: %w", err)
   385  	}
   386  
   387  	input := f.initUpgradeShootInput(provider)
   388  	return &RuntimeInput{
   389  		upgradeShootInput:        input,
   390  		config:                   cfg,
   391  		hyperscalerInputProvider: provider,
   392  		trialNodesNumber:         f.config.TrialNodesNumber,
   393  		oidcDefaultValues:        f.oidcDefaultValues,
   394  	}, nil
   395  }
   396  
   397  func (f *InputBuilderFactory) initUpgradeShootInput(provider HyperscalerInputProvider) gqlschema.UpgradeShootInput {
   398  	input := gqlschema.UpgradeShootInput{
   399  		GardenerConfig: &gqlschema.GardenerUpgradeInput{
   400  			KubernetesVersion: &f.config.KubernetesVersion,
   401  		},
   402  	}
   403  
   404  	if f.config.MachineImage != "" {
   405  		input.GardenerConfig.MachineImage = &f.config.MachineImage
   406  	}
   407  	if f.config.MachineImageVersion != "" {
   408  		input.GardenerConfig.MachineImageVersion = &f.config.MachineImageVersion
   409  	}
   410  
   411  	// sync with the autoscaler and maintenance settings
   412  	input.GardenerConfig.MaxSurge = &provider.Defaults().GardenerConfig.MaxSurge
   413  	input.GardenerConfig.MaxUnavailable = &provider.Defaults().GardenerConfig.MaxUnavailable
   414  	input.GardenerConfig.EnableKubernetesVersionAutoUpdate = &f.config.AutoUpdateKubernetesVersion
   415  	input.GardenerConfig.EnableMachineImageVersionAutoUpdate = &f.config.AutoUpdateMachineImageVersion
   416  
   417  	return input
   418  }
   419  
   420  func (f *InputBuilderFactory) forFreemiumPlan(provider internal.CloudProvider) (HyperscalerInputProvider, error) {
   421  	if !f.IsFreemiumProviderEnabled(provider) {
   422  		return nil, fmt.Errorf("freemium provider %s is not enabled", provider)
   423  	}
   424  	switch provider {
   425  	case internal.AWS:
   426  		return &cloudProvider.AWSFreemiumInput{}, nil
   427  	case internal.Azure:
   428  		return &cloudProvider.AzureFreemiumInput{}, nil
   429  	default:
   430  		return nil, fmt.Errorf("provider %s is not supported", provider)
   431  	}
   432  }
   433  
   434  func (f *InputBuilderFactory) IsFreemiumProviderEnabled(provider internal.CloudProvider) bool {
   435  	_, found := f.enabledFreemiumProviders[strings.ToLower(string(provider))]
   436  	return found
   437  }