mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.6 KiB
236 lines
7.6 KiB
/*
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package manager
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/ghodss/yaml"
|
|
)
|
|
|
|
const (
|
|
// TODO (iantw): Align this with a character not allowed to show up in resource names.
|
|
layoutNodeKeySeparator = "#"
|
|
)
|
|
|
|
// ExpandedTemplate is the structure returned by the expansion service.
|
|
type ExpandedTemplate struct {
|
|
Config *Configuration `json:"config"`
|
|
Layout *Layout `json:"layout"`
|
|
}
|
|
|
|
// Expander abstracts interactions with the expander and deployer services.
|
|
type Expander interface {
|
|
ExpandTemplate(t Template) (*ExpandedTemplate, error)
|
|
}
|
|
|
|
// NewExpander returns a new initialized Expander.
|
|
func NewExpander(url string, tr TypeResolver) Expander {
|
|
return &expander{url, tr}
|
|
}
|
|
|
|
type expander struct {
|
|
expanderURL string
|
|
typeResolver TypeResolver
|
|
}
|
|
|
|
func (e *expander) getBaseURL() string {
|
|
return fmt.Sprintf("%s/expand", e.expanderURL)
|
|
}
|
|
|
|
func expanderError(t *Template, err error) error {
|
|
return fmt.Errorf("cannot expand template named %s (%s):\n%s\n", t.Name, err, t.Content)
|
|
}
|
|
|
|
// ExpanderResponse gives back a layout, which has nested structure
|
|
// Resource0
|
|
// ResourceDefinition
|
|
// Resource0, 0
|
|
// ResourceDefinition
|
|
// Resource0, 0, 0
|
|
// ResourceDefinition
|
|
// Resource0, 0, 1
|
|
// ResourceDefinition
|
|
// Resource0, 1
|
|
// ResourceDefinition
|
|
//
|
|
// All the leaf nodes in this tree are either primitives or a currently unexpandable type.
|
|
// Next we will resolve all the unexpandable types and re-enter expansion, at which point
|
|
// all primitives are untouched and returned as root siblings with no children in the
|
|
// resulting layout. The previously unexpandable nodes will become sibling root nodes,
|
|
// but with children. We want to replace the leaf nodes that were formerly unexpandable
|
|
// with their respective newly created trees.
|
|
//
|
|
// So, do as follows:
|
|
// 1) Do a walk of the tree and find each leaf. Check its Type and place a pointer to it
|
|
// into a map with the resource name and type as key if it is non-primitive.
|
|
// 2) Re-expand the template with the new imports.
|
|
// 3) For each root level sibling, check if its name exists in the hash map from (1)
|
|
// 4) Replace the Layout of the node in the hash map with the current node if applicable.
|
|
// 5) Return to (1)
|
|
|
|
// TODO (iantw): There may be a tricky corner case here where a known template could be
|
|
// masked by an unknown template, which on the subsequent expansion could allow a collision
|
|
// between the name#template key to exist in the layout given a particular choice of naming.
|
|
// In practice, it would be nearly impossible to hit, but consider including properties/name/type
|
|
// into a hash of sorts to make this robust...
|
|
func walkLayout(l *Layout, toReplace map[string]*LayoutResource) map[string]*LayoutResource {
|
|
ret := map[string]*LayoutResource{}
|
|
toVisit := l.Resources
|
|
|
|
for len(toVisit) > 0 {
|
|
lr := toVisit[0]
|
|
nodeKey := lr.Resource.Name + layoutNodeKeySeparator + lr.Resource.Type
|
|
if len(lr.Layout.Resources) == 0 && Primitives[lr.Resource.Type] == false {
|
|
ret[nodeKey] = lr
|
|
} else if toReplace[nodeKey] != nil {
|
|
toReplace[nodeKey].Resources = lr.Resources
|
|
}
|
|
toVisit = append(toVisit, lr.Resources...)
|
|
toVisit = toVisit[1:]
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// ExpandTemplate expands the supplied template, and returns a configuration.
|
|
func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
|
|
// We have a fencepost problem here.
|
|
// 1. Start by trying to resolve any missing templates
|
|
// 2. Expand the configuration using all the of the imports available to us at this point
|
|
// 3. Expansion may yield additional templates, so we run the type resolution again
|
|
// 4. If type resolution resulted in new imports being available, return to 2.
|
|
config := &Configuration{}
|
|
if err := yaml.Unmarshal([]byte(t.Content), config); err != nil {
|
|
e := fmt.Errorf("Unable to unmarshal configuration (%s): %s\n", err, t.Content)
|
|
return nil, e
|
|
}
|
|
|
|
var finalLayout *Layout
|
|
needResolve := map[string]*LayoutResource{}
|
|
|
|
// Start things off by attempting to resolve the templates in a first pass.
|
|
newImp, err := e.typeResolver.ResolveTypes(config, t.Imports)
|
|
if err != nil {
|
|
e := fmt.Errorf("type resolution failed:%s\n", err)
|
|
return nil, expanderError(&t, e)
|
|
}
|
|
|
|
t.Imports = append(t.Imports, newImp...)
|
|
|
|
for {
|
|
// Now expand with everything imported.
|
|
result, err := e.expandTemplate(&t)
|
|
if err != nil {
|
|
e := fmt.Errorf("template expansion:%s\n", err)
|
|
return nil, expanderError(&t, e)
|
|
}
|
|
|
|
// Once we set this layout, we're operating on the "needResolve" *LayoutResources,
|
|
// which are pointers into the original layout structure. After each expansion we
|
|
// lose the templates in the previous expansion, so we have to keep the first one
|
|
// around and keep appending to the pointers in it as we get more layers of expansion.
|
|
if finalLayout == nil {
|
|
finalLayout = result.Layout
|
|
}
|
|
needResolve = walkLayout(result.Layout, needResolve)
|
|
|
|
newImp, err = e.typeResolver.ResolveTypes(result.Config, nil)
|
|
if err != nil {
|
|
e := fmt.Errorf("type resolution failed:%s\n", err)
|
|
return nil, expanderError(&t, e)
|
|
}
|
|
|
|
// If the new imports contain nothing, we are done. Everything is fully expanded.
|
|
if len(newImp) == 0 {
|
|
result.Layout = finalLayout
|
|
return result, nil
|
|
}
|
|
|
|
t.Imports = append(t.Imports, newImp...)
|
|
var content []byte
|
|
content, err = yaml.Marshal(result.Config)
|
|
t.Content = string(content)
|
|
if err != nil {
|
|
e := fmt.Errorf("Unable to unmarshal response from expander (%s): %s\n",
|
|
err, result.Config)
|
|
return nil, expanderError(&t, e)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (e *expander) expandTemplate(t *Template) (*ExpandedTemplate, error) {
|
|
j, err := json.Marshal(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response, err := http.Post(e.getBaseURL(), "application/json", ioutil.NopCloser(bytes.NewReader(j)))
|
|
if err != nil {
|
|
e := fmt.Errorf("http POST failed:%s\n", err)
|
|
return nil, e
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode != http.StatusOK {
|
|
err := fmt.Errorf("expander service response:%v", response)
|
|
return nil, err
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
if err != nil {
|
|
e := fmt.Errorf("error reading response:%s\n", err)
|
|
return nil, e
|
|
}
|
|
|
|
er := &ExpansionResponse{}
|
|
if err := json.Unmarshal(body, er); err != nil {
|
|
e := fmt.Errorf("cannot unmarshal response body (%s):%s\n", err, body)
|
|
return nil, e
|
|
}
|
|
|
|
template, err := er.Unmarshal()
|
|
if err != nil {
|
|
e := fmt.Errorf("cannot unmarshal response yaml (%s):%v\n", err, er)
|
|
return nil, e
|
|
}
|
|
|
|
return template, nil
|
|
}
|
|
|
|
// ExpansionResponse describes the results of marshaling an ExpandedTemplate.
|
|
type ExpansionResponse struct {
|
|
Config string `json:"config"`
|
|
Layout string `json:"layout"`
|
|
}
|
|
|
|
// Unmarshal creates and returns an ExpandedTemplate from an ExpansionResponse.
|
|
func (er *ExpansionResponse) Unmarshal() (*ExpandedTemplate, error) {
|
|
template := &ExpandedTemplate{}
|
|
if err := yaml.Unmarshal([]byte(er.Config), &template.Config); err != nil {
|
|
return nil, fmt.Errorf("cannot unmarshal config (%s):\n%s", err, er.Config)
|
|
}
|
|
|
|
if err := yaml.Unmarshal([]byte(er.Layout), &template.Layout); err != nil {
|
|
return nil, fmt.Errorf("cannot unmarshal layout (%s):\n%s", err, er.Layout)
|
|
}
|
|
|
|
return template, nil
|
|
}
|