mirror of
https://github.com/FlipsideCrypto/convox.git
synced 2026-02-06 10:56:56 +00:00
manifest: remove links from services (#41)
* remove links from services * make internal service discovery use one port for less confusion * lint and cleanup * use correct env priority on one-offs
This commit is contained in:
parent
3bd48fc505
commit
454d28fbf3
@ -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`.
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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"`
|
||||
|
||||
38
pkg/manifest/validate.go
Normal file
38
pkg/manifest/validate.go
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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})
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user