Merge pull request #462 from jackgr/manager

WIP: Fourth round of refactoring for Manager
pull/477/head
Jack Greenfield 9 years ago
commit b51c3a8ee2

@ -111,12 +111,12 @@ func newManager(c *router.Context) manager.Manager {
cfg := c.Config
service := repo.NewInmemRepoService()
cp := c.CredentialProvider
repoProvider := repo.NewRepoProvider(service, repo.NewGCSRepoProvider(cp), cp)
expander := manager.NewExpander(getServiceURL(cfg.ExpanderURL, cfg.ExpanderName, expanderPort))
rp := repo.NewRepoProvider(service, repo.NewGCSRepoProvider(cp), cp)
expander := manager.NewExpander(getServiceURL(cfg.ExpanderURL, cfg.ExpanderName, expanderPort), rp)
deployer := manager.NewDeployer(getServiceURL(cfg.DeployerURL, cfg.DeployerName, deployerPort))
address := strings.TrimPrefix(getServiceURL(cfg.MongoAddress, cfg.MongoName, cfg.MongoPort), "http://")
repository := createRepository(address)
return manager.NewManager(expander, deployer, repository, repoProvider, service, c.CredentialProvider)
return manager.NewManager(expander, deployer, repository, rp, service, c.CredentialProvider)
}
func createRepository(address string) repository.Repository {
@ -202,9 +202,9 @@ func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *rout
handler := "manager: create deployment"
util.LogHandlerEntry(handler, r)
defer r.Body.Close()
t := getTemplate(w, r, handler)
if t != nil {
d, err := c.Manager.CreateDeployment(t)
depReq := getDeploymentRequest(w, r, handler)
if depReq != nil {
d, err := c.Manager.CreateDeployment(depReq)
if err != nil {
httputil.BadRequest(w, r, err)
return nil
@ -212,6 +212,7 @@ func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *rout
util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated)
}
return nil
}
@ -242,9 +243,9 @@ func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.
return err
}
t := getTemplate(w, r, handler)
if t != nil {
d, err := c.Manager.PutDeployment(name, t)
depReq := getDeploymentRequest(w, r, handler)
if depReq != nil {
d, err := c.Manager.PutDeployment(name, depReq)
if err != nil {
httputil.BadRequest(w, r, err)
return nil
@ -252,6 +253,7 @@ func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.
util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated)
}
return nil
}
@ -289,14 +291,15 @@ func getPathVariable(w http.ResponseWriter, r *http.Request, variable, handler s
return unescaped, nil
}
func getTemplate(w http.ResponseWriter, r *http.Request, handler string) *common.Template {
func getDeploymentRequest(w http.ResponseWriter, r *http.Request, handler string) *common.DeploymentRequest {
util.LogHandlerEntry(handler, r)
t := &common.Template{}
if err := httputil.Decode(w, r, t); err != nil {
depReq := &common.DeploymentRequest{}
if err := httputil.Decode(w, r, depReq); err != nil {
httputil.BadRequest(w, r, err)
return nil
}
return t
return depReq
}
func listManifestsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error {
@ -348,9 +351,9 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context
handler := "manager: expand config"
util.LogHandlerEntry(handler, r)
defer r.Body.Close()
t := getTemplate(w, r, handler)
if t != nil {
c, err := c.Manager.Expand(t)
depReq := getDeploymentRequest(w, r, handler)
if depReq != nil {
c, err := c.Manager.Expand(depReq)
if err != nil {
httputil.BadRequest(w, r, err)
return nil
@ -358,6 +361,7 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context
util.LogHandlerExitWithJSON(handler, w, c, http.StatusCreated)
}
return nil
}

@ -60,12 +60,12 @@ func TestHealthz(t *testing.T) {
func TestCreateDeployments(t *testing.T) {
c := stubContext()
tpl := &common.Template{Name: "foo"}
depReq := &common.DeploymentRequest{Name: "foo"}
s := httpHarness(c, "POST /deployments", createDeploymentHandlerFunc)
defer s.Close()
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(tpl); err != nil {
if err := json.NewEncoder(&b).Encode(depReq); err != nil {
t.Fatal(err)
}

@ -17,190 +17,138 @@ limitations under the License.
package manager
import (
"github.com/kubernetes/helm/pkg/common"
"github.com/kubernetes/helm/pkg/expansion"
"github.com/kubernetes/helm/pkg/repo"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/ghodss/yaml"
"github.com/kubernetes/helm/pkg/common"
)
/*
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 {
// ExpandedConfiguration is the structure returned by the expansion service.
type ExpandedConfiguration struct {
Config *common.Configuration `json:"config"`
Layout *common.Layout `json:"layout"`
}
// Expander abstracts interactions with the expander and deployer services.
type Expander interface {
ExpandTemplate(t *common.Template) (*ExpandedTemplate, error)
}
// TODO: Remove mockResolver when type resolver is completely excised
type mockResolver struct {
}
func (r *mockResolver) ResolveTypes(c *common.Configuration, i []*common.ImportFile) ([]*common.ImportFile, error) {
return nil, nil
ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error)
}
// NewExpander returns a new initialized Expander.
func NewExpander(url string) Expander {
tr := &mockResolver{}
return &expander{url, tr}
func NewExpander(URL string, rp repo.IRepoProvider) Expander {
if rp == nil {
rp = repo.NewRepoProvider(nil, nil, nil)
}
return &expander{expanderURL: URL, repoProvider: rp}
}
type expander struct {
repoProvider repo.IRepoProvider
expanderURL string
typeResolver TypeResolver
}
// TypeResolver finds Types in a Configuration which aren't yet reduceable to an import file
// or primitive, and attempts to replace them with a template from a URL.
type TypeResolver interface {
ResolveTypes(config *common.Configuration, imports []*common.ImportFile) ([]*common.ImportFile, error)
}
func (e *expander) getBaseURL() string {
return fmt.Sprintf("%s/expand", e.expanderURL)
}
func expanderError(t *common.Template, err error) error {
return fmt.Errorf("cannot expand template named %s (%s):\n%s", 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 *common.Layout, imports []*common.ImportFile, toReplace map[string]*common.LayoutResource) map[string]*common.LayoutResource {
ret := map[string]*common.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 && isTemplate(lr.Resource.Type, imports) {
ret[nodeKey] = lr
} else if toReplace[nodeKey] != nil {
toReplace[nodeKey].Resources = lr.Resources
}
toVisit = append(toVisit, lr.Resources...)
toVisit = toVisit[1:]
// ExpandConfiguration expands the supplied configuration and returns
// an expanded configuration.
func (e *expander) ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) {
expConf, err := e.expandConfiguration(conf)
if err != nil {
return nil, fmt.Errorf("cannot expand configuration:%s\n%v\n", err, conf)
}
return ret
return expConf, nil
}
// isTemplate returns whether a given type is a template.
func isTemplate(t string, imports []*common.ImportFile) bool {
for _, imp := range imports {
if imp.Name == t {
return true
func (e *expander) expandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) {
resources := []*common.Resource{}
layouts := []*common.LayoutResource{}
// Iterate over all of the resources in the unexpanded configuration
for _, resource := range conf.Resources {
// A primitive layout resource captures only the name and type
layout := &common.LayoutResource{
Resource: common.Resource{
Name: resource.Name, Type: resource.Type,
},
}
}
return false
}
// ExpandTemplate expands the supplied template, and returns a configuration.
// It will also update the imports in the provided template if any were added
// during type resolution.
func (e *expander) ExpandTemplate(t *common.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 := &common.Configuration{}
if err := yaml.Unmarshal([]byte(t.Content), config); err != nil {
e := fmt.Errorf("Unable to unmarshal configuration (%s): %s", err, t.Content)
return nil, e
}
// If the type is not a chart reference, then it must be primitive
if !repo.IsChartReference(resource.Type) {
// Add it to the flat list of exapnded resources
resources = append(resources, resource)
var finalLayout *common.Layout
needResolve := map[string]*common.LayoutResource{}
// Add its layout to the list of layouts at this level
layouts = append(layouts, layout)
continue
}
// 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", err)
return nil, expanderError(t, e)
}
// It is a chart, so go fetch it, decompress it and unpack it
cbr, _, err := e.repoProvider.GetChartByReference(resource.Type)
if err != nil {
return nil, err
}
t.Imports = append(t.Imports, newImp...)
defer cbr.Close()
for {
// Now expand with everything imported.
result, err := e.expandTemplate(t)
// Now, load the charts contents into strings that we can pass to exapnsion
content, err := cbr.LoadContent()
if err != nil {
e := fmt.Errorf("template expansion: %s", err)
return nil, expanderError(t, e)
return nil, err
}
// 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
// Build a request to the expansion service and call it to do the expansion
svcReq := &expansion.ServiceRequest{
ChartInvocation: resource,
Chart: content,
}
needResolve = walkLayout(result.Layout, t.Imports, needResolve)
newImp, err = e.typeResolver.ResolveTypes(result.Config, t.Imports)
svcResp, err := e.callService(svcReq)
if err != nil {
e := fmt.Errorf("type resolution failed: %s", err)
return nil, expanderError(t, e)
return nil, err
}
// If the new imports contain nothing, we are done. Everything is fully expanded.
if len(newImp) == 0 {
result.Layout = finalLayout
return result, nil
// Call ourselves recursively with the list of resources returned by expansion
expConf, err := e.expandConfiguration(svcResp)
if err != nil {
return nil, err
}
// Update imports with any new imports from type resolution.
t.Imports = append(t.Imports, newImp...)
// Append the reources returned by the recursion to the flat list of resources
resources = append(resources, expConf.Config.Resources...)
// This was not a primitive resource, so add its properties to the layout
layout.Properties = resource.Properties
// Now add the all of the layout resources returned by the recursion to the layout
layout.Resources = expConf.Layout.Resources
layouts = append(layouts, layout)
}
// All done with this level, so return the espanded configuration
return &ExpandedConfiguration{
Config: &common.Configuration{Resources: resources},
Layout: &common.Layout{Resources: layouts},
}, nil
}
func (e *expander) expandTemplate(t *common.Template) (*ExpandedTemplate, error) {
j, err := json.Marshal(t)
func (e *expander) callService(svcReq *expansion.ServiceRequest) (*common.Configuration, error) {
j, err := json.Marshal(svcReq)
if err != nil {
return nil, err
}
@ -232,37 +180,11 @@ func (e *expander) expandTemplate(t *common.Template) (*ExpandedTemplate, error)
return nil, err
}
er := &ExpansionResponse{}
if err := json.Unmarshal(body, er); err != nil {
svcResp := &common.Configuration{}
if err := json.Unmarshal(body, svcResp); err != nil {
e := fmt.Errorf("cannot unmarshal response body (%s):%s", err, body)
return nil, e
}
template, err := er.Unmarshal()
if err != nil {
e := fmt.Errorf("cannot unmarshal response yaml (%s):%v", 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
return svcResp, nil
}

@ -26,44 +26,28 @@ import (
"strings"
"testing"
"github.com/ghodss/yaml"
"github.com/kubernetes/helm/pkg/chart"
"github.com/kubernetes/helm/pkg/common"
"github.com/kubernetes/helm/pkg/expansion"
"github.com/kubernetes/helm/pkg/repo"
"github.com/kubernetes/helm/pkg/util"
"github.com/ghodss/yaml"
)
var validTemplateTestCaseData = common.Template{
Name: "TestTemplate",
Content: string(validContentTestCaseData),
Imports: validImportFilesTestCaseData,
}
var validContentTestCaseData = []byte(`
imports:
- path: test-type.py
resources:
- name: test
type: test-type.py
properties:
test-property: test-value
`)
var validImportFilesTestCaseData = []*common.ImportFile{
{
Name: "test-type.py",
Content: "test-type.py validTemplateTestCaseData content",
},
{
Name: "test.py",
Content: "test.py validTemplateTestCaseData content",
},
{
Name: "test2.py",
Content: "test2.py validTemplateTestCaseData content",
},
}
var (
TestRepoBucket = "kubernetes-charts-testing"
TestRepoURL = "gs://" + TestRepoBucket
TestChartName = "frobnitz"
TestChartVersion = "0.0.1"
TestArchiveName = TestChartName + "-" + TestChartVersion + ".tgz"
TestResourceType = TestRepoURL + "/" + TestArchiveName
TestRepoType = string(repo.GCSRepoType)
TestRepoFormat = string(repo.GCSRepoFormat)
TestRepoCredentialName = "default"
TestRepoName = TestRepoBucket
)
var validConfigTestCaseData = []byte(`
var validResponseTestCaseData = []byte(`
resources:
- name: test-service
properties:
@ -93,6 +77,26 @@ resources:
var validLayoutTestCaseData = []byte(`
resources:
- name: test_invocation
resources:
- name: test-service
type: Service
- name: test-rc
type: ReplicationController
- name: test3-service
type: Service
- name: test3-rc
type: ReplicationController
- name: test4-service
type: Service
- name: test4-rc
type: ReplicationController
type: gs://kubernetes-charts-testing/frobnitz-0.0.1.tgz
`)
/*
[]byte(`
resources:
- name: test
properties:
test-property: test-value
@ -126,18 +130,12 @@ resources:
type: test2.jinja
`)
var validResponseTestCaseData = ExpansionResponse{
Config: string(validConfigTestCaseData),
Layout: string(validLayoutTestCaseData),
}
var roundTripContent = `
config:
resources:
- name: test
type: test.py
properties:
test: test
resources:
- name: test
type: test.py
properties:
test: test
`
var roundTripExpanded = `
@ -207,35 +205,58 @@ layout:
test: test
`
var roundTripTemplate = common.Template{
Name: "TestTemplate",
Content: roundTripContent,
Imports: nil,
var roundTripResponse = &ExpandedConfiguration{
Config: roundTripExpanded,
}
var roundTripResponse2 = &ExpandedConfiguration{
Config: roundTripExpanded2,
}
var roundTripResponses = []*ExpandedConfiguration{
roundTripResponse,
roundTripResponse2,
}
*/
type mockRepoProvider struct {
}
func (m *mockRepoProvider) GetChartByReference(reference string) (*chart.Chart, repo.IChartRepo, error) {
return &chart.Chart{}, nil, nil
}
func (m *mockRepoProvider) GetRepoByChartURL(URL string) (repo.IChartRepo, error) {
return nil, nil
}
func (m *mockRepoProvider) GetRepoByURL(URL string) (repo.IChartRepo, error) {
return nil, nil
}
type ExpanderTestCase struct {
Description string
Error string
Handler func(w http.ResponseWriter, r *http.Request)
ValidResponse *ExpandedTemplate
ValidResponse *ExpandedConfiguration
}
func TestExpandTemplate(t *testing.T) {
roundTripResponse := &ExpandedTemplate{}
if err := yaml.Unmarshal([]byte(finalExpanded), roundTripResponse); err != nil {
panic(err)
}
// roundTripResponse := &ExpandedConfiguration{}
// if err := yaml.Unmarshal([]byte(finalExpanded), roundTripResponse); err != nil {
// panic(err)
// }
tests := []ExpanderTestCase{
{
"expect success for ExpandTemplate",
"expect success for ExpandConfiguration",
"",
expanderSuccessHandler,
getValidResponse(t, "expect success for ExpandTemplate"),
getValidExpandedConfiguration(),
},
{
"expect error for ExpandTemplate",
"cannot expand template",
"expect error for ExpandConfiguration",
"simulated failure",
expanderErrorHandler,
nil,
},
@ -245,12 +266,23 @@ func TestExpandTemplate(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(etc.Handler))
defer ts.Close()
expander := NewExpander(ts.URL)
actualResponse, err := expander.ExpandTemplate(&validTemplateTestCaseData)
expander := NewExpander(ts.URL, getTestRepoProvider(t))
resource := &common.Resource{
Name: "test_invocation",
Type: TestResourceType,
}
conf := &common.Configuration{
Resources: []*common.Resource{
resource,
},
}
actualResponse, err := expander.ExpandConfiguration(conf)
if err != nil {
message := err.Error()
if etc.Error == "" {
t.Errorf("Error in test case %s when there should not be.", etc.Description)
t.Errorf("unexpected error in test case %s: %s", etc.Description, err)
}
if !strings.Contains(message, etc.Error) {
t.Errorf("error in test case:%s:%s\n", etc.Description, message)
@ -270,42 +302,45 @@ func TestExpandTemplate(t *testing.T) {
}
}
func getValidResponse(t *testing.T, description string) *ExpandedTemplate {
response, err := validResponseTestCaseData.Unmarshal()
if err != nil {
t.Errorf("cannot unmarshal valid response for test case '%s': %s\n", description, err)
func getValidServiceResponse() *common.Configuration {
conf := &common.Configuration{}
if err := yaml.Unmarshal(validResponseTestCaseData, conf); err != nil {
panic(fmt.Errorf("cannot unmarshal valid response: %s\n", err))
}
return response
return conf
}
func expanderErrorHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
http.Error(w, "something failed", http.StatusInternalServerError)
}
func getValidExpandedConfiguration() *ExpandedConfiguration {
conf := getValidServiceResponse()
layout := &common.Layout{}
if err := yaml.Unmarshal(validLayoutTestCaseData, layout); err != nil {
panic(fmt.Errorf("cannot unmarshal valid response: %s\n", err))
}
var roundTripResponse = ExpansionResponse{
Config: roundTripExpanded,
Layout: roundTripLayout,
}
return &ExpandedConfiguration{Config: conf, Layout: layout}
var roundTripResponse2 = ExpansionResponse{
Config: roundTripExpanded2,
Layout: roundTripLayout2,
}
var roundTripResponses = []ExpansionResponse{
roundTripResponse,
roundTripResponse2,
func expanderErrorHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
http.Error(w, "simulated failure", http.StatusInternalServerError)
}
/*
func roundTripHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
handler := "expandybird: expand"
util.LogHandlerEntry(handler, r)
if len(roundTripResponses) < 1 {
http.Error(w, "Too many calls to round trip handler", http.StatusInternalServerError)
return
}
util.LogHandlerExitWithJSON(handler, w, roundTripResponses[0], http.StatusOK)
roundTripResponses = roundTripResponses[1:]
}
*/
func expanderSuccessHandler(w http.ResponseWriter, r *http.Request) {
handler := "expandybird: expand"
@ -318,19 +353,37 @@ func expanderSuccessHandler(w http.ResponseWriter, r *http.Request) {
return
}
template := &common.Template{}
if err := json.Unmarshal(body, template); err != nil {
svcReq := &expansion.ServiceRequest{}
if err := json.Unmarshal(body, svcReq); err != nil {
status := fmt.Sprintf("cannot unmarshal request body:%s\n%s\n", err, body)
http.Error(w, status, http.StatusInternalServerError)
return
}
if !reflect.DeepEqual(validTemplateTestCaseData, *template) {
status := fmt.Sprintf("error in http handler:\nwant:%s\nhave:%s\n",
util.ToJSONOrError(validTemplateTestCaseData), util.ToJSONOrError(template))
http.Error(w, status, http.StatusInternalServerError)
return
/*
if !reflect.DeepEqual(validRequestTestCaseData, *svcReq) {
status := fmt.Sprintf("error in http handler:\nwant:%s\nhave:%s\n",
util.ToJSONOrError(validRequestTestCaseData), util.ToJSONOrError(template))
http.Error(w, status, http.StatusInternalServerError)
return
}
*/
svcResp := getValidServiceResponse()
util.LogHandlerExitWithJSON(handler, w, svcResp, http.StatusOK)
}
func getTestRepoProvider(t *testing.T) repo.IRepoProvider {
rs := repo.NewInmemRepoService()
rp := repo.NewRepoProvider(rs, nil, nil)
tr, err := repo.NewRepo(TestRepoURL, TestRepoCredentialName, TestRepoName, TestRepoFormat, TestRepoType)
if err != nil {
t.Fatalf("cannot create test repository: %s", err)
}
if err := rs.CreateRepo(tr); err != nil {
t.Fatalf("cannot initialize repository service: %s", err)
}
util.LogHandlerExitWithJSON(handler, w, validResponseTestCaseData, http.StatusOK)
return rp
}

@ -33,14 +33,14 @@ type Manager interface {
// Deployments
ListDeployments() ([]common.Deployment, error)
GetDeployment(name string) (*common.Deployment, error)
CreateDeployment(t *common.Template) (*common.Deployment, error)
CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error)
DeleteDeployment(name string, forget bool) (*common.Deployment, error)
PutDeployment(name string, t *common.Template) (*common.Deployment, error)
PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error)
// Manifests
ListManifests(deploymentName string) (map[string]*common.Manifest, error)
GetManifest(deploymentName string, manifest string) (*common.Manifest, error)
Expand(t *common.Template) (*common.Manifest, error)
Expand(t *common.DeploymentRequest) (*common.Manifest, error)
// Charts
ListCharts() ([]string, error)
@ -125,27 +125,27 @@ func (m *manager) GetManifest(deploymentName string, manifestName string) (*comm
return d, nil
}
// CreateDeployment expands the supplied template, creates the resulting
// configuration in the cluster, creates a new deployment that tracks it,
// and stores the deployment in the repository. Returns the deployment.
func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, error) {
log.Printf("Creating deployment: %s", t.Name)
_, err := m.repository.CreateDeployment(t.Name)
// CreateDeployment expands the supplied configuration, creates the resulting
// resources in the cluster, creates a new deployment that tracks it, stores the
// deployment in the repository and returns the deployment.
func (m *manager) CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error) {
log.Printf("Creating deployment: %s", depReq.Name)
_, err := m.repository.CreateDeployment(depReq.Name)
if err != nil {
log.Printf("CreateDeployment failed %v", err)
return nil, err
}
manifest, err := m.createManifest(t)
manifest, err := m.Expand(depReq)
if err != nil {
log.Printf("Manifest creation failed: %v", err)
m.repository.SetDeploymentState(t.Name, failState(err))
m.repository.SetDeploymentState(depReq.Name, failState(err))
return nil, err
}
if err := m.repository.AddManifest(manifest); err != nil {
log.Printf("AddManifest failed %v", err)
m.repository.SetDeploymentState(t.Name, failState(err))
m.repository.SetDeploymentState(depReq.Name, failState(err))
return nil, err
}
@ -153,7 +153,7 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro
if err != nil {
// Deployment failed, mark as failed
log.Printf("CreateConfiguration failed: %v", err)
m.repository.SetDeploymentState(t.Name, failState(err))
m.repository.SetDeploymentState(depReq.Name, failState(err))
// If we failed before being able to create some of the resources, then
// return the failure as such. Otherwise, we're going to add the manifest
@ -166,40 +166,24 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro
errs := getResourceErrors(actualConfig)
if len(errs) > 0 {
e := fmt.Errorf("Found resource errors during deployment: %v", errs)
m.repository.SetDeploymentState(t.Name, failState(e))
m.repository.SetDeploymentState(depReq.Name, failState(e))
return nil, e
}
m.repository.SetDeploymentState(t.Name, &common.DeploymentState{Status: common.DeployedStatus})
m.repository.SetDeploymentState(depReq.Name, &common.DeploymentState{Status: common.DeployedStatus})
}
// Update the manifest with the actual state of the reified resources
manifest.ExpandedConfig = actualConfig
if err := m.repository.SetManifest(manifest); err != nil {
log.Printf("SetManifest failed %v", err)
m.repository.SetDeploymentState(t.Name, failState(err))
m.repository.SetDeploymentState(depReq.Name, failState(err))
return nil, err
}
// Finally update the type instances for this deployment.
m.setChartInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name)
}
func (m *manager) createManifest(t *common.Template) (*common.Manifest, error) {
et, err := m.expander.ExpandTemplate(t)
if err != nil {
log.Printf("Expansion failed %v", err)
return nil, err
}
return &common.Manifest{
Name: generateManifestName(),
Deployment: t.Name,
InputConfig: t,
ExpandedConfig: et.Config,
Layout: et.Layout,
}, nil
m.setChartInstances(depReq.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(depReq.Name)
}
func (m *manager) setChartInstances(deploymentName string, manifestName string, layout *common.Layout) {
@ -284,13 +268,13 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment
// PutDeployment replaces the configuration of the deployment with
// the supplied identifier in the cluster, and returns the deployment.
func (m *manager) PutDeployment(name string, t *common.Template) (*common.Deployment, error) {
func (m *manager) PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error) {
_, err := m.repository.GetValidDeployment(name)
if err != nil {
return nil, err
}
manifest, err := m.createManifest(t)
manifest, err := m.Expand(depReq)
if err != nil {
log.Printf("Manifest creation failed: %v", err)
m.repository.SetDeploymentState(name, failState(err))
@ -311,20 +295,23 @@ func (m *manager) PutDeployment(name string, t *common.Template) (*common.Deploy
}
// Finally update the type instances for this deployment.
m.setChartInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name)
m.setChartInstances(depReq.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(depReq.Name)
}
func (m *manager) Expand(t *common.Template) (*common.Manifest, error) {
et, err := m.expander.ExpandTemplate(t)
func (m *manager) Expand(depReq *common.DeploymentRequest) (*common.Manifest, error) {
expConf, err := m.expander.ExpandConfiguration(&depReq.Configuration)
if err != nil {
log.Printf("Expansion failed %v", err)
return nil, err
}
return &common.Manifest{
ExpandedConfig: et.Config,
Layout: et.Layout,
Name: generateManifestName(),
Deployment: depReq.Name,
InputConfig: &depReq.Configuration,
ExpandedConfig: expConf.Config,
Layout: expConf.Layout,
}, nil
}
@ -386,7 +373,7 @@ func (m *manager) RemoveRepo(repoName string) error {
return m.service.DeleteRepo(repoURL)
}
// GetRepo returns the repository with the given URL
// GetRepo returns the repository with the given name
func (m *manager) GetRepo(repoName string) (repo.IRepo, error) {
repoURL, err := m.service.GetRepoURLByName(repoName)
if err != nil {

@ -26,8 +26,6 @@ import (
"testing"
)
var template = common.Template{Name: "test", Content: "test"}
var layout = common.Layout{
Resources: []*common.LayoutResource{{Resource: common.Resource{Name: "test", Type: "test"}}},
}
@ -47,7 +45,9 @@ var resourcesWithFailureState = common.Configuration{
},
}},
}
var expandedConfig = ExpandedTemplate{
var deploymentRequest = common.DeploymentRequest{Name: "test", Configuration: configuration}
var expandedConfig = ExpandedConfiguration{
Config: &configuration,
Layout: &layout,
}
@ -70,8 +70,8 @@ var errTest = errors.New("test error")
type expanderStub struct{}
func (expander *expanderStub) ExpandTemplate(t *common.Template) (*ExpandedTemplate, error) {
if reflect.DeepEqual(*t, template) {
func (expander *expanderStub) ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) {
if reflect.DeepEqual(conf, &configuration) {
return &expandedConfig, nil
}
@ -333,25 +333,25 @@ func TestGetManifest(t *testing.T) {
func TestCreateDeployment(t *testing.T) {
testRepository.reset()
testDeployer.reset()
d, err := testManager.CreateDeployment(&template)
d, err := testManager.CreateDeployment(&deploymentRequest)
if !reflect.DeepEqual(d, &deployment) || err != nil {
t.Fatalf("Expected a different set of response values from invoking CreateDeployment."+
"Received: %v, %s. Expected: %#v, %s.", d, err, &deployment, "nil")
}
if testRepository.Created[0] != template.Name {
if testRepository.Created[0] != deploymentRequest.Name {
t.Fatalf("Repository CreateDeployment was called with %s but expected %s.",
testRepository.Created[0], template.Name)
testRepository.Created[0], deploymentRequest.Name)
}
if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") {
if !strings.HasPrefix(testRepository.ManifestAdd[deploymentRequest.Name].Name, "manifest-") {
t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestAdd[template.Name].Name)
"to begin with manifest-.", testRepository.ManifestAdd[deploymentRequest.Name].Name)
}
if !strings.HasPrefix(testRepository.ManifestSet[template.Name].Name, "manifest-") {
if !strings.HasPrefix(testRepository.ManifestSet[deploymentRequest.Name].Name, "manifest-") {
t.Fatalf("Repository SetManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestSet[template.Name].Name)
"to begin with manifest-.", testRepository.ManifestSet[deploymentRequest.Name].Name)
}
if !reflect.DeepEqual(*testDeployer.Created[0], configuration) || err != nil {
@ -376,11 +376,11 @@ func TestCreateDeploymentCreationFailure(t *testing.T) {
testRepository.reset()
testDeployer.reset()
testDeployer.FailCreate = true
d, err := testManager.CreateDeployment(&template)
d, err := testManager.CreateDeployment(&deploymentRequest)
if testRepository.Created[0] != template.Name {
if testRepository.Created[0] != deploymentRequest.Name {
t.Fatalf("Repository CreateDeployment was called with %s but expected %s.",
testRepository.Created[0], template.Name)
testRepository.Created[0], deploymentRequest.Name)
}
if len(testRepository.Deleted) != 0 {
@ -406,11 +406,11 @@ func TestCreateDeploymentCreationResourceFailure(t *testing.T) {
testRepository.reset()
testDeployer.reset()
testDeployer.FailCreateResource = true
d, err := testManager.CreateDeployment(&template)
d, err := testManager.CreateDeployment(&deploymentRequest)
if testRepository.Created[0] != template.Name {
if testRepository.Created[0] != deploymentRequest.Name {
t.Fatalf("Repository CreateDeployment was called with %s but expected %s.",
testRepository.Created[0], template.Name)
testRepository.Created[0], deploymentRequest.Name)
}
if len(testRepository.Deleted) != 0 {
@ -422,14 +422,18 @@ func TestCreateDeploymentCreationResourceFailure(t *testing.T) {
t.Fatal("CreateDeployment failure did not mark deployment as failed")
}
if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") {
t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestAdd[template.Name].Name)
if manifest, ok := testRepository.ManifestAdd[deploymentRequest.Name]; ok {
if !strings.HasPrefix(manifest.Name, "manifest-") {
t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+
"to begin with manifest-.", manifest.Name)
}
}
if !strings.HasPrefix(testRepository.ManifestSet[template.Name].Name, "manifest-") {
t.Fatalf("Repository SetManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestSet[template.Name].Name)
if manifest, ok := testRepository.ManifestSet[deploymentRequest.Name]; ok {
if !strings.HasPrefix(manifest.Name, "manifest-") {
t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+
"to begin with manifest-.", manifest.Name)
}
}
if err != nil || !reflect.DeepEqual(d, &deployment) {
@ -445,25 +449,25 @@ func TestCreateDeploymentCreationResourceFailure(t *testing.T) {
func TestDeleteDeploymentForget(t *testing.T) {
testRepository.reset()
testDeployer.reset()
d, err := testManager.CreateDeployment(&template)
d, err := testManager.CreateDeployment(&deploymentRequest)
if !reflect.DeepEqual(d, &deployment) || err != nil {
t.Fatalf("Expected a different set of response values from invoking CreateDeployment."+
"Received: %v, %s. Expected: %#v, %s.", d, err, &deployment, "nil")
}
if testRepository.Created[0] != template.Name {
if testRepository.Created[0] != deploymentRequest.Name {
t.Fatalf("Repository CreateDeployment was called with %s but expected %s.",
testRepository.Created[0], template.Name)
testRepository.Created[0], deploymentRequest.Name)
}
if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") {
if !strings.HasPrefix(testRepository.ManifestAdd[deploymentRequest.Name].Name, "manifest-") {
t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestAdd[template.Name].Name)
"to begin with manifest-.", testRepository.ManifestAdd[deploymentRequest.Name].Name)
}
if !strings.HasPrefix(testRepository.ManifestSet[template.Name].Name, "manifest-") {
if !strings.HasPrefix(testRepository.ManifestSet[deploymentRequest.Name].Name, "manifest-") {
t.Fatalf("Repository SetManifest was called with %s but expected manifest name"+
"to begin with manifest-.", testRepository.ManifestSet[template.Name].Name)
"to begin with manifest-.", testRepository.ManifestSet[deploymentRequest.Name].Name)
}
if !reflect.DeepEqual(*testDeployer.Created[0], configuration) || err != nil {
@ -492,21 +496,9 @@ func TestDeleteDeploymentForget(t *testing.T) {
}
func TestExpand(t *testing.T) {
m, err := testManager.Expand(&template)
m, err := testManager.Expand(&deploymentRequest)
if err != nil {
t.Fatal("Failed to expand template into manifest.")
}
if m.Name != "" {
t.Fatalf("Name was not empty: %v", *m)
}
if m.Deployment != "" {
t.Fatalf("Deployment was not empty: %v", *m)
}
if m.InputConfig != nil {
t.Fatalf("Input config not nil: %v", *m)
t.Fatal("Failed to expand deployment request into manifest.")
}
if !reflect.DeepEqual(*m.ExpandedConfig, configuration) {

@ -83,7 +83,7 @@ func (m *mockManager) GetDeployment(name string) (*common.Deployment, error) {
return nil, errors.New("mock error: No such deployment")
}
func (m *mockManager) CreateDeployment(t *common.Template) (*common.Deployment, error) {
func (m *mockManager) CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error) {
return &common.Deployment{}, nil
}
@ -91,7 +91,7 @@ func (m *mockManager) DeleteDeployment(name string, forget bool) (*common.Deploy
return &common.Deployment{}, nil
}
func (m *mockManager) PutDeployment(name string, t *common.Template) (*common.Deployment, error) {
func (m *mockManager) PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error) {
return &common.Deployment{}, nil
}
@ -103,7 +103,7 @@ func (m *mockManager) GetManifest(deploymentName string, manifest string) (*comm
return &common.Manifest{}, nil
}
func (m *mockManager) Expand(t *common.Template) (*common.Manifest, error) {
func (m *mockManager) Expand(depReq *common.DeploymentRequest) (*common.Manifest, error) {
return &common.Manifest{}, nil
}

@ -110,8 +110,9 @@ func (c *Client) DescribeDeployment(name string) (*common.Deployment, error) {
// PostDeployment posts a deployment object to the manager service.
func (c *Client) PostDeployment(res *common.Resource) error {
// This is a stop-gap until we get this API cleaned up.
t := common.CreateDeploymentRequest{
ChartInvocation: res,
t := common.DeploymentRequest{
Configuration: common.Configuration{Resources: []*common.Resource{res}},
Name: res.Name,
}
data, err := json.Marshal(t)

@ -70,14 +70,15 @@ func (s DeploymentStatus) String() string {
type Manifest struct {
Deployment string `json:"deployment,omitempty"`
Name string `json:"name,omitempty"`
InputConfig *Template `json:"inputConfig,omitempty"`
InputConfig *Configuration `json:"inputConfig,omitempty"`
ExpandedConfig *Configuration `json:"expandedConfig,omitempty"`
Layout *Layout `json:"layout,omitempty"`
}
// CreateDeploymentRequest defines the manager API to create deployments.
type CreateDeploymentRequest struct {
ChartInvocation *Resource `json:"chart_invocation"`
// DeploymentRequest defines the manager API to create deployments.
type DeploymentRequest struct {
Configuration
Name string `json:"name"`
}
// ChartInstance defines the metadata for an instantiation of a chart.

Loading…
Cancel
Save