github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/runtime/provider.go (about) 1 package runtime 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net/http" 8 "strings" 9 "sync" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/kyma-project/kyma-environment-broker/internal" 13 kebError "github.com/kyma-project/kyma-environment-broker/internal/error" 14 "github.com/kyma-project/kyma-environment-broker/internal/iosafety" 15 "github.com/kyma-project/kyma/components/kyma-operator/pkg/apis/installer/v1alpha1" 16 "gopkg.in/yaml.v2" 17 ) 18 19 const ( 20 releaseInstallerURLFormat = "https://storage.googleapis.com/kyma-prow-artifacts/%s/kyma-installer-cluster.yaml" 21 onDemandInstallerURLFormat = "https://storage.googleapis.com/kyma-development-artifacts/%s/kyma-installer-cluster.yaml" 22 ) 23 24 // ComponentsListProvider provides the whole components list for creating a Kyma Runtime 25 type ComponentsListProvider struct { 26 managedRuntimeComponentsYAMLPath string 27 newAdditionalRuntimeComponentsYAMLPath string 28 httpClient HTTPDoer 29 components map[string][]internal.KymaComponent 30 mu sync.Mutex 31 } 32 33 type HTTPDoer interface { 34 Do(req *http.Request) (*http.Response, error) 35 } 36 37 // NewComponentsListProvider returns new instance of the ComponentsListProvider 38 func NewComponentsListProvider(managedRuntimeComponentsYAMLPath, newAdditionalRuntimeComponentsYAMLPath string) *ComponentsListProvider { 39 return &ComponentsListProvider{ 40 httpClient: http.DefaultClient, 41 managedRuntimeComponentsYAMLPath: managedRuntimeComponentsYAMLPath, 42 newAdditionalRuntimeComponentsYAMLPath: newAdditionalRuntimeComponentsYAMLPath, 43 components: make(map[string][]internal.KymaComponent, 0), 44 } 45 } 46 47 // AllComponents returns all components for Kyma Runtime. It fetches always the 48 // Kyma open-source components from the given url and management components from 49 // the file system and merge them together. 50 func (r *ComponentsListProvider) AllComponents(kymaVersion internal.RuntimeVersionData, config *internal.ConfigForPlan) ([]internal.KymaComponent, error) { 51 r.mu.Lock() 52 defer r.mu.Unlock() 53 54 if cmps, ok := r.components[kymaVersion.Version]; ok { 55 return cmps, nil 56 } 57 58 kymaComponents, err := r.getKymaComponents(kymaVersion) 59 if err != nil { 60 return nil, fmt.Errorf("while getting Kyma components: %w", err) 61 } 62 63 additionalComponents, err := r.getAdditionalComponents(kymaVersion) 64 if err != nil { 65 return nil, fmt.Errorf("while getting additional components: %w", err) 66 } 67 68 allComponents := append(kymaComponents, additionalComponents...) 69 70 r.components[kymaVersion.Version] = allComponents 71 return allComponents, nil 72 } 73 74 func (r *ComponentsListProvider) getKymaComponents(kymaVersion internal.RuntimeVersionData) (comp []internal.KymaComponent, err error) { 75 if kymaVersion.MajorVersion > 1 { 76 return r.getComponentsFromComponentsYaml(kymaVersion.Version) 77 } 78 return r.getComponentsFromInstallerYaml(kymaVersion.Version) 79 } 80 81 func (r *ComponentsListProvider) getAdditionalComponents(kymaVersion internal.RuntimeVersionData) ([]internal.KymaComponent, error) { 82 if kymaVersion.MajorVersion > 1 { 83 return r.getAdditionalComponentsForNewKyma() 84 } 85 return r.getAdditionalComponentsForKyma() 86 } 87 88 // Installation represents the installer CR. 89 // It is copied because using directly the installer CR 90 // with such fields: 91 // 92 // metav1.TypeMeta `json:",inline"` 93 // metav1.ObjectMeta `json:"metadata,omitempty"` 94 // 95 // is not working with "gopkg.in/yaml.v2" stream decoder. 96 // On the other hand "sigs.k8s.io/yaml" does not support 97 // stream decoding. 98 type Installation struct { 99 Kind string `json:"kind"` 100 Spec v1alpha1.InstallationSpec `json:"spec"` 101 } 102 103 type kymaComponents struct { 104 DefaultNamespace string `yaml:"defaultNamespace"` 105 Prerequisites []internal.KymaComponent `yaml:"prerequisites"` 106 Components []internal.KymaComponent `yaml:"components"` 107 } 108 109 func (r *ComponentsListProvider) checkStatusCode(resp *http.Response) error { 110 if resp.StatusCode == http.StatusOK { 111 return nil 112 } 113 114 // limited buff to ready only ~4kb, so big response will not blowup our component 115 body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 4096)) 116 if err != nil { 117 body = []byte(fmt.Sprintf("cannot read body, got error: %s", err)) 118 } 119 msg := fmt.Sprintf("while checking response status code for Kyma components list: "+ 120 "got unexpected status code, want %d, got %d, url: %s, body: %s", 121 http.StatusOK, resp.StatusCode, resp.Request.URL.String(), body) 122 123 switch { 124 case resp.StatusCode == http.StatusRequestTimeout: 125 return kebError.NewTemporaryError(msg) 126 case resp.StatusCode >= http.StatusInternalServerError: 127 return kebError.NewTemporaryError(msg) 128 default: 129 return fmt.Errorf(msg) 130 } 131 } 132 133 // isOnDemandRelease returns true if the version is recognized as on-demand. 134 // 135 // Detection rules: 136 // 137 // For pull requests: PR-<number> 138 // For changes to the main branch: main-<commit_sha> 139 // 140 // source: https://github.com/kyma-project/test-infra/blob/main/docs/prow/prow-architecture.md#generate-development-artifacts 141 func (r *ComponentsListProvider) isOnDemandRelease(version string) bool { 142 isOnDemandVersion := strings.HasPrefix(version, "PR-") || 143 strings.HasPrefix(version, "main-") 144 return isOnDemandVersion 145 } 146 147 func (r *ComponentsListProvider) getInstallerYamlURL(kymaVersion string) string { 148 if r.isOnDemandRelease(kymaVersion) { 149 return fmt.Sprintf(onDemandInstallerURLFormat, kymaVersion) 150 } 151 return fmt.Sprintf(releaseInstallerURLFormat, kymaVersion) 152 } 153 154 func (r *ComponentsListProvider) getComponentsYamlURL(kymaVersion string) string { 155 if r.isOnDemandRelease(kymaVersion) { 156 return fmt.Sprintf(onDemandComponentsURLFormat, kymaVersion) 157 } 158 return fmt.Sprintf(releaseComponentsURLFormat, kymaVersion) 159 } 160 161 func (r *ComponentsListProvider) getComponentsFromComponentsYaml(kymaVersion string) ([]internal.KymaComponent, error) { 162 yamlURL := r.getComponentsYamlURL(kymaVersion) 163 164 req, err := http.NewRequest(http.MethodGet, yamlURL, nil) 165 if err != nil { 166 return nil, fmt.Errorf("while creating http request: %w", err) 167 } 168 169 resp, err := r.httpClient.Do(req) 170 if err != nil { 171 return nil, kebError.AsTemporaryError(err, "while making request for Kyma components list") 172 } 173 defer func() { 174 if drainErr := iosafety.DrainReader(resp.Body); drainErr != nil { 175 err = multierror.Append(err, fmt.Errorf("while draining response body: %w", drainErr)) 176 } 177 178 if closeErr := resp.Body.Close(); closeErr != nil { 179 err = multierror.Append(err, fmt.Errorf("while closing response body: %w", closeErr)) 180 } 181 }() 182 183 if err = r.checkStatusCode(resp); err != nil { 184 return nil, err 185 } 186 187 dec := yaml.NewDecoder(resp.Body) 188 189 var kymaCmps kymaComponents 190 if err = dec.Decode(&kymaCmps); err != nil { 191 return nil, err 192 } 193 194 allKymaComponents := make([]internal.KymaComponent, 0) 195 allKymaComponents = append(allKymaComponents, kymaCmps.Prerequisites...) 196 allKymaComponents = append(allKymaComponents, kymaCmps.Components...) 197 198 for i, cmp := range allKymaComponents { 199 if cmp.Namespace == "" { 200 allKymaComponents[i].Namespace = kymaCmps.DefaultNamespace 201 } 202 } 203 204 return allKymaComponents, nil 205 } 206 207 func (r *ComponentsListProvider) getComponentsFromInstallerYaml(kymaVersion string) ([]internal.KymaComponent, error) { 208 yamlURL := r.getInstallerYamlURL(kymaVersion) 209 210 req, err := http.NewRequest(http.MethodGet, yamlURL, nil) 211 if err != nil { 212 return nil, fmt.Errorf("while creating http request: %w", err) 213 } 214 215 resp, err := r.httpClient.Do(req) 216 if err != nil { 217 return nil, kebError.AsTemporaryError(err, "while making request for Kyma components list") 218 } 219 defer func() { 220 if drainErr := iosafety.DrainReader(resp.Body); drainErr != nil { 221 err = multierror.Append(err, fmt.Errorf("while draining response body: %w", drainErr)) 222 } 223 224 if closeErr := resp.Body.Close(); closeErr != nil { 225 err = multierror.Append(err, fmt.Errorf("while closing response body: %w", closeErr)) 226 } 227 }() 228 229 if err := r.checkStatusCode(resp); err != nil { 230 return nil, err 231 } 232 233 dec := yaml.NewDecoder(resp.Body) 234 235 var t Installation 236 for dec.Decode(&t) == nil { 237 if t.Kind == "Installation" { 238 components := make([]internal.KymaComponent, len(t.Spec.Components)) 239 for i, cmp := range t.Spec.Components { 240 components[i] = r.v1alpha1ToKymaComponent(cmp) 241 } 242 return components, nil 243 } 244 } 245 return nil, fmt.Errorf("installer cr not found") 246 } 247 248 func (r *ComponentsListProvider) getAdditionalComponentsForNewKyma() ([]internal.KymaComponent, error) { 249 yamlContents, err := r.readYAML(r.newAdditionalRuntimeComponentsYAMLPath) 250 if err != nil { 251 return nil, fmt.Errorf("while reading additional components yaml: %w", err) 252 } 253 return r.getComponentsFromYAML(yamlContents) 254 } 255 256 func (r *ComponentsListProvider) getAdditionalComponentsForKyma() ([]internal.KymaComponent, error) { 257 yamlContents, err := r.readYAML(r.managedRuntimeComponentsYAMLPath) 258 if err != nil { 259 return nil, fmt.Errorf("while reading additional components yaml: %w", err) 260 } 261 return r.getComponentsFromYAML(yamlContents) 262 } 263 264 func (r *ComponentsListProvider) readYAML(yamlFilePath string) ([]byte, error) { 265 yamlContents, err := ioutil.ReadFile(yamlFilePath) 266 if err != nil { 267 return nil, fmt.Errorf("while reading yaml file: %w", err) 268 } 269 return yamlContents, nil 270 } 271 272 func (r *ComponentsListProvider) getComponentsFromYAML(yamlFileContents []byte) ([]internal.KymaComponent, error) { 273 var components struct { 274 Components []internal.KymaComponent `json:"components"` 275 } 276 err := yaml.Unmarshal(yamlFileContents, &components) 277 if err != nil { 278 return nil, fmt.Errorf("while unmarshalling YAML file with additional components: %w", err) 279 } 280 return components.Components, nil 281 } 282 283 func (r *ComponentsListProvider) v1alpha1ToKymaComponent(cmp v1alpha1.KymaComponent) internal.KymaComponent { 284 var source *internal.ComponentSource 285 if cmp.Source != nil { 286 source.URL = cmp.Source.URL 287 } 288 289 return internal.KymaComponent{ 290 Name: cmp.Name, 291 ReleaseName: cmp.ReleaseName, 292 Namespace: cmp.Namespace, 293 Source: source, 294 } 295 }