diff --git a/docs/guides/service-discovery.md b/docs/guides/service-discovery.md index 4c91047..98c6e6b 100644 --- a/docs/guides/service-discovery.md +++ b/docs/guides/service-discovery.md @@ -36,7 +36,6 @@ For an app named `myapp` with a `convox.yml` like this: internal: true ports: - 5000 - - 5001 web: port: 3000 @@ -44,16 +43,15 @@ You would see a `convox services` output similar to this: $ convox services SERVICE DOMAIN PORTS - auth auth.convox-myapp.svc.cluster.local 5000 5001 + auth auth.convox-myapp.svc.cluster.local 5000 web web.myapp.0a1b2c3d4e5f.convox.cloud 443:3000 -The `web` Service could reach the `auth` Service using the following endpoints: +The `web` Service could reach the `auth` Service using the following URL: -* `auth.convox-myapp.svc.cluster.local:5000` -* `auth.convox-myapp.svc.cluster.local:5001` +* `http://auth.convox-myapp.svc.cluster.local:5000` -> Note that the internal `auth` Service is no longer receiving automatic HTTPS termination. If you want this -> connection to be encrypted you would need to terminate HTTPS inside your Service. +> Note that the internal port of the `auth` Service is not receiving automatic SSL termination. +> If you want this connection to be encrypted you would need to handle SSL inside the Service. DNS search suffixes are automatically configured for internal hostnames on a Rack. The following URLs would also work for contacting the `auth` Service: @@ -61,5 +59,5 @@ also work for contacting the `auth` Service: * `http://auth:5000` for Services on the same app. * `http://auth.convox-myapp:5000` for other apps on the same Rack. -> The `convox` portion of the internal hostnames in the examples above is the name of the Rack. +> The `convox` portion of these internal hostnames is the name of the Rack. > You can find the name of a Rack using `convox rack`. \ No newline at end of file diff --git a/pkg/manifest/helpers.go b/pkg/manifest/helpers.go index 7f9d25f..e3f257b 100644 --- a/pkg/manifest/helpers.go +++ b/pkg/manifest/helpers.go @@ -1,20 +1,9 @@ package manifest import ( - "math/rand" "regexp" ) -func coalesce(ss ...string) string { - for _, s := range ss { - if s != "" { - return s - } - } - - return "" -} - var regexpInterpolation = regexp.MustCompile(`\$\{([^}]*?)\}`) func interpolate(data []byte, env map[string]string) ([]byte, error) { @@ -24,13 +13,3 @@ func interpolate(data []byte, env map[string]string) ([]byte, error) { return p, nil } - -var randomAlphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") - -func randomString(size int) string { - b := make([]rune, size) - for i := range b { - b[i] = randomAlphabet[rand.Intn(len(randomAlphabet))] - } - return string(b) -} diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index 998c5a9..088a17f 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -2,7 +2,6 @@ package manifest import ( "fmt" - "io" "math/rand" "sort" "strings" @@ -63,7 +62,7 @@ func Load(data []byte, env map[string]string) (*Manifest, error) { return nil, err } - if err := m.ValidateEnv(); err != nil { + if err := m.Validate(); err != nil { return nil, err } @@ -82,6 +81,68 @@ func (m *Manifest) Agents() []string { return a } +func (m *Manifest) ApplyDefaults() error { + for i, s := range m.Services { + if s.Build.Path == "" && s.Image == "" { + m.Services[i].Build.Path = "." + } + + if m.Services[i].Build.Path != "" && s.Build.Manifest == "" { + m.Services[i].Build.Manifest = "Dockerfile" + } + + if s.Drain == 0 { + m.Services[i].Drain = 30 + } + + if s.Health.Path == "" { + m.Services[i].Health.Path = "/" + } + + if s.Health.Interval == 0 { + m.Services[i].Health.Interval = 5 + } + + if s.Health.Grace == 0 { + m.Services[i].Health.Grace = m.Services[i].Health.Interval + } + + if s.Health.Timeout == 0 { + m.Services[i].Health.Timeout = m.Services[i].Health.Interval - 1 + } + + if s.Port.Port > 0 && s.Port.Scheme == "" { + m.Services[i].Port.Scheme = "http" + } + + sp := fmt.Sprintf("services.%s.scale", s.Name) + + // if no scale attributes set + if len(m.AttributesByPrefix(sp)) == 0 { + m.Services[i].Scale.Count = ServiceScaleCount{Min: 1, Max: 1} + } + + // if no explicit count attribute set yet has multiple scale attributes other than count + if !m.AttributeExists(fmt.Sprintf("%s.count", sp)) && len(m.AttributesByPrefix(sp)) > 1 { + m.Services[i].Scale.Count = ServiceScaleCount{Min: 1, Max: 1} + } + + if m.Services[i].Scale.Cpu == 0 { + m.Services[i].Scale.Cpu = DefaultCpu + } + + if m.Services[i].Scale.Memory == 0 { + m.Services[i].Scale.Memory = DefaultMem + } + + if !m.AttributeExists(fmt.Sprintf("services.%s.sticky", s.Name)) { + m.Services[i].Sticky = true + } + } + + return nil +} + func (m *Manifest) Attributes() []string { attrs := []string{} @@ -106,7 +167,7 @@ func (m *Manifest) AttributesByPrefix(prefix string) []string { return attrs } -func (m *Manifest) AttributeSet(name string) bool { +func (m *Manifest) AttributeExists(name string) bool { return m.attributes[name] } @@ -114,20 +175,9 @@ func (m *Manifest) Env() map[string]string { return m.env } -// used only for tests -func (m *Manifest) SetAttributes(attrs []string) { - m.attributes = map[string]bool{} - - for _, a := range attrs { - m.attributes[a] = true - } -} - -// used only for tests -func (m *Manifest) SetEnv(env map[string]string) { - m.env = env -} - +// CombineEnv calculates the final environment of each service +// and filters m.env to the union of all service env vars +// defined in the manifest func (m *Manifest) CombineEnv() error { for i, s := range m.Services { me := make([]string, len(m.Environment)) @@ -135,6 +185,25 @@ func (m *Manifest) CombineEnv() error { m.Services[i].Environment = append(me, s.Environment...) } + keys := map[string]bool{} + + for _, s := range m.Services { + env, err := m.ServiceEnvironment(s.Name) + if err != nil { + return err + } + + for k := range env { + keys[k] = true + } + } + + for k := range m.env { + if !keys[k] { + delete(m.env, k) + } + } + return nil } @@ -195,95 +264,16 @@ func (m *Manifest) ServiceEnvironment(service string) (map[string]string, error) return env, nil } -// ValidateEnv returns an error if required env vars for a service are not available -// It also filters m.env to the union of all service env vars defined in the manifest -func (m *Manifest) ValidateEnv() error { - keys := map[string]bool{} +// used only for tests +func (m *Manifest) SetAttributes(attrs []string) { + m.attributes = map[string]bool{} - for _, s := range m.Services { - env, err := m.ServiceEnvironment(s.Name) - if err != nil { - return err - } - - for k := range env { - keys[k] = true - } - } - - for k := range m.env { - if !keys[k] { - delete(m.env, k) - } - } - - return nil -} - -func (m *Manifest) ApplyDefaults() error { - for i, s := range m.Services { - if s.Build.Path == "" && s.Image == "" { - m.Services[i].Build.Path = "." - } - - if m.Services[i].Build.Path != "" && s.Build.Manifest == "" { - m.Services[i].Build.Manifest = "Dockerfile" - } - - if s.Drain == 0 { - m.Services[i].Drain = 30 - } - - if s.Health.Path == "" { - m.Services[i].Health.Path = "/" - } - - if s.Health.Interval == 0 { - m.Services[i].Health.Interval = 5 - } - - if s.Health.Grace == 0 { - m.Services[i].Health.Grace = m.Services[i].Health.Interval - } - - if s.Health.Timeout == 0 { - m.Services[i].Health.Timeout = m.Services[i].Health.Interval - 1 - } - - if s.Port.Port > 0 && s.Port.Scheme == "" { - m.Services[i].Port.Scheme = "http" - } - - sp := fmt.Sprintf("services.%s.scale", s.Name) - - // if no scale attributes set - if len(m.AttributesByPrefix(sp)) == 0 { - m.Services[i].Scale.Count = ServiceScaleCount{Min: 1, Max: 1} - } - - // if no explicit count attribute set yet has multiple scale attributes other than count - if !m.AttributeSet(fmt.Sprintf("%s.count", sp)) && len(m.AttributesByPrefix(sp)) > 1 { - m.Services[i].Scale.Count = ServiceScaleCount{Min: 1, Max: 1} - } - - if m.Services[i].Scale.Cpu == 0 { - m.Services[i].Scale.Cpu = DefaultCpu - } - - if m.Services[i].Scale.Memory == 0 { - m.Services[i].Scale.Memory = DefaultMem - } - - if !m.AttributeSet(fmt.Sprintf("services.%s.sticky", s.Name)) { - m.Services[i].Sticky = true - } - } - - return nil -} - -func message(w io.Writer, format string, args ...interface{}) { - if w != nil { - w.Write([]byte(fmt.Sprintf(format, args...) + "\n")) + for _, a := range attrs { + m.attributes[a] = true } } + +// used only for tests +func (m *Manifest) SetEnv(env map[string]string) { + m.env = env +} diff --git a/pkg/manifest/manifest_test.go b/pkg/manifest/manifest_test.go index 9b3431f..7924205 100644 --- a/pkg/manifest/manifest_test.go +++ b/pkg/manifest/manifest_test.go @@ -329,7 +329,6 @@ func TestManifestLoad(t *testing.T) { // env processing that normally happens as part of load require.NoError(t, n.CombineEnv()) - require.NoError(t, n.ValidateEnv()) m, err := testdataManifest("full", env) require.NoError(t, err) @@ -388,7 +387,6 @@ func TestManifestLoadSimple(t *testing.T) { // env processing that normally happens as part of load require.NoError(t, n.CombineEnv()) - require.NoError(t, n.ValidateEnv()) m, err := testdataManifest("simple", map[string]string{"REQUIRED": "test"}) require.NoError(t, err) @@ -414,6 +412,7 @@ func TestManifestLoadInvalid(t *testing.T) { m, err = testdataManifest("invalid.2", map[string]string{}) require.NotNil(t, m) + require.NoError(t, err) require.Len(t, m.Services, 0) } diff --git a/pkg/manifest/service.go b/pkg/manifest/service.go index cc9834a..6858afe 100644 --- a/pkg/manifest/service.go +++ b/pkg/manifest/service.go @@ -20,7 +20,6 @@ type Service struct { Image string `yaml:"image,omitempty"` Init bool `yaml:"init,omitempty"` Internal bool `yaml:"internal,omitempty"` - Links []string `yaml:"links,omitempty"` Port ServicePortScheme `yaml:"port,omitempty"` Ports []ServicePortProtocol `yaml:"ports,omitempty"` Privileged bool `yaml:"privileged,omitempty"` diff --git a/pkg/manifest/validate.go b/pkg/manifest/validate.go new file mode 100644 index 0000000..01f39db --- /dev/null +++ b/pkg/manifest/validate.go @@ -0,0 +1,38 @@ +package manifest + +import ( + "fmt" + "strings" +) + +func (m *Manifest) Validate() error { + if err := m.validateEnv(); err != nil { + return err + } + + if err := m.validateResources(); err != nil { + return err + } + + return nil +} + +func (m *Manifest) validateEnv() error { + for _, s := range m.Services { + if _, err := m.ServiceEnvironment(s.Name); err != nil { + return err + } + } + + return nil +} + +func (m *Manifest) validateResources() error { + for _, r := range m.Resources { + if strings.TrimSpace(r.Type) == "" { + return fmt.Errorf("resource %q has blank type", r.Name) + } + } + + return nil +} diff --git a/pkg/manifest/yaml.go b/pkg/manifest/yaml.go index 8ae3d45..e17d301 100644 --- a/pkg/manifest/yaml.go +++ b/pkg/manifest/yaml.go @@ -447,11 +447,11 @@ func (v *ServiceScaleCount) UnmarshalYAML(unmarshal func(interface{}) error) err } case map[interface{}]interface{}: if min := t["min"]; min != nil { - switch min.(type) { + switch u := min.(type) { case int: - v.Min = min.(int) + v.Min = u case string: - mins, err := strconv.Atoi(min.(string)) + mins, err := strconv.Atoi(u) if err != nil { return err } @@ -461,11 +461,11 @@ func (v *ServiceScaleCount) UnmarshalYAML(unmarshal func(interface{}) error) err } } if max := t["max"]; max != nil { - switch max.(type) { + switch u := max.(type) { case int: - v.Max = max.(int) + v.Max = u case string: - maxs, err := strconv.Atoi(max.(string)) + maxs, err := strconv.Atoi(u) if err != nil { return err } diff --git a/provider/k8s/process.go b/provider/k8s/process.go index 12a94b0..005644d 100644 --- a/provider/k8s/process.go +++ b/provider/k8s/process.go @@ -350,27 +350,17 @@ func (p *Provider) podSpecFromService(app, service, release string) (*ac.PodSpec return nil, err } - env := map[string]string{} - senv, err := p.systemEnvironment(app, release) if err != nil { return nil, err } + env := map[string]string{} + for k, v := range senv { env[k] = v } - e := structs.Environment{} - - if err := e.Load([]byte(r.Env)); err != nil { - return nil, err - } - - for k, v := range e { - env[k] = v - } - if s, _ := m.Service(service); s != nil { if s.Command != "" { parts, err := shellquote.Split(s.Command) @@ -384,10 +374,6 @@ func (p *Provider) podSpecFromService(app, service, release string) (*ac.PodSpec env[k] = v } - for _, l := range s.Links { - env[fmt.Sprintf("%s_URL", envName(l))] = fmt.Sprintf("https://%s.%s.%s", l, app, p.Name) - } - for _, r := range s.Resources { cm, err := p.Cluster.CoreV1().ConfigMaps(p.AppNamespace(app)).Get(fmt.Sprintf("resource-%s", r), am.GetOptions{}) if err != nil { @@ -421,6 +407,16 @@ func (p *Provider) podSpecFromService(app, service, release string) (*ac.PodSpec } } + e := structs.Environment{} + + if err := e.Load([]byte(r.Env)); err != nil { + return nil, err + } + + for k, v := range e { + env[k] = v + } + for k, v := range env { c.Env = append(c.Env, ac.EnvVar{Name: k, Value: v}) } diff --git a/provider/k8s/template/app/service.yml.tmpl b/provider/k8s/template/app/service.yml.tmpl index ca16744..819e4f3 100644 --- a/provider/k8s/template/app/service.yml.tmpl +++ b/provider/k8s/template/app/service.yml.tmpl @@ -80,10 +80,6 @@ spec: valueFrom: fieldRef: fieldPath: status.hostIP - {{ range .Service.Links }} - - name: {{ envname . }}_URL - value: https://{{.}}.{{$.App.Name}}.{{$.Rack}} - {{ end }} {{ range .Service.Resources }} - name: {{ envname . }}_URL valueFrom: diff --git a/provider/k8s/template/app/timer.yml.tmpl b/provider/k8s/template/app/timer.yml.tmpl index b113339..562cc38 100644 --- a/provider/k8s/template/app/timer.yml.tmpl +++ b/provider/k8s/template/app/timer.yml.tmpl @@ -37,10 +37,6 @@ spec: - {{ safe . }} {{ end }} env: - {{ range .Service.Links }} - - name: {{ envname . }}_URL - value: https://{{.}}.{{$.App.Name}}.{{$.Rack}} - {{ end }} {{ range .Service.Resources }} - name: {{ envname . }}_URL valueFrom: