From 2d38a154dc19254a10a8753db289df568ecae57c Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Tue, 12 Aug 2014 15:34:16 +0200 Subject: [PATCH 1/5] Initial addition of project template --- pkg/project/example/project.json | 157 +++++++++++++++++++++++++++++ pkg/project/generator.go | 132 ++++++++++++++++++++++++ pkg/project/generator/from_url.go | 26 +++++ pkg/project/generator/generator.go | 45 +++++++++ pkg/project/generator/template.go | 118 ++++++++++++++++++++++ pkg/project/generator_test.go | 58 +++++++++++ pkg/project/types.go | 85 ++++++++++++++++ 7 files changed, 621 insertions(+) create mode 100644 pkg/project/example/project.json create mode 100644 pkg/project/generator.go create mode 100644 pkg/project/generator/from_url.go create mode 100644 pkg/project/generator/generator.go create mode 100644 pkg/project/generator/template.go create mode 100644 pkg/project/generator_test.go create mode 100644 pkg/project/types.go diff --git a/pkg/project/example/project.json b/pkg/project/example/project.json new file mode 100644 index 000000000000..0ba9aab0193f --- /dev/null +++ b/pkg/project/example/project.json @@ -0,0 +1,157 @@ +{ + "id": "example1", + "name": "my-awesome-php-app", + "description": "Example PHP application with PostgreSQL database", + "buildConfig": [ + { + "name": "mfojtik/nginx-php-app", + "type": "docker", + "sourceUri": "https://raw.githubusercontent.com/mfojtik/phpapp/master/Dockerfile", + "imageRepository": "int.registry.com:5000/mfojtik/phpapp" + }, + { + "name": "postgres", + "type": "docker", + "imageRepository": "registry.hub.docker.com/postgres", + "sourceUri": "https://raw.githubusercontent.com/docker-library/postgres/docker/9.2/Dockerfile" + } + ], + "imageRepository": [ + { + "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", + "generate": "[GET:http://broken.url/test]" + }, + { + "name": "SAMPLE_VAR", + "description": "Sample", + "type": "string", + "value": "foo" + } + ], + "serviceLinks": [ + { + "export": [ + { + "name": "POSTGRES_ADMIN_USERNAME", + "value": "${DB_USER}" + }, + { + "name": "POSTGRES_ADMIN_PASSWORD", + "value": "${DB_PASSWORD}" + }, + { + "name": "POSTGRES_DATABASE_NAME", + "value": "${DB_NAME}" + } + ], + "from": "database", + "to": "frontend" + } + ], + "services": [ + { + "name": "database", + "description": "Standalone PostgreSQL 9.2 database service", + "labels": { + "name": "database-service" + }, + "deploymentConfig": { + "deployment": { + "podTemplate": { + "containers": [ + { + "name": "postgresql-1", + "image": { + "name": "postgres", + "tag": "9.2" + }, + "env": [ + { + "name": "POSTGRES_ADMIN_USERNAME", + "value": "${DB_USER}" + }, + { + "name": "POSTGRES_ADMIN_PASSWORD", + "value": "${DB_PASSWORD}" + }, + { + "name": "POSTGRES_DATABASE_NAME", + "value": "${DB_NAME}" + }, + { + "name": "FOO", + "value": "${BAR}" + } + ], + "ports": [ + { + "containerPort": 5432, + "hostPort": 5432 + } + ] + } + ] + } + } + } + }, + { + "name": "frontend", + "description": "Sample PHP 5.2 application served by NGINX", + "labels": { + "name": "frontend-service" + }, + "deploymentConfig": { + "deployment": { + "podTemplate": { + "containers": [ + { + "name": "nginx-php-app", + "hooks": { + "prestart": { + "cmd": "import_database.sh" + }, + "url": "git://github.com/user/myapp-hooks.git" + }, + "image": { + "name": "mfojtik/nginx-php-app", + "tag": "latest" + }, + "ports": [ + { + "containerPort": 8080, + "hostPort": 8080 + } + ] + } + ] + } + } + } + } + ] +} diff --git a/pkg/project/generator.go b/pkg/project/generator.go new file mode 100644 index 000000000000..c2141c8175b6 --- /dev/null +++ b/pkg/project/generator.go @@ -0,0 +1,132 @@ +package project + +import ( + "fmt" + "math/rand" + "regexp" + "strings" + "time" + + "github.com/openshift/origin/pkg/project/generator" +) + +const valueExp = `(\$\{([a-zA-Z0-9\_]+)\})` + +type ParamHash map[string]Parameter + +// Generate the value for the Parameter if the default Value is not set and the +// Generator field is specified. Otherwise, just return the default Value +// +func (p *Parameter) GenerateValue() error { + if p.Value != "" || p.Generate == "" { + return nil + } + + generatedValue, err := generator.Template(p.Generate) + if err != nil { + return err + } + p.Value = generatedValue + + return nil +} + +// The string representation of PValue +// +func (s PValue) String() string { + return string(s) +} + +// Replace references to parameters in PValue with their values. +// The format is specified in the `valueExp` constant ${PARAM_NAME}. +// +// If the referenced parameter is not defined, then the substitution is ignored. +// +func (s *PValue) Substitute(params ParamHash) { + newValue := *s + + templExp, _ := regexp.Compile(valueExp) + for _, match := range templExp.FindAllStringSubmatch(string(newValue), -1) { + // If the Parameter is not defined, then leave the value as it is + if params[match[2]].Value == "" { + continue + } + newValue = PValue(strings.Replace(string(newValue), match[1], params[match[2]].Value, 1)) + } + + *s = newValue +} + +// Generate Value field for defined Parameters. +// If the Parameter define Generate, then the Value is generated based +// on that template. The template is a pseudo-regexp formatted string. +// +// Example: +// +// s := generate.Template("[a-zA-Z0-9]{4}") +// // s: "Ga0b" +// +// s := generate.Template("[GET:http://example.com/new]") +// // s: +// +// +func (p *Project) ProcessParameters() { + // Initialize random seed + rand.Seed(time.Now().UnixNano()) + + for i, _ := range p.Parameters { + if err := p.Parameters[i].GenerateValue(); err != nil { + fmt.Printf("ERROR: Unable to process parameter %s: %v\n", p.Parameters[i].Name, err) + p.Parameters[i].Value = p.Parameters[i].Generate + } + } +} + +// A shorthand method to get list of *all* container defined in the Project +// template +// +func (p *Project) Containers() []*Container { + var result []*Container + for sid, _ := range p.Services { + for _, c := range p.Services[sid].DeploymentConfig.Deployment.PodTemplate.Containers { + result = append(result, &c) + } + } + return result +} + +// Convert Parameter slice to more effective data structure +// +func (p *Project) ParameterHash() ParamHash { + paramHash := make(ParamHash) + for _, p := range p.Parameters { + paramHash[p.Name] = p + } + return paramHash +} + +// Process all Env variables in the Project template and replace parameters +// referenced in their values with the Parameter values. +// +// The replacement is done in Containers and ServiceLinks. +// +func (p *Project) SubstituteEnvValues() { + + params := p.ParameterHash() + + for _, container := range p.Containers() { + (*container).Env.Process(params) + } + + for s, _ := range p.ServiceLinks { + p.ServiceLinks[s].Export.Process(params) + } +} + +// Substitute referenced parameters in Env values with parameter values. +// +func (e *Env) Process(params ParamHash) { + for i, _ := range *e { + (*e)[i].Value.Substitute(params) + } +} diff --git a/pkg/project/generator/from_url.go b/pkg/project/generator/from_url.go new file mode 100644 index 000000000000..d7ee68c1f185 --- /dev/null +++ b/pkg/project/generator/from_url.go @@ -0,0 +1,26 @@ +package generator + +import ( + "io/ioutil" + "net/http" + "strings" +) + +func httpGet(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + return string(body), err +} + +func replaceUrlWithData(s *string, expresion string) error { + result, err := httpGet(expresion[5 : len(expresion)-1]) + if err != nil { + return err + } + *s = strings.Replace(*s, expresion, strings.TrimSpace(result), 1) + return nil +} diff --git a/pkg/project/generator/generator.go b/pkg/project/generator/generator.go new file mode 100644 index 000000000000..193fbf82984d --- /dev/null +++ b/pkg/project/generator/generator.go @@ -0,0 +1,45 @@ +package generator + +import ( + "fmt" + "strings" +) + +func Template(s string) (string, error) { + switch s { + case "password": + return SimplePassword(8) + case "uuid": + return RandomUUID(), nil + default: + return FromTemplate(s) + } +} + +func StrongPassword(length int) (string, error) { + return FromTemplate(fmt.Sprintf("[\\w]{%d}", length)) +} + +func SimplePassword(length int) (string, error) { + return FromTemplate(fmt.Sprintf("[\\a]{%d}", length)) +} + +func RandomUUID() string { + uuid, _ := FromTemplate("[\\a]{8}-[\\a]{4}-4[\\a]{3}-8[\\a]{3}-[\\a]{12}") + return strings.ToLower(uuid) +} + +func RandomCleanUUID() string { + uuid := RandomUUID() + return strings.Replace(uuid, "-", "", -1) +} + +func RandomNumericUUID() string { + uuid, _ := FromTemplate("[\\d]{8}-[\\d]{4}-4[\\d]{3}-8[\\d]{3}-[\\d]{12}") + return uuid +} + +func RandomCleanNumbericUUID() string { + uuid := RandomNumericUUID() + return strings.Replace(uuid, "-", "", -1) +} diff --git a/pkg/project/generator/template.go b/pkg/project/generator/template.go new file mode 100644 index 000000000000..4dcfacf2396f --- /dev/null +++ b/pkg/project/generator/template.go @@ -0,0 +1,118 @@ +package generator + +import ( + "fmt" + "math/rand" + "regexp" + "strconv" + "strings" +) + +const ( + Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + Numerals = "0123456789" + Ascii = Alphabet + Numerals + "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:`" +) + +type GeneratorExprRanges [][]byte + +func seedAndReturnRandom(n int) int { + return rand.Intn(n) +} + +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, expresion string, ranges [][]byte, length int) 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 expresion: %s", expresion) + } + result := make([]byte, length, length) + for i := 0; i <= length-1; i++ { + result[i] = alphabet[seedAndReturnRandom(len(alphabet))] + } + *s = strings.Replace(*s, expresion, string(result), 1) + return nil +} + +func findExpresionPos(s string) GeneratorExprRanges { + rangeExp, _ := regexp.Compile(`([\\]?[a-zA-Z0-9]\-?[a-zA-Z0-9]?)`) + matches := rangeExp.FindAllStringIndex(s, -1) + result := make(GeneratorExprRanges, len(matches), len(matches)) + for i, r := range matches { + result[i] = []byte{s[r[0]], s[r[1]-1]} + } + return result +} + +func rangesAndLength(s string) (string, int, error) { + l := strings.LastIndex(s, "{") + // If the length ({}) is not specified in expresion, + // then assume the length is 1 character + // + if l > 0 { + expr := s[0:strings.LastIndex(s, "{")] + length, err := parseLength(s) + return expr, length, err + } else { + return s, 1, nil + } +} + +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 FromTemplate(template string) (string, error) { + result := template + generatorsExp, _ := regexp.Compile(`\[([a-zA-Z0-9\-\\]+)\](\{([0-9]+)\})`) + remoteExp, _ := regexp.Compile(`\[GET\:(http(s)?:\/\/(.+))\]`) + genMatches := generatorsExp.FindAllStringIndex(template, -1) + remMatches := remoteExp.FindAllStringIndex(template, -1) + // Parse [a-z]{} types + for _, r := range genMatches { + ranges, length, err := rangesAndLength(template[r[0]:r[1]]) + if err != nil { + return "", err + } + positions := findExpresionPos(ranges) + if err := replaceWithGenerated(&result, template[r[0]:r[1]], positions, length); err != nil { + return "", err + } + } + // Parse [GET:] type + for _, r := range remMatches { + if err := replaceUrlWithData(&result, template[r[0]:r[1]]); err != nil { + return "", err + } + } + return result, nil +} diff --git a/pkg/project/generator_test.go b/pkg/project/generator_test.go new file mode 100644 index 000000000000..589a54a219ad --- /dev/null +++ b/pkg/project/generator_test.go @@ -0,0 +1,58 @@ +package project + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + "testing" +) + +const projectExampleJSON = "./example/project.json" + +var projectTempl Project + +func TestTemplateUnmarshal(t *testing.T) { + jsonFile, _ := ioutil.ReadFile(projectExampleJSON) + err := json.Unmarshal(jsonFile, &projectTempl) + if err != nil { + t.Errorf("Unable to parse the sample project.json: %v", err) + } +} + +func TestProcessParameters(t *testing.T) { + projectTempl.ProcessParameters() + + for _, p := range projectTempl.Parameters { + if p.Value == "" { + t.Errorf("Failed to process '%s' parameter", p.Name) + } + fmt.Printf("%s -> %s = %s\n", p.Name, p.Generate, p.Value) + } +} + +func TestSubstituteEnvValues(t *testing.T) { + projectTempl.SubstituteEnvValues() + + for _, c := range projectTempl.Containers() { + for _, e := range c.Env { + if strings.Contains(string(e.Value), "${") { + if e.Name != "FOO" { + t.Errorf("Failed to substitute %s environment variable: %s", e.Name, e.Value) + } + } + fmt.Printf("%s=%s\n", e.Name, e.Value) + } + } + + for _, s := range projectTempl.ServiceLinks { + for _, e := range s.Export { + if strings.Contains(string(e.Value), "${") { + if e.Name != "FOO" { + t.Errorf("Failed to substitute %s environment variable: %s", e.Name, e.Value) + } + } + fmt.Printf("%s=%s\n", e.Name, e.Value) + } + } +} diff --git a/pkg/project/types.go b/pkg/project/types.go new file mode 100644 index 000000000000..080840eb5d52 --- /dev/null +++ b/pkg/project/types.go @@ -0,0 +1,85 @@ +package project + +import "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + +type ( + Uri string + PValue string +) + +type Project struct { + api.JSONBase `json:",inline" yaml:",inline"` + BuildConfig []BuildConfig `json:"buildConfig" yaml:"buildConfig"` + ImageRepository []ImageRepository `json:"imageRepository" yaml:"imageRepository"` + Parameters []Parameter `json:"parameters" yaml:"parameters"` + ServiceLinks []ServiceLink `json:"serviceLinks" yaml:"serviceLinks"` + Services []Service `json:"services" yaml:"services"` +} + +type ImageRepository struct { + Name string `json:"name" yaml:"name"` + Url Uri `json:"url" yaml:"url"` +} + +type BuildConfig struct { + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` + SourceUri Uri `json:"sourceUri" yaml:"sourceUri"` + ImageRepository string `json:"imageRepository" yaml:"imageRepository"` +} + +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"` +} + +type Env []struct { + Name string `json:"name" yaml:"name"` + Value PValue `json:"value" yaml:"value"` +} + +type ServiceLink struct { + From string `json:"from" yaml:"from"` + To string `json:"from" yaml:"from"` + Export Env `json:"export" yaml:"export"` +} + +type DeploymentConfig struct { + Deployment Deployment `json:"deployment" yaml:"deployment"` +} + +type Deployment struct { + PodTemplate PodTemplate `json:"podTemplate" yaml:"podTemplate"` +} + +type PodTemplate struct { + Containers []Container `json:"containers" yaml:"containers"` + Replicas int `json:"replicas" yaml:"replicas"` +} + +type Image struct { + Name string `json:"name" yaml:"name"` + Tag string `json:"tag" yaml:"tag"` +} + +type ContainerPort struct { + ContainerPort int `json:"containerPort" yaml:"containerPort"` + HostPort int `json:"hostPort" yaml:"hostPort"` +} + +type Container struct { + Name string `json:"name" yaml:"name"` + Image Image `json:"image" yaml:"image"` + Env Env `json:"env" yaml:"env"` + Ports []ContainerPort `json:"ports" yaml:"ports"` +} + +type Service struct { + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + Labels map[string]PValue `json:"labels" yaml:"labels"` + DeploymentConfig DeploymentConfig `json:"deploymentConfig" yaml:"deploymentConfig"` +} From dd3f921da2871a1da4cd3e7d74ff3c9f5d35a81d Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Wed, 13 Aug 2014 15:00:56 +0200 Subject: [PATCH 2/5] Added serviceLink processing for Services --- pkg/project/example/project.json | 2 +- pkg/project/generator.go | 6 +-- pkg/project/service_links.go | 72 +++++++++++++++++++++++++++++++ pkg/project/service_links_test.go | 17 ++++++++ pkg/project/types.go | 2 +- 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 pkg/project/service_links.go create mode 100644 pkg/project/service_links_test.go diff --git a/pkg/project/example/project.json b/pkg/project/example/project.json index 0ba9aab0193f..105c44b4b1cf 100644 --- a/pkg/project/example/project.json +++ b/pkg/project/example/project.json @@ -54,6 +54,7 @@ ], "serviceLinks": [ { + "from": "database", "export": [ { "name": "POSTGRES_ADMIN_USERNAME", @@ -68,7 +69,6 @@ "value": "${DB_NAME}" } ], - "from": "database", "to": "frontend" } ], diff --git a/pkg/project/generator.go b/pkg/project/generator.go index c2141c8175b6..01bfd2fa400f 100644 --- a/pkg/project/generator.go +++ b/pkg/project/generator.go @@ -87,10 +87,8 @@ func (p *Project) ProcessParameters() { // func (p *Project) Containers() []*Container { var result []*Container - for sid, _ := range p.Services { - for _, c := range p.Services[sid].DeploymentConfig.Deployment.PodTemplate.Containers { - result = append(result, &c) - } + for _, s := range p.Services { + result = append(result, s.Containers()...) } return result } diff --git a/pkg/project/service_links.go b/pkg/project/service_links.go new file mode 100644 index 000000000000..81ea9f345c7e --- /dev/null +++ b/pkg/project/service_links.go @@ -0,0 +1,72 @@ +package project + +import "fmt" + +func (e *Env) Append(env *Env) { + *e = append(*e, *env...) +} + +func (e *Env) Exists(name string) bool { + for _, env := range *e { + if env.Name == name { + return true + } + } + return false +} + +func (s *Service) Containers() []*Container { + result := make([]*Container, len((*s).DeploymentConfig.Deployment.PodTemplate.Containers)) + + for i, _ := range s.DeploymentConfig.Deployment.PodTemplate.Containers { + result[i] = &s.DeploymentConfig.Deployment.PodTemplate.Containers[i] + } + + return result +} + +func (s *Service) ContainersEnv() []*Env { + var result []*Env + for _, c := range s.Containers() { + result = append(result, &c.Env) + } + return result +} + +func (s *Service) AddEnv(env Env) { + fmt.Printf("s.Containers() %+v\n", s.Containers()) + for _, c := range s.Containers() { + (*c).Env = append(c.Env, env...) + } +} + +func (p *Project) ServiceByName(name string) *Service { + for i, _ := range p.Services { + if p.Services[i].Name == name { + return &p.Services[i] + } + } + return nil +} + +func (p *Project) ProcessServiceLinks() { + var ( + fromService, toService *Service + ) + + for i, _ := range p.ServiceLinks { + fromService = p.ServiceByName(p.ServiceLinks[i].From) + if fromService == nil { + fmt.Printf("ERROR: Invalid FROM service in links: %+v\n", p.ServiceLinks[i].From) + continue + } + + toService = p.ServiceByName(p.ServiceLinks[i].To) + if toService == nil { + fmt.Printf("ERROR: Invalid TO service in links: %+v\n", p.ServiceLinks[i].To) + continue + } + + toService.AddEnv(p.ServiceLinks[i].Export) + } +} diff --git a/pkg/project/service_links_test.go b/pkg/project/service_links_test.go new file mode 100644 index 000000000000..db41597430fa --- /dev/null +++ b/pkg/project/service_links_test.go @@ -0,0 +1,17 @@ +package project + +import "testing" + +func TestServiceLinks(t *testing.T) { + projectTempl.ProcessParameters() + projectTempl.ProcessServiceLinks() + + s := projectTempl.ServiceByName("frontend") + for _, env := range s.ContainersEnv() { + for _, export := range projectTempl.ServiceLinks[0].Export { + if env.Exists(export.Name) == false { + t.Errorf("Failed to export %s variable via serviceLinks to %s", export.Name, s.Name) + } + } + } +} diff --git a/pkg/project/types.go b/pkg/project/types.go index 080840eb5d52..56e0e437dd97 100644 --- a/pkg/project/types.go +++ b/pkg/project/types.go @@ -43,7 +43,7 @@ type Env []struct { type ServiceLink struct { From string `json:"from" yaml:"from"` - To string `json:"from" yaml:"from"` + To string `json:"to" yaml:"to"` Export Env `json:"export" yaml:"export"` } From 0e96a759c0ced6c6c6ef79859733f890d7ebac8f Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Thu, 14 Aug 2014 13:03:51 +0200 Subject: [PATCH 3/5] Renamed 'project' package to 'template' --- pkg/{project => template}/example/project.json | 0 pkg/{project => template}/generator.go | 14 +++++++------- pkg/{project => template}/generator/from_url.go | 0 pkg/{project => template}/generator/generator.go | 0 pkg/{project => template}/generator/template.go | 0 pkg/{project => template}/generator_test.go | 4 ++-- pkg/{project => template}/service_links.go | 6 +++--- pkg/{project => template}/service_links_test.go | 2 +- pkg/{project => template}/types.go | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) rename pkg/{project => template}/example/project.json (100%) rename pkg/{project => template}/generator.go (91%) rename pkg/{project => template}/generator/from_url.go (100%) rename pkg/{project => template}/generator/generator.go (100%) rename pkg/{project => template}/generator/template.go (100%) rename pkg/{project => template}/generator_test.go (96%) rename pkg/{project => template}/service_links.go (92%) rename pkg/{project => template}/service_links_test.go (96%) rename pkg/{project => template}/types.go (98%) diff --git a/pkg/project/example/project.json b/pkg/template/example/project.json similarity index 100% rename from pkg/project/example/project.json rename to pkg/template/example/project.json diff --git a/pkg/project/generator.go b/pkg/template/generator.go similarity index 91% rename from pkg/project/generator.go rename to pkg/template/generator.go index 01bfd2fa400f..23a446a58386 100644 --- a/pkg/project/generator.go +++ b/pkg/template/generator.go @@ -1,4 +1,4 @@ -package project +package template import ( "fmt" @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/openshift/origin/pkg/project/generator" + "github.com/openshift/origin/pkg/template/generator" ) const valueExp = `(\$\{([a-zA-Z0-9\_]+)\})` @@ -70,7 +70,7 @@ func (s *PValue) Substitute(params ParamHash) { // // s: // // -func (p *Project) ProcessParameters() { +func (p *Template) ProcessParameters() { // Initialize random seed rand.Seed(time.Now().UnixNano()) @@ -82,10 +82,10 @@ func (p *Project) ProcessParameters() { } } -// A shorthand method to get list of *all* container defined in the Project +// A shorthand method to get list of *all* container defined in the Template // template // -func (p *Project) Containers() []*Container { +func (p *Template) Containers() []*Container { var result []*Container for _, s := range p.Services { result = append(result, s.Containers()...) @@ -95,7 +95,7 @@ func (p *Project) Containers() []*Container { // Convert Parameter slice to more effective data structure // -func (p *Project) ParameterHash() ParamHash { +func (p *Template) ParameterHash() ParamHash { paramHash := make(ParamHash) for _, p := range p.Parameters { paramHash[p.Name] = p @@ -108,7 +108,7 @@ func (p *Project) ParameterHash() ParamHash { // // The replacement is done in Containers and ServiceLinks. // -func (p *Project) SubstituteEnvValues() { +func (p *Template) SubstituteEnvValues() { params := p.ParameterHash() diff --git a/pkg/project/generator/from_url.go b/pkg/template/generator/from_url.go similarity index 100% rename from pkg/project/generator/from_url.go rename to pkg/template/generator/from_url.go diff --git a/pkg/project/generator/generator.go b/pkg/template/generator/generator.go similarity index 100% rename from pkg/project/generator/generator.go rename to pkg/template/generator/generator.go diff --git a/pkg/project/generator/template.go b/pkg/template/generator/template.go similarity index 100% rename from pkg/project/generator/template.go rename to pkg/template/generator/template.go diff --git a/pkg/project/generator_test.go b/pkg/template/generator_test.go similarity index 96% rename from pkg/project/generator_test.go rename to pkg/template/generator_test.go index 589a54a219ad..587f947fa508 100644 --- a/pkg/project/generator_test.go +++ b/pkg/template/generator_test.go @@ -1,4 +1,4 @@ -package project +package template import ( "encoding/json" @@ -10,7 +10,7 @@ import ( const projectExampleJSON = "./example/project.json" -var projectTempl Project +var projectTempl Template func TestTemplateUnmarshal(t *testing.T) { jsonFile, _ := ioutil.ReadFile(projectExampleJSON) diff --git a/pkg/project/service_links.go b/pkg/template/service_links.go similarity index 92% rename from pkg/project/service_links.go rename to pkg/template/service_links.go index 81ea9f345c7e..ea5a6f6f7f13 100644 --- a/pkg/project/service_links.go +++ b/pkg/template/service_links.go @@ -1,4 +1,4 @@ -package project +package template import "fmt" @@ -40,7 +40,7 @@ func (s *Service) AddEnv(env Env) { } } -func (p *Project) ServiceByName(name string) *Service { +func (p *Template) ServiceByName(name string) *Service { for i, _ := range p.Services { if p.Services[i].Name == name { return &p.Services[i] @@ -49,7 +49,7 @@ func (p *Project) ServiceByName(name string) *Service { return nil } -func (p *Project) ProcessServiceLinks() { +func (p *Template) ProcessServiceLinks() { var ( fromService, toService *Service ) diff --git a/pkg/project/service_links_test.go b/pkg/template/service_links_test.go similarity index 96% rename from pkg/project/service_links_test.go rename to pkg/template/service_links_test.go index db41597430fa..679ac9ee4292 100644 --- a/pkg/project/service_links_test.go +++ b/pkg/template/service_links_test.go @@ -1,4 +1,4 @@ -package project +package template import "testing" diff --git a/pkg/project/types.go b/pkg/template/types.go similarity index 98% rename from pkg/project/types.go rename to pkg/template/types.go index 56e0e437dd97..7d26125befdd 100644 --- a/pkg/project/types.go +++ b/pkg/template/types.go @@ -1,4 +1,4 @@ -package project +package template import "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -7,7 +7,7 @@ type ( PValue string ) -type Project struct { +type Template struct { api.JSONBase `json:",inline" yaml:",inline"` BuildConfig []BuildConfig `json:"buildConfig" yaml:"buildConfig"` ImageRepository []ImageRepository `json:"imageRepository" yaml:"imageRepository"` From 5865bf43e3f97fc5da7f0510c441fd45c26ea7a3 Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Thu, 14 Aug 2014 13:15:17 +0200 Subject: [PATCH 4/5] Added 'RandomSeed' into Template --- pkg/template/generator.go | 12 +----------- pkg/template/generator_test.go | 2 ++ pkg/template/types.go | 2 ++ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/pkg/template/generator.go b/pkg/template/generator.go index 23a446a58386..9fa8cbd34578 100644 --- a/pkg/template/generator.go +++ b/pkg/template/generator.go @@ -5,7 +5,6 @@ import ( "math/rand" "regexp" "strings" - "time" "github.com/openshift/origin/pkg/template/generator" ) @@ -16,7 +15,6 @@ type ParamHash map[string]Parameter // Generate the value for the Parameter if the default Value is not set and the // Generator field is specified. Otherwise, just return the default Value -// func (p *Parameter) GenerateValue() error { if p.Value != "" || p.Generate == "" { return nil @@ -41,7 +39,6 @@ func (s PValue) String() string { // The format is specified in the `valueExp` constant ${PARAM_NAME}. // // If the referenced parameter is not defined, then the substitution is ignored. -// func (s *PValue) Substitute(params ParamHash) { newValue := *s @@ -68,11 +65,8 @@ func (s *PValue) Substitute(params ParamHash) { // // s := generate.Template("[GET:http://example.com/new]") // // s: -// -// func (p *Template) ProcessParameters() { - // Initialize random seed - rand.Seed(time.Now().UnixNano()) + rand.Seed(p.RandomSeed) for i, _ := range p.Parameters { if err := p.Parameters[i].GenerateValue(); err != nil { @@ -84,7 +78,6 @@ func (p *Template) ProcessParameters() { // A shorthand method to get list of *all* container defined in the Template // template -// func (p *Template) Containers() []*Container { var result []*Container for _, s := range p.Services { @@ -94,7 +87,6 @@ func (p *Template) Containers() []*Container { } // Convert Parameter slice to more effective data structure -// func (p *Template) ParameterHash() ParamHash { paramHash := make(ParamHash) for _, p := range p.Parameters { @@ -107,7 +99,6 @@ func (p *Template) ParameterHash() ParamHash { // referenced in their values with the Parameter values. // // The replacement is done in Containers and ServiceLinks. -// func (p *Template) SubstituteEnvValues() { params := p.ParameterHash() @@ -122,7 +113,6 @@ func (p *Template) SubstituteEnvValues() { } // Substitute referenced parameters in Env values with parameter values. -// func (e *Env) Process(params ParamHash) { for i, _ := range *e { (*e)[i].Value.Substitute(params) diff --git a/pkg/template/generator_test.go b/pkg/template/generator_test.go index 587f947fa508..342f41158e2f 100644 --- a/pkg/template/generator_test.go +++ b/pkg/template/generator_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "strings" "testing" + "time" ) const projectExampleJSON = "./example/project.json" @@ -18,6 +19,7 @@ func TestTemplateUnmarshal(t *testing.T) { if err != nil { t.Errorf("Unable to parse the sample project.json: %v", err) } + projectTempl.RandomSeed = time.Now().UnixNano() } func TestProcessParameters(t *testing.T) { diff --git a/pkg/template/types.go b/pkg/template/types.go index 7d26125befdd..3b38dac525ee 100644 --- a/pkg/template/types.go +++ b/pkg/template/types.go @@ -14,6 +14,8 @@ type Template struct { Parameters []Parameter `json:"parameters" yaml:"parameters"` ServiceLinks []ServiceLink `json:"serviceLinks" yaml:"serviceLinks"` Services []Service `json:"services" yaml:"services"` + + RandomSeed int64 } type ImageRepository struct { From 544ed4782d8c67acc99e6e09300c92c9b020164b Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Thu, 14 Aug 2014 15:09:01 +0200 Subject: [PATCH 5/5] Use MustCompile for regexp --- pkg/template/generator.go | 9 +++--- pkg/template/generator/generator.go | 49 +++++++++++------------------ pkg/template/generator/template.go | 14 ++++++--- 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/pkg/template/generator.go b/pkg/template/generator.go index 9fa8cbd34578..aaa558afa435 100644 --- a/pkg/template/generator.go +++ b/pkg/template/generator.go @@ -9,7 +9,7 @@ import ( "github.com/openshift/origin/pkg/template/generator" ) -const valueExp = `(\$\{([a-zA-Z0-9\_]+)\})` +var valueExp = regexp.MustCompile(`(\$\{([a-zA-Z0-9\_]+)\})`) type ParamHash map[string]Parameter @@ -20,7 +20,9 @@ func (p *Parameter) GenerateValue() error { return nil } - generatedValue, err := generator.Template(p.Generate) + g := generator.Generator{} + generatedValue, err := g.Generate(p.Generate).Value() + if err != nil { return err } @@ -42,8 +44,7 @@ func (s PValue) String() string { func (s *PValue) Substitute(params ParamHash) { newValue := *s - templExp, _ := regexp.Compile(valueExp) - for _, match := range templExp.FindAllStringSubmatch(string(newValue), -1) { + for _, match := range valueExp.FindAllStringSubmatch(string(newValue), -1) { // If the Parameter is not defined, then leave the value as it is if params[match[2]].Value == "" { continue diff --git a/pkg/template/generator/generator.go b/pkg/template/generator/generator.go index 193fbf82984d..9b259e331ae6 100644 --- a/pkg/template/generator/generator.go +++ b/pkg/template/generator/generator.go @@ -1,45 +1,34 @@ package generator -import ( - "fmt" - "strings" -) +import "fmt" -func Template(s string) (string, error) { - switch s { - case "password": - return SimplePassword(8) - case "uuid": - return RandomUUID(), nil - default: - return FromTemplate(s) - } +type GeneratorType interface { + Value() (string, error) } -func StrongPassword(length int) (string, error) { - return FromTemplate(fmt.Sprintf("[\\w]{%d}", length)) +type ExpresionGenerator struct { + expr string } -func SimplePassword(length int) (string, error) { - return FromTemplate(fmt.Sprintf("[\\a]{%d}", length)) +func (g ExpresionGenerator) Value() (string, error) { + return FromTemplate(g.expr) } -func RandomUUID() string { - uuid, _ := FromTemplate("[\\a]{8}-[\\a]{4}-4[\\a]{3}-8[\\a]{3}-[\\a]{12}") - return strings.ToLower(uuid) +type PasswordGenerator struct { + length int } -func RandomCleanUUID() string { - uuid := RandomUUID() - return strings.Replace(uuid, "-", "", -1) +func (g PasswordGenerator) Value() (string, error) { + return FromTemplate(fmt.Sprintf("[\\a]{%d}", g.length)) } -func RandomNumericUUID() string { - uuid, _ := FromTemplate("[\\d]{8}-[\\d]{4}-4[\\d]{3}-8[\\d]{3}-[\\d]{12}") - return uuid -} +type Generator struct{} -func RandomCleanNumbericUUID() string { - uuid := RandomNumericUUID() - return strings.Replace(uuid, "-", "", -1) +func (g Generator) Generate(t string) GeneratorType { + switch t { + case "password": + return PasswordGenerator{length: 8} + default: + return ExpresionGenerator{expr: t} + } } diff --git a/pkg/template/generator/template.go b/pkg/template/generator/template.go index 4dcfacf2396f..64776002ea9c 100644 --- a/pkg/template/generator/template.go +++ b/pkg/template/generator/template.go @@ -14,9 +14,15 @@ const ( Ascii = Alphabet + Numerals + "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:`" ) +var ( + rangeExp = regexp.MustCompile(`([\\]?[a-zA-Z0-9]\-?[a-zA-Z0-9]?)`) + generatorsExp = regexp.MustCompile(`\[([a-zA-Z0-9\-\\]+)\](\{([0-9]+)\})`) + remoteExp = regexp.MustCompile(`\[GET\:(http(s)?:\/\/(.+))\]`) +) + type GeneratorExprRanges [][]byte -func seedAndReturnRandom(n int) int { +func randomInt(n int) int { return rand.Intn(n) } @@ -52,14 +58,13 @@ func replaceWithGenerated(s *string, expresion string, ranges [][]byte, length i } result := make([]byte, length, length) for i := 0; i <= length-1; i++ { - result[i] = alphabet[seedAndReturnRandom(len(alphabet))] + result[i] = alphabet[randomInt(len(alphabet))] } *s = strings.Replace(*s, expresion, string(result), 1) return nil } func findExpresionPos(s string) GeneratorExprRanges { - rangeExp, _ := regexp.Compile(`([\\]?[a-zA-Z0-9]\-?[a-zA-Z0-9]?)`) matches := rangeExp.FindAllStringIndex(s, -1) result := make(GeneratorExprRanges, len(matches), len(matches)) for i, r := range matches { @@ -93,8 +98,6 @@ func parseLength(s string) (int, error) { func FromTemplate(template string) (string, error) { result := template - generatorsExp, _ := regexp.Compile(`\[([a-zA-Z0-9\-\\]+)\](\{([0-9]+)\})`) - remoteExp, _ := regexp.Compile(`\[GET\:(http(s)?:\/\/(.+))\]`) genMatches := generatorsExp.FindAllStringIndex(template, -1) remMatches := remoteExp.FindAllStringIndex(template, -1) // Parse [a-z]{} types @@ -109,6 +112,7 @@ func FromTemplate(template string) (string, error) { } } // Parse [GET:] type + // for _, r := range remMatches { if err := replaceUrlWithData(&result, template[r[0]:r[1]]); err != nil { return "", err