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.
helm/cmd/expandybird/expander/expander.go

143 lines
4.1 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 expander
import (
"bytes"
"encoding/json"
"fmt"
"github.com/ghodss/yaml"
"log"
"os/exec"
"github.com/kubernetes/helm/pkg/expansion"
)
type expander struct {
ExpansionBinary string
}
// NewExpander returns an ExpandyBird expander.
func NewExpander(binary string) expansion.Expander {
return &expander{binary}
}
type expandyBirdConfigOutput struct {
Resources []interface{} `yaml:"resources,omitempty"`
}
type expandyBirdOutput struct {
Config *expandyBirdConfigOutput `yaml:"config,omitempty"`
Layout interface{} `yaml:"layout,omitempty"`
}
// 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) {
err := expansion.ValidateRequest(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
schemaIndex := -1
for i, f := range chartMembers {
if f.Path == chartFile.Expander.Entrypoint {
entrypointIndex = i
}
if f.Path == chartFile.Schema {
schemaIndex = 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)
}
if chartFile.Schema != "" && schemaIndex == -1 {
message := fmt.Sprintf("The schema in the chart.yaml cannot be found: %s", chartFile.Schema)
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,
}
if chartFile.Schema != "" {
cmd.Env = []string{"VALIDATE_SCHEMA=1"}
}
for i, f := range chartMembers {
name := f.Path
path := f.Path
if i == entrypointIndex {
// This is how expandyBird identifies the entrypoint.
name = chartInv.Type
} else if i == schemaIndex {
// Doesn't matter what it was originally called, expandyBird expects to find it here.
name = chartInv.Type + ".schema"
}
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
}