diff --git a/appsody/initializer.go b/appsody/initializer.go index 2ddeabb1..b6fb4517 100644 --- a/appsody/initializer.go +++ b/appsody/initializer.go @@ -7,6 +7,8 @@ import ( "os" "os/exec" "strings" + + "github.com/lkingland/faas/k8s" ) // NameMappings are short-name to repository full name mappings, @@ -39,11 +41,14 @@ func (n *Initializer) Initialize(name, language, path string) error { // Appsody does not support domain names as the project name // (ex: www.example.com), and has extremely strict naming requirements - // (only lower case letters, numbers and dashes). So for now replace - // any dots with dashes. - name = strings.ReplaceAll(name, ".", "-") + // (subdomains per rfc 1035). So let's just assume its name must be a valid domain, and + // encode it as a 1035 domain by doubling down on hyphens. + project, err := k8s.ToSubdomain(name) + if err != nil { + return err + } - // Dereference stack short name + // Dereference stack short name. ex. "go" -> "go-ce-functions" stackName, ok := stackShortNames[language] if !ok { languages := []string{} @@ -56,13 +61,12 @@ func (n *Initializer) Initialize(name, language, path string) error { // set up the command, specifying a sanitized project name and connecting // standard output and error. - cmd := exec.Command("appsody", "init", "boson/"+stackName, "--project-name", name) + cmd := exec.Command("appsody", "init", "boson/"+stackName, "--project-name", project) cmd.Dir = path - fmt.Println(cmd) - // If verbose logging is enabled, echo appsody's chatty stdout. if n.Verbose { + fmt.Println(cmd) cmd.Stdout = os.Stdout } diff --git a/k8s/names.go b/k8s/names.go new file mode 100644 index 00000000..3bf4cf7e --- /dev/null +++ b/k8s/names.go @@ -0,0 +1,67 @@ +package k8s + +import ( + "errors" + "strings" + + "k8s.io/apimachinery/pkg/util/validation" +) + +// ToSubdomain converts a domain to a subdomain. +// If the input is not a valid domain an error is thrown. +func ToSubdomain(in string) (string, error) { + if err := validation.IsFullyQualifiedDomainName(nil, in); err != nil { + return "", err.ToAggregate() + } + + out := []rune{} + for _, c := range in { + // convert dots to hyphens + if c == '.' { + out = append(out, '-') + } else if c == '-' { + out = append(out, '-') + out = append(out, '-') + } else { + out = append(out, c) + } + } + return string(out), nil +} + +// FromSubdomain converts a doman which has been encoded as +// a subdomain using the algorithm of ToSubdoman back to a domain. +// Input errors if not a 1035 label. +func FromSubdomain(in string) (string, error) { + if errs := validation.IsDNS1035Label(in); len(errs) > 0 { + return "", errors.New(strings.Join(errs, ",")) + } + + rr := []rune(in) + out := []rune{} + + for i := 0; i < len(rr); i++ { + c := rr[i] + if c == '-' { + // If the next rune is either nonexistent + // or not also a dash, this is an encoded dot. + if i+1 == len(rr) || rr[i+1] != '-' { + out = append(out, '.') + continue + } + + // If the next rune is also a dash, this is + // an escaping dash, so append a slash, and + // increment the pointer such that the next + // loop begins with the next potential tuple. + if rr[i+1] == '-' { + out = append(out, '-') + i++ + continue + } + } + out = append(out, c) + } + + return string(out), nil +} diff --git a/k8s/names_test.go b/k8s/names_test.go new file mode 100644 index 00000000..8262f78c --- /dev/null +++ b/k8s/names_test.go @@ -0,0 +1,56 @@ +package k8s + +import "testing" + +// TestToSubdomain ensures that a valid domain name is +// encoded into the expected subdmain. +func TestToSubdomain(t *testing.T) { + cases := []struct { + In string + Out string + Err bool + }{ + {"", "", true}, // invalid domain + {"*", "", true}, // invalid domain + {"example", "", true}, // invalid domain + {"example.com", "example-com", false}, + {"my-domain.com", "my--domain-com", false}, + } + + for _, c := range cases { + out, err := ToSubdomain(c.In) + if err != nil && !c.Err { + t.Fatalf("Unexpected error: %v", err) + } + if out != c.Out { + t.Fatalf("expected '%v' to yield '%v', got '%v'", c.In, c.Out, out) + } + } +} + +// TestFromSubdomain ensures that a valid subdomain is decoded +// back into a domain. +func TestFromSubdomain(t *testing.T) { + cases := []struct { + In string + Out string + Err bool + }{ + {"", "", true}, // invalid subdomain + {"*", "", true}, // invalid subdomain + {"example-com", "example.com", false}, + {"my--domain-com", "my-domain.com", false}, + {"cdn----1-my--domain-com", "cdn--1.my-domain.com", false}, + } + + for _, c := range cases { + out, err := FromSubdomain(c.In) + if err != nil && !c.Err { + t.Fatalf("Unexpected error: %v", err) + } + if out != c.Out { + t.Fatalf("expected '%v' to yield '%v', got '%v'", c.In, c.Out, out) + } + } + +}