github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/runtime/components_provider.go (about)

     1  package runtime
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/kyma-project/kyma-environment-broker/internal"
    11  	kebError "github.com/kyma-project/kyma-environment-broker/internal/error"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/iosafety"
    13  	"gopkg.in/yaml.v2"
    14  )
    15  
    16  const (
    17  	releaseComponentsURLFormat  = "https://github.com/kyma-project/kyma/releases/download/%s/kyma-components.yaml"
    18  	onDemandComponentsURLFormat = "https://storage.googleapis.com/kyma-development-artifacts/%s/kyma-components.yaml"
    19  )
    20  
    21  // ComponentsProvider provides the list of required and additional components for creating a Kyma Runtime
    22  type ComponentsProvider struct {
    23  	mu         sync.Mutex
    24  	httpClient HTTPDoer
    25  }
    26  
    27  // NewComponentsProvider returns new instance of the ComponentsProvider
    28  func NewComponentsProvider() *ComponentsProvider {
    29  	return &ComponentsProvider{
    30  		httpClient: http.DefaultClient,
    31  	}
    32  }
    33  
    34  // AllComponents returns all components for Kyma Runtime. It fetches always the
    35  // Kyma open-source components from the given url and management components from
    36  // ConfigMaps and merge them together
    37  func (p *ComponentsProvider) AllComponents(kymaVersion internal.RuntimeVersionData, config *internal.ConfigForPlan) ([]internal.KymaComponent, error) {
    38  	p.mu.Lock()
    39  	defer p.mu.Unlock()
    40  
    41  	kymaComponents, err := p.requiredComponents(kymaVersion)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("while getting Kyma components: %w", err)
    44  	}
    45  
    46  	additionalComponents := config.AdditionalComponents
    47  	if err != nil {
    48  		return nil, fmt.Errorf("while getting additional components: %w", err)
    49  	}
    50  
    51  	allComponents := append(kymaComponents, additionalComponents...)
    52  
    53  	return allComponents, nil
    54  
    55  }
    56  
    57  func (p *ComponentsProvider) requiredComponents(kymaVersion internal.RuntimeVersionData) ([]internal.KymaComponent,
    58  	error) {
    59  	return p.getComponentsFromComponentsYaml(kymaVersion.Version)
    60  
    61  }
    62  
    63  func (p *ComponentsProvider) getComponentsFromComponentsYaml(version string) ([]internal.KymaComponent, error) {
    64  	yamlURL := p.getComponentsYamlURL(version)
    65  
    66  	req, err := http.NewRequest(http.MethodGet, yamlURL, nil)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("while creating HTTP request: %w", err)
    69  	}
    70  
    71  	resp, err := p.httpClient.Do(req)
    72  	if err != nil {
    73  		return nil, kebError.AsTemporaryError(err, "while making HTTP request for Kyma components list")
    74  	}
    75  	defer func() {
    76  		if drainErr := iosafety.DrainReader(resp.Body); drainErr != nil {
    77  			err = fmt.Errorf("while trying to drain body reader: %w", err)
    78  		}
    79  		if closeErr := resp.Body.Close(); closeErr != nil {
    80  			err = fmt.Errorf("while trying to close response body reader: %w", err)
    81  		}
    82  	}()
    83  
    84  	if err = p.checkStatusCode(resp); err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	type kymaComponents struct {
    89  		DefaultNamespace string                   `yaml:"defaultNamespace"`
    90  		Prerequisites    []internal.KymaComponent `yaml:"prerequisites"`
    91  		Components       []internal.KymaComponent `yaml:"components"`
    92  	}
    93  
    94  	decoder := yaml.NewDecoder(resp.Body)
    95  
    96  	var kymaCmps kymaComponents
    97  	if err = decoder.Decode(&kymaCmps); err != nil {
    98  		return nil, fmt.Errorf("while decoding response body: %w", err)
    99  	}
   100  
   101  	requiredComponents := make([]internal.KymaComponent, 0)
   102  	requiredComponents = append(requiredComponents, kymaCmps.Prerequisites...)
   103  	requiredComponents = append(requiredComponents, kymaCmps.Components...)
   104  
   105  	for i, cmp := range requiredComponents {
   106  		if cmp.Namespace == "" {
   107  			requiredComponents[i].Namespace = kymaCmps.DefaultNamespace
   108  		}
   109  	}
   110  
   111  	return requiredComponents, nil
   112  }
   113  
   114  func (p *ComponentsProvider) getComponentsYamlURL(kymaVersion string) string {
   115  	if p.isOnDemandRelease(kymaVersion) {
   116  		return fmt.Sprintf(onDemandComponentsURLFormat, kymaVersion)
   117  	}
   118  	return fmt.Sprintf(releaseComponentsURLFormat, kymaVersion)
   119  }
   120  
   121  // isOnDemandRelease returns true if the version is recognized as on-demand.
   122  //
   123  // Detection rules:
   124  //
   125  //	For pull requests: PR-<number>
   126  //	For changes to the main branch: main-<commit_sha>
   127  //
   128  // source: https://github.com/kyma-project/test-infra/blob/main/docs/prow/prow-architecture.md#generate-development-artifacts
   129  func (p *ComponentsProvider) isOnDemandRelease(version string) bool {
   130  	isOnDemandVersion := strings.HasPrefix(version, "PR-") ||
   131  		strings.HasPrefix(version, "main-")
   132  	return isOnDemandVersion
   133  }
   134  
   135  func (p *ComponentsProvider) checkStatusCode(resp *http.Response) error {
   136  	if resp.StatusCode == http.StatusOK {
   137  		return nil
   138  	}
   139  
   140  	// limited buff to ready only ~4kb, so big response will not blowup our component
   141  	body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
   142  	if err != nil {
   143  		body = []byte(fmt.Sprintf("cannot read body, got error: %s", err))
   144  	}
   145  	msg := fmt.Sprintf("while checking response status code for Kyma components list: "+
   146  		"got unexpected status code, want %d, got %d, url: %s, body: %s",
   147  		http.StatusOK, resp.StatusCode, resp.Request.URL.String(), body)
   148  
   149  	switch {
   150  	case resp.StatusCode == http.StatusRequestTimeout:
   151  		return kebError.NewTemporaryError(msg)
   152  	case resp.StatusCode >= http.StatusInternalServerError:
   153  		return kebError.NewTemporaryError(msg)
   154  	default:
   155  		return fmt.Errorf(msg)
   156  	}
   157  }