// ExpandChart passes the given configuration to the expander and returns the // expanded configuration as a string on success. func (e *expander) ExpandChart(request *expansion.ServiceRequest) (*expansion.ServiceResponse, error) { if err := expansion.ValidateRequest(request); err != nil { return nil, err } request, err := expansion.ValidateProperties(request) if err != nil { return nil, err } chartInv := request.ChartInvocation chartFile := request.Chart.Chartfile chartMembers := request.Chart.Members if e.ExpansionBinary == "" { message := fmt.Sprintf("expansion binary cannot be empty") return nil, fmt.Errorf("%s: %s", chartInv.Name, message) } entrypointIndex := -1 for i, f := range chartMembers { if f.Path == chartFile.Expander.Entrypoint { entrypointIndex = i } } if entrypointIndex == -1 { message := fmt.Sprintf("The entrypoint in the chart.yaml cannot be found: %s", chartFile.Expander.Entrypoint) return nil, fmt.Errorf("%s: %s", chartInv.Name, message) } // Those are automatically increasing buffers, so writing arbitrary large // data here won't block the child process. var stdout bytes.Buffer var stderr bytes.Buffer // Now we convert the new chart representation into the form that classic ExpandyBird takes. chartInvJSON, err := json.Marshal(chartInv) if err != nil { return nil, fmt.Errorf("error marshalling chart invocation %s: %s", chartInv.Name, err) } content := "{ \"resources\": [" + string(chartInvJSON) + "] }" cmd := &exec.Cmd{ Path: e.ExpansionBinary, // Note, that binary name still has to be passed argv[0]. Args: []string{e.ExpansionBinary, content}, Stdout: &stdout, Stderr: &stderr, } for i, f := range chartMembers { name := f.Path path := f.Path if i == entrypointIndex { // This is how expandyBird identifies the entrypoint. name = chartInv.Type } cmd.Args = append(cmd.Args, name, path, string(f.Content)) } if err := cmd.Start(); err != nil { log.Printf("error starting expansion process: %s", err) return nil, err } cmd.Wait() log.Printf("Expansion process: pid: %d SysTime: %v UserTime: %v", cmd.ProcessState.Pid(), cmd.ProcessState.SystemTime(), cmd.ProcessState.UserTime()) if stderr.String() != "" { return nil, fmt.Errorf("%s: %s", chartInv.Name, stderr.String()) } output := &expandyBirdOutput{} if err := yaml.Unmarshal(stdout.Bytes(), output); err != nil { return nil, fmt.Errorf("cannot unmarshal expansion result (%s):\n%s", err, output) } return &expansion.ServiceResponse{Resources: output.Config.Resources}, nil }
// ExpandChart resolves the given files to a sequence of JSON-marshalable values. func (e *expander) ExpandChart(request *expansion.ServiceRequest) (*expansion.ServiceResponse, error) { err := expansion.ValidateRequest(request) if err != nil { return nil, err } request, err = expansion.ValidateProperties(request) if err != nil { return nil, err } chartInv := request.ChartInvocation chartMembers := request.Chart.Members resources := []interface{}{} for _, file := range chartMembers { name := file.Path content := file.Content tmpl := template.New(name).Funcs(sprig.HermeticTxtFuncMap()) for _, otherFile := range chartMembers { otherName := otherFile.Path otherContent := otherFile.Content if name == otherName { continue } _, err := tmpl.Parse(string(otherContent)) if err != nil { return nil, err } } // Have to put something in that resolves non-empty or Go templates get confused. _, err := tmpl.Parse("# Content begins now") if err != nil { return nil, err } tmpl, err = tmpl.Parse(string(content)) if err != nil { return nil, err } generated := bytes.NewBuffer(nil) if err := tmpl.ExecuteTemplate(generated, name, chartInv.Properties); err != nil { return nil, err } stream, err := parseYAMLStream(generated) if err != nil { return nil, fmt.Errorf("%s\nContent:\n%s", err.Error(), generated) } for _, doc := range stream { resources = append(resources, doc) } } return &expansion.ServiceResponse{Resources: resources}, nil }