diff --git a/pkg/template/doc.go b/pkg/template/doc.go new file mode 100644 index 000000000000..0ad6c5d8d15a --- /dev/null +++ b/pkg/template/doc.go @@ -0,0 +1,5 @@ +// Package template transforms project templates into api objects recognized +// by Kubernetes. It generates parameter values based on their input +// expressions and substitutes parameters found in the container env vars +// with their corresponding values. +package template diff --git a/pkg/template/example/project.json b/pkg/template/example/project.json new file mode 100644 index 000000000000..c1774e513183 --- /dev/null +++ b/pkg/template/example/project.json @@ -0,0 +1,184 @@ +{ + "id": "example1", + "name": "my-awesome-php-app", + "description": "Example PHP application with PostgreSQL database", + "buildConfigs": [ + { + "name": "mfojtik/nginx-php-app", + "type": "docker", + "sourceUri": "https://raw.githubusercontent.com/mfojtik/phpapp/master/Dockerfile", + "imageRepository": "mfojtik/nginx-php-app" + }, + { + "name": "postgres", + "type": "docker", + "sourceUri": "https://raw.githubusercontent.com/docker-library/postgres/docker/9.2/Dockerfile", + "imageRepository": "postgres" + } + ], + "imageRepositories": [ + { + "name": "mfojtik/nginx-php-app", + "url": "internal.registry.com:5000/mfojtik/phpapp" + }, + { + "name": "postgres", + "url": "registry.hub.docker.com/postgres" + } + ], + "parameters": [ + { + "name": "DB_PASSWORD", + "description": "PostgreSQL admin user password", + "type": "string", + "generate": "[a-zA-Z0-9]{8}" + }, + { + "name": "DB_USER", + "description": "PostgreSQL username", + "type": "string", + "generate": "admin[a-zA-Z0-9]{4}" + }, + { + "name": "DB_NAME", + "description": "PostgreSQL database name", + "type": "string", + "value": "mydb" + }, + { + "name": "REMOTE_KEY", + "description": "Example of remote key", + "type": "string", + "value": "[GET:http://custom.url.int]" + } + ], + "services": [ + { + "id": "database", + "kind": "Service", + "apiVersion": "v1beta1", + "port": 5432, + "selector": { + "name": "database" + } + }, + { + "id": "frontend", + "kind": "Service", + "apiVersion": "v1beta1", + "port": 8080, + "selector": { + "name": "frontend" + } + } + ], + "deploymentConfigs": [ + { + "kind": "DeploymentConfig", + "apiVersion": "v1beta1", + "labels": { + "name": "database" + }, + "desiredState": { + "replicas": 2, + "replicaSelector": { + "name": "database" + }, + "podTemplate": { + "kind": "Pod", + "apiVersion": "v1beta1", + "id": "database", + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "database", + "containers": [{ + "name": "postgresql", + "image": "postgres", + "env": [ + { + "name": "PGPASSWORD", + "value": "${DB_PASSWORD}" + }, + { + "name": "PGUSER", + "value": "${DB_USER}" + }, + { + "name": "PGDATABASE", + "value": "${DB_NAME}" + }, + { + "name": "FOO", + "value": "${BAR}" + } + ], + "ports": [ + { + "containerPort": 5432 + } + ] + } + ] + } + }, + "labels": { + "name": "database" + } + } + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1beta1", + "labels": { + "name": "frontend" + }, + "desiredState": { + "replicas": 2, + "replicaSelector": { + "name": "frontend" + }, + "podTemplate": { + "kind": "Pod", + "apiVersion": "v1beta1", + "id": "frontend", + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "frontend", + "containers": [{ + "name": "frontend", + "image": "mfojtik/nginx-php-app", + "env": [ + { + "name": "PGPASSWORD", + "value": "${DB_PASSWORD}" + }, + { + "name": "PGUSER", + "value": "${DB_USER}" + }, + { + "name": "PGDATABASE", + "value": "${DB_NAME}" + } + ], + "ports": [ + { + "containerPort": 9292, + "hostPort": 8080 + } + ] + } + ] + } + }, + "labels": { + "name": "frontend" + } + } + } + } + ] +} diff --git a/pkg/template/generator/doc.go b/pkg/template/generator/doc.go new file mode 100644 index 000000000000..515eb9e85c18 --- /dev/null +++ b/pkg/template/generator/doc.go @@ -0,0 +1,3 @@ +// Package generator defines GeneratorInterface interface +// and implements several random value generators +package generator diff --git a/pkg/template/generator/expression_value.go b/pkg/template/generator/expression_value.go new file mode 100644 index 000000000000..eca7baf0b45f --- /dev/null +++ b/pkg/template/generator/expression_value.go @@ -0,0 +1,124 @@ +package generator + +import ( + "fmt" + "math/rand" + "regexp" + "strconv" + "strings" +) + +// ExpressionValueGenerator generates random string based on the input +// expression. The input expression is a string, which may contain +// generator constructs matching "[a-zA-Z0-9]{length}" expression. +// +// Examples: +// - "test[0-9]{1}x" => "test7x" +// - "[0-1]{8}" => "01001100" +// - "0x[A-F0-9]{4}" => "0xB3AF" +// - "[a-zA-Z0-9]{8}" => "hW4yQU5i" +type ExpressionValueGenerator struct { + seed *rand.Rand +} + +const ( + Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + Numerals = "0123456789" + Ascii = Alphabet + Numerals + "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:`" +) + +var ( + rangeExp = regexp.MustCompile(`([\\]?[a-zA-Z0-9]\-?[a-zA-Z0-9]?)`) + generatorsExp = regexp.MustCompile(`\[([a-zA-Z0-9\-\\]+)\](\{([0-9]+)\})`) +) + +func init() { + RegisterGenerator(generatorsExp, func(seed *rand.Rand) (GeneratorInterface, error) { return newExpressionValueGenerator(seed) }) +} + +func newExpressionValueGenerator(seed *rand.Rand) (ExpressionValueGenerator, error) { + return ExpressionValueGenerator{seed: seed}, nil +} + +func (g ExpressionValueGenerator) GenerateValue(expression string) (interface{}, error) { + genMatches := generatorsExp.FindAllStringIndex(expression, -1) + for _, r := range genMatches { + ranges, length, err := rangesAndLength(expression[r[0]:r[1]]) + if err != nil { + return "", err + } + positions := findExpressionPos(ranges) + err = replaceWithGenerated(&expression, expression[r[0]:r[1]], positions, length, g.seed) + if err != nil { + return "", err + } + } + return expression, nil +} + +func alphabetSlice(from, to byte) (string, error) { + leftPos := strings.Index(Ascii, string(from)) + rightPos := strings.LastIndex(Ascii, string(to)) + if leftPos > rightPos { + return "", fmt.Errorf("Invalid range specified: %s-%s", string(from), string(to)) + } + return Ascii[leftPos:rightPos], nil +} + +func replaceWithGenerated(s *string, expression string, ranges [][]byte, length int, seed *rand.Rand) error { + var alphabet string + for _, r := range ranges { + switch string(r[0]) + string(r[1]) { + case `\w`: + alphabet += Ascii + case `\d`: + alphabet += Numerals + case `\a`: + alphabet += Alphabet + Numerals + default: + if slice, err := alphabetSlice(r[0], r[1]); err != nil { + return err + } else { + alphabet += slice + } + } + } + if len(alphabet) == 0 { + return fmt.Errorf("Empty range in expression: %s", expression) + } + result := make([]byte, length) + for i := 0; i < length; i++ { + result[i] = alphabet[seed.Intn(len(alphabet))] + } + *s = strings.Replace(*s, expression, string(result), 1) + return nil +} + +func findExpressionPos(s string) [][]byte { + matches := rangeExp.FindAllStringIndex(s, -1) + result := make([][]byte, len(matches)) + for i, r := range matches { + result[i] = []byte{s[r[0]], s[r[1]-1]} + } + return result +} + +func parseLength(s string) (int, error) { + lengthStr := string(s[strings.LastIndex(s, "{")+1 : len(s)-1]) + if l, err := strconv.Atoi(lengthStr); err != nil { + return 0, fmt.Errorf("Unable to parse length from %v", s) + } else { + return l, nil + } +} + +func rangesAndLength(s string) (string, int, error) { + l := strings.LastIndex(s, "{") + if l > 0 { + expr := s[0:strings.LastIndex(s, "{")] + length, err := parseLength(s) + return expr, length, err + } else { + return "", 0, fmt.Errorf("Unable to parse length from %v", s) + } +} diff --git a/pkg/template/generator/generator.go b/pkg/template/generator/generator.go new file mode 100644 index 000000000000..a451a1fafc93 --- /dev/null +++ b/pkg/template/generator/generator.go @@ -0,0 +1,59 @@ +package generator + +import ( + "fmt" + "math/rand" + "regexp" + "sync" +) + +// GeneratorInterface is an abstract interface for generating +// random values from an input expression +type GeneratorInterface interface { + GenerateValue(expression string) (interface{}, error) +} + +// Generator implements GeneratorInterface +type Generator struct { + seed *rand.Rand +} + +func New(seed *rand.Rand) (Generator, error) { + return Generator{seed: seed}, nil +} + +// GenerateValue loops over registered generators and tries to find the +// one matching the input expression. If match is found, it then generates +// value using that matching generator +func (g *Generator) GenerateValue(expression string) (interface{}, error) { + if len(generators) <= 0 { + return expression, fmt.Errorf("No registered generators.") + } + + for regexp, generatorFactory := range generators { + if regexp.FindStringIndex(expression) != nil { + generator, err := generatorFactory(g.seed) + if err != nil { + return expression, err + } + return generator.GenerateValue(expression) + } + } + + return expression, fmt.Errorf("No matching generators found.") +} + +// GeneratorFactory is an abstract factory for creating generators +// (objects that implement GeneratorInterface interface) +type GeneratorFactory func(*rand.Rand) (GeneratorInterface, error) + +// generators stores registered generators +var generators = make(map[*regexp.Regexp]GeneratorFactory) +var generatorsMutex sync.Mutex + +// RegisterGenerator registers new generator for a certain input expression +func RegisterGenerator(r *regexp.Regexp, f GeneratorFactory) { + generatorsMutex.Lock() + defer generatorsMutex.Unlock() + generators[r] = f +} diff --git a/pkg/template/generator/generator_test.go b/pkg/template/generator/generator_test.go new file mode 100644 index 000000000000..8b4bf4ee8ece --- /dev/null +++ b/pkg/template/generator/generator_test.go @@ -0,0 +1,76 @@ +package generator + +import ( + "fmt" + "math/rand" + "net" + "net/http" + "testing" + + generator "." +) + +func TestCreateGenerator(t *testing.T) { + _, err := generator.New(rand.New(rand.NewSource(1337))) + if err != nil { + t.Errorf("Failed to create generator") + } +} + +func TestExpressionValueGenerator(t *testing.T) { + sampleGenerator, _ := generator.New(rand.New(rand.NewSource(1337))) + + var tests = []struct { + Expression string + ExpectedValue string + }{ + {"test[A-Z0-9]{4}template", "testQ3HVtemplate"}, + {"[\\d]{4}", "6841"}, + {"[\\w]{4}", "DVgK"}, + {"[\\a]{10}", "nFWmvmjuaZ"}, + } + + for _, test := range tests { + value, _ := sampleGenerator.GenerateValue(test.Expression) + if value != test.ExpectedValue { + t.Errorf("Failed to generate expected value from %s\n. Generated: %s\n. Expected: %s\n", test.Expression, value, test.ExpectedValue) + } + } +} + +func TestPasswordGenerator(t *testing.T) { + sampleGenerator, _ := generator.New(rand.New(rand.NewSource(1337))) + + value, _ := sampleGenerator.GenerateValue("password") + if value != "4U390O49" { + t.Errorf("Failed to generate expected password. Generated: %s\n. Expected: %s\n", value, "4U390O49") + } +} + +func TestErrRemoteValueGenerator(t *testing.T) { + sampleGenerator, _ := generator.New(rand.New(rand.NewSource(1337))) + + _, err := sampleGenerator.GenerateValue("[GET:http://api.example.com/new]") + if err == nil { + t.Errorf("Expected error while fetching non-existent remote.") + } +} + +func TestFakeRemoteValueGenerator(t *testing.T) { + // Run fake remote server + http.HandleFunc("/v1/value/generate", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "NewRandomString") + }) + listener, _ := net.Listen("tcp", ":12345") + go http.Serve(listener, nil) + + sampleGenerator, _ := generator.New(rand.New(rand.NewSource(1337))) + + value, err := sampleGenerator.GenerateValue("[GET:http://127.0.0.1:12345/v1/value/generate]") + if err != nil { + t.Errorf(err.Error()) + } + if value != "NewRandomString" { + t.Errorf("Failed to fetch remote value using GET.") + } +} diff --git a/pkg/template/generator/password.go b/pkg/template/generator/password.go new file mode 100644 index 000000000000..e2c1f3da973f --- /dev/null +++ b/pkg/template/generator/password.go @@ -0,0 +1,30 @@ +package generator + +import ( + "fmt" + "math/rand" + "regexp" +) + +// PasswordGenerator generates string of 8 random alphanumeric characters +// from an input expression matching "password" string. +// +// Example: +// - "password" => "hW4yQU5i" +type PasswordGenerator struct { + expressionValueGenerator ExpressionValueGenerator +} + +var passwordExp = regexp.MustCompile(`password`) + +func init() { + RegisterGenerator(passwordExp, func(seed *rand.Rand) (GeneratorInterface, error) { return newPasswordGenerator(seed) }) +} + +func newPasswordGenerator(seed *rand.Rand) (PasswordGenerator, error) { + return PasswordGenerator{ExpressionValueGenerator{seed: seed}}, nil +} + +func (g PasswordGenerator) GenerateValue(string) (interface{}, error) { + return g.expressionValueGenerator.GenerateValue(fmt.Sprintf("[\\a]{%d}", 8)) +} diff --git a/pkg/template/generator/remote_value.go b/pkg/template/generator/remote_value.go new file mode 100644 index 000000000000..d9a05a4b92de --- /dev/null +++ b/pkg/template/generator/remote_value.go @@ -0,0 +1,48 @@ +package generator + +import ( + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "regexp" + "strings" +) + +// RemoteValueGenerator fetches random value from an external url +// endpoint based on the "[GET:]" expression. +// +// Example: +// - "[GET:http://api.example.com/generateRandomValue]" +type RemoteValueGenerator struct { +} + +var remoteExp = regexp.MustCompile(`\[GET\:(http(s)?:\/\/(.+))\]`) + +func init() { + RegisterGenerator(remoteExp, func(*rand.Rand) (GeneratorInterface, error) { return newRemoteValueGenerator(nil) }) +} + +func newRemoteValueGenerator(*rand.Rand) (RemoteValueGenerator, error) { + return RemoteValueGenerator{}, nil +} + +func (g RemoteValueGenerator) GenerateValue(expression string) (interface{}, error) { + matches := remoteExp.FindAllStringIndex(expression, -1) + if len(matches) < 1 { + return expression, fmt.Errorf("No matches found.") + } + for _, r := range matches { + response, err := http.Get(expression[5 : len(expression)-1]) + if err != nil { + return "", err + } + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", err + } + expression = strings.Replace(expression, expression[r[0]:r[1]], strings.TrimSpace(string(body)), 1) + } + return expression, nil +} diff --git a/pkg/template/template.go b/pkg/template/template.go new file mode 100644 index 000000000000..354f530bed51 --- /dev/null +++ b/pkg/template/template.go @@ -0,0 +1,96 @@ +package template + +import ( + "math/rand" + "regexp" + "strings" + + "github.com/openshift/origin/pkg/template/generator" +) + +var parameterExp = regexp.MustCompile(`\$\{([a-zA-Z0-9\_]+)\}`) + +// AddCustomTemplateParameter allow to pass the custom parameter to the +// template. It will replace the existing parameter, when it is already +// defined in the template. +func AddCustomTemplateParameter(p Parameter, t *Template) { + if param := GetTemplateParameterByName(p.Name, t); param != nil { + *param = p + } else { + t.Parameters = append(t.Parameters, p) + } +} + +// GetTemplateParameterByName will return the pointer to the Template +// parameter based on the Parameter name. +func GetTemplateParameterByName(name string, t *Template) *Parameter { + for i, param := range t.Parameters { + if param.Name == name { + return &(t.Parameters[i]) + } + } + return nil +} + +// ProcessParameters searches for every parameter expression +// in the env of each deploymentConfigs->podTemplate->containers and +// substitutes it with it's corresponding parameter value. +// +// Parameter expression example: +// - ${PARAMETER_NAME} +func ProcessEnvParameters(t *Template) error { + // Make searching for given parameter name/value more effective + paramMap := make(map[string]string, len(t.Parameters)) + for _, param := range t.Parameters { + paramMap[param.Name] = param.Value + } + + // Loop over all env vars and substitute parameter expressions with values + for i, _ := range t.DeploymentConfigs { + manifest := &t.DeploymentConfigs[i].DesiredState.PodTemplate.DesiredState.Manifest + for j, _ := range manifest.Containers { + for k, _ := range manifest.Containers[j].Env { + envValue := &manifest.Containers[j].Env[k].Value + // Match all parameter expressions found in the given env var + for _, match := range parameterExp.FindAllStringSubmatch(*envValue, -1) { + // Substitute expression with its value, if corresponding parameter found + if len(match) > 1 { + if paramValue, found := paramMap[match[1]]; found { + *envValue = strings.Replace(*envValue, match[0], paramValue, 1) + } + } + } + } + } + } + return nil +} + +// GenerateParameterValue generates Value for each Parameter of the given +// Template that has Generate field specified and doesn't have any Value yet. +// +// Examples of what certain Generate field values generate: +// - "test[0-9]{1}x" => "test7x" +// - "[0-1]{8}" => "01001100" +// - "0x[A-F0-9]{4}" => "0xB3AF" +// - "[a-zA-Z0-9]{8}" => "hW4yQU5i" +// - "password" => "hW4yQU5i" +// - "[GET:http://api.example.com/generateRandomValue]" => remote string +func GenerateParameterValues(t *Template, seed *rand.Rand) error { + for i, _ := range t.Parameters { + p := &t.Parameters[i] + if p.Generate != "" && p.Value == "" { + // Inherit the seed from parameter + generator, err := generator.New(seed) + if err != nil { + return err + } + value, err := generator.GenerateValue(p.Generate) + if err != nil { + return err + } + p.Value = value.(string) + } + } + return nil +} diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go new file mode 100644 index 000000000000..718a997bb583 --- /dev/null +++ b/pkg/template/template_test.go @@ -0,0 +1,59 @@ +package template + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "testing" +) + +func TestNewTemplate(t *testing.T) { + var template Template + + jsonData, _ := ioutil.ReadFile("example/project.json") + if err := json.Unmarshal(jsonData, &template); err != nil { + t.Errorf("Unable to process the JSON template file: %v", err) + } +} + +func TestCustomParameter(t *testing.T) { + var template Template + + jsonData, _ := ioutil.ReadFile("example/project.json") + json.Unmarshal(jsonData, &template) + + AddCustomTemplateParameter(Parameter{Name: "CUSTOM_PARAM", Value: "1"}, &template) + AddCustomTemplateParameter(Parameter{Name: "CUSTOM_PARAM", Value: "2"}, &template) + + if p := GetTemplateParameterByName("CUSTOM_PARAM", &template); p == nil { + t.Errorf("Unable to add a custom parameter to the template") + } else { + if p.Value != "2" { + t.Errorf("Unable to replace the custom parameter value in template") + } + } + +} + +func ExampleProcessTemplateParameters() { + var template Template + + jsonData, _ := ioutil.ReadFile("example/project.json") + json.Unmarshal(jsonData, &template) + + // Define custom parameter for transformation: + customParam := Parameter{Name: "CUSTOM_PARAM1", Value: "1"} + AddCustomTemplateParameter(customParam, &template) + + // Generate parameter values + GenerateParameterValues(&template, rand.New(rand.NewSource(1337))) + + // Substitute parameters with values in container env vars + ProcessEnvParameters(&template) + + result, _ := json.Marshal(template) + fmt.Println(string(result)) + // Output: + // {"id":"example1","creationTimestamp":null,"buildConfigs":[{"name":"mfojtik/nginx-php-app","type":"docker","sourceUri":"https://raw.githubusercontent.com/mfojtik/phpapp/master/Dockerfile","imageRepository":"mfojtik/nginx-php-app"},{"name":"postgres","type":"docker","sourceUri":"https://raw.githubusercontent.com/docker-library/postgres/docker/9.2/Dockerfile","imageRepository":"postgres"}],"imageRepositories":[{"creationTimestamp":null,"name":"mfojtik/nginx-php-app","url":"internal.registry.com:5000/mfojtik/phpapp"},{"creationTimestamp":null,"name":"postgres","url":"registry.hub.docker.com/postgres"}],"parameters":[{"name":"DB_PASSWORD","description":"PostgreSQL admin user password","type":"string","generate":"[a-zA-Z0-9]{8}","value":"bQPdwNJi"},{"name":"DB_USER","description":"PostgreSQL username","type":"string","generate":"admin[a-zA-Z0-9]{4}","value":"adminJwWP"},{"name":"DB_NAME","description":"PostgreSQL database name","type":"string","generate":"","value":"mydb"},{"name":"REMOTE_KEY","description":"Example of remote key","type":"string","generate":"","value":"[GET:http://custom.url.int]"},{"name":"CUSTOM_PARAM1","description":"","type":"","generate":"","value":"1"}],"services":[{"kind":"Service","id":"database","creationTimestamp":null,"apiVersion":"v1beta1","port":5432,"selector":{"name":"database"},"containerPort":0},{"kind":"Service","id":"frontend","creationTimestamp":null,"apiVersion":"v1beta1","port":8080,"selector":{"name":"frontend"},"containerPort":0}],"deploymentConfigs":[{"kind":"DeploymentConfig","creationTimestamp":null,"apiVersion":"v1beta1","labels":{"name":"database"},"desiredState":{"replicas":2,"replicaSelector":{"name":"database"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta1","id":"database","volumes":null,"containers":[{"name":"postgresql","image":"postgres","ports":[{"containerPort":5432}],"env":[{"name":"PGPASSWORD","value":"bQPdwNJi"},{"name":"PGUSER","value":"adminJwWP"},{"name":"PGDATABASE","value":"mydb"},{"name":"FOO","value":"${BAR}"}]}]},"restartpolicy":{}},"labels":{"name":"database"}}}},{"kind":"DeploymentConfig","creationTimestamp":null,"apiVersion":"v1beta1","labels":{"name":"frontend"},"desiredState":{"replicas":2,"replicaSelector":{"name":"frontend"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta1","id":"frontend","volumes":null,"containers":[{"name":"frontend","image":"mfojtik/nginx-php-app","ports":[{"hostPort":8080,"containerPort":9292}],"env":[{"name":"PGPASSWORD","value":"bQPdwNJi"},{"name":"PGUSER","value":"adminJwWP"},{"name":"PGDATABASE","value":"mydb"}]}]},"restartpolicy":{}},"labels":{"name":"frontend"}}}}]} +} diff --git a/pkg/template/types.go b/pkg/template/types.go new file mode 100644 index 000000000000..4873316ebfe4 --- /dev/null +++ b/pkg/template/types.go @@ -0,0 +1,67 @@ +package template + +import "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + +type Template struct { + api.JSONBase `json:",inline" yaml:",inline"` + BuildConfigs []BuildConfig `json:"buildConfigs" yaml:"buildConfigs"` + ImageRepositories []ImageRepository `json:"imageRepositories" yaml:"imageRepositories"` + Parameters []Parameter `json:"parameters" yaml:"parameters"` + Services []api.Service `json:"services" yaml:"services"` + DeploymentConfigs []DeploymentConfig `json:"deploymentConfigs" yaml:"deploymentConfigs"` +} + +type ImageRepositoryList struct { + api.JSONBase `json:",inline" yaml:",inline"` + Items []ImageRepository `json:"items,omitempty" yaml:"items,omitempty"` +} + +type ImageRepository struct { + api.JSONBase `json:",inline" yaml:",inline"` + Name string `json:"name" yaml:"name"` + Url string `json:"url" yaml:"url"` +} + +type ParameterList struct { + api.JSONBase `json:",inline" yaml:",inline"` + Items []Parameter `json:"items,omitempty" yaml:"items,omitempty"` +} + +type Parameter struct { + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + Type string `json:"type" yaml:"type"` + Generate string `json:"generate" yaml:"generate"` + Value string `json:"value" yaml:"value"` +} + +// FIXME: Replace this with EnvVar struct from kubernetes. +type Env []struct { + Name string `json:"name" yaml:"name"` + Value string `json:"value" yaml:"value"` +} + +type DeploymentConfigList struct { + api.JSONBase `json:",inline" yaml:",inline"` + Items []DeploymentConfig `json:"items,omitempty" yaml:"items,omitempty"` +} + +type DeploymentConfig struct { + api.JSONBase `json:",inline" yaml:",inline"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + DesiredState api.ReplicationControllerState `json:"desiredState" yaml:"desiredState"` +} + +// FIXME: Replace these with Kubernetes structs when the build will get +// intergrated. +type BuildConfigList struct { + api.JSONBase `json:",inline" yaml:",inline"` + Items []BuildConfig `json:"items,omitempty" yaml:"items,omitempty"` +} + +type BuildConfig struct { + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` + SourceUri string `json:"sourceUri" yaml:"sourceUri"` + ImageRepository string `json:"imageRepository" yaml:"imageRepository"` +}