mirror of https://github.com/knative/func.git
feat: function name matches KService name (#317)
* feat: function name matches KService name Signed-off-by: Zbynek Roubalik <zroubali@redhat.com> * fix typo Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>
This commit is contained in:
parent
36926acfb7
commit
541e8586f7
|
@ -79,7 +79,6 @@ type ListItem struct {
|
|||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
Runtime string `json:"runtime" yaml:"runtime"`
|
||||
URL string `json:"url" yaml:"url"`
|
||||
KService string `json:"kservice" yaml:"kservice"`
|
||||
Ready string `json:"ready" yaml:"ready"`
|
||||
}
|
||||
|
||||
|
@ -108,7 +107,6 @@ type Describer interface {
|
|||
type Description struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Image string `json:"image" yaml:"image"`
|
||||
KService string `json:"kservice" yaml:"kservice"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
Routes []string `json:"routes" yaml:"routes"`
|
||||
Subscriptions []Subscription `json:"subscriptions" yaml:"subscriptions"`
|
||||
|
|
|
@ -52,7 +52,13 @@ kn func create --trigger events myfunc
|
|||
}
|
||||
|
||||
func runCreate(cmd *cobra.Command, args []string) error {
|
||||
config := newCreateConfig(args).Prompt()
|
||||
config := newCreateConfig(args)
|
||||
|
||||
if err := utils.ValidateFunctionName(config.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config = config.Prompt()
|
||||
|
||||
function := bosonFunc.Function{
|
||||
Name: config.Name,
|
||||
|
@ -131,12 +137,21 @@ func (c createConfig) Prompt() createConfig {
|
|||
return c
|
||||
}
|
||||
|
||||
derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.Path, prompt.WithRequired(true)))
|
||||
var derivedName, derivedPath string
|
||||
for {
|
||||
derivedName, derivedPath = deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.Path, prompt.WithRequired(true)))
|
||||
err := utils.ValidateFunctionName(derivedName)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
|
||||
return createConfig{
|
||||
Name: derivedName,
|
||||
Path: derivedPath,
|
||||
Runtime: prompt.ForString("Runtime", c.Runtime),
|
||||
Trigger: prompt.ForString("Trigger", c.Trigger),
|
||||
// Templates intentiopnally omitted from prompt for being an edge case.
|
||||
// Templates intentionally omitted from prompt for being an edge case.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,8 +116,6 @@ func (d description) Human(w io.Writer) error {
|
|||
fmt.Fprintf(w, " %v\n", d.Name)
|
||||
fmt.Fprintln(w, "Function is built in image:")
|
||||
fmt.Fprintf(w, " %v\n", d.Image)
|
||||
fmt.Fprintln(w, "Function is deployed as Knative Service:")
|
||||
fmt.Fprintf(w, " %v\n", d.KService)
|
||||
fmt.Fprintln(w, "Function is deployed in namespace:")
|
||||
fmt.Fprintf(w, " %v\n", d.Namespace)
|
||||
fmt.Fprintln(w, "Routes:")
|
||||
|
@ -138,7 +136,6 @@ func (d description) Human(w io.Writer) error {
|
|||
func (d description) Plain(w io.Writer) error {
|
||||
fmt.Fprintf(w, "Name %v\n", d.Name)
|
||||
fmt.Fprintf(w, "Image %v\n", d.Image)
|
||||
fmt.Fprintf(w, "Knative Service %v\n", d.KService)
|
||||
fmt.Fprintf(w, "Namespace %v\n", d.Namespace)
|
||||
|
||||
for _, route := range d.Routes {
|
||||
|
|
|
@ -117,9 +117,9 @@ func (items listItems) Plain(w io.Writer) error {
|
|||
tabWriter := tabwriter.NewWriter(w, 0, 8, 2, ' ', 0)
|
||||
defer tabWriter.Flush()
|
||||
|
||||
fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\n", "NAME", "NAMESPACE", "RUNTIME", "URL", "KSERVICE", "READY")
|
||||
fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\n", "NAME", "NAMESPACE", "RUNTIME", "URL", "READY")
|
||||
for _, item := range items {
|
||||
fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\n", item.Name, item.Namespace, item.Runtime, item.URL, item.KService, item.Ready)
|
||||
fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\n", item.Name, item.Namespace, item.Runtime, item.URL, item.Ready)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
Creates a new Function project at _`path`_. If _`path`_ is unspecified, assumes the current directory. If _`path`_ does not exist, it will be created. The function name is the name of the leaf directory at path. The user can specify the runtime and trigger with flags.
|
||||
|
||||
Function name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?').
|
||||
|
||||
Similar `kn` command: none.
|
||||
|
||||
```console
|
||||
|
|
|
@ -45,14 +45,14 @@ The unit of deployment in Boson Functions is an [OCI](https://opencontainers.org
|
|||
container image, typically referred to as a Docker container image.
|
||||
|
||||
In order for the `func` CLI to manage these containers, you'll need to be
|
||||
logged in to a container registry. For example, `docker.io/lanceball`
|
||||
logged in to a container registry. For example, `docker.io/developer`
|
||||
|
||||
|
||||
```bash
|
||||
# Typically, this will log you in to docker hub if you
|
||||
# omit <registry.url>. If you are using a registry
|
||||
# other than Docker hub, provide that for <registry.url>
|
||||
docker login -u lanceball -p [redacted] <registry.url>
|
||||
docker login -u developer -p [redacted] <registry.url>
|
||||
```
|
||||
|
||||
> Note: many of the `func` CLI commands take a `--registry` argument.
|
||||
|
@ -62,20 +62,21 @@ docker login -u lanceball -p [redacted] <registry.url>
|
|||
```bash
|
||||
# This should be set to a registry that you have write permission
|
||||
# on and you have logged into in the previous step.
|
||||
export FUNC_REGISTRY=docker.io/lanceball
|
||||
export FUNC_REGISTRY=docker.io/developer
|
||||
```
|
||||
|
||||
## Creating a Project
|
||||
|
||||
With your Knative enabled cluster up and running, you can now create a new
|
||||
Function Project. Let's start by creating a project directory. Function names
|
||||
in `func` correspond to URLs at the moment, and there are some finicky cases
|
||||
at the moment. To ensure that everything works as it should, create a project
|
||||
directory consisting of three URL parts. Here is a good one.
|
||||
Function Project. Let's start by creating a project directory. Function name
|
||||
must consist of lower case alphanumeric characters or '-',
|
||||
and must start and end with an alphanumeric character
|
||||
(e.g. 'my-name', or '123-abc', regex used for validation is `[a-z0-9]([-a-z0-9]*[a-z0-9])?`).
|
||||
|
||||
|
||||
```bash
|
||||
mkdir fn.example.io
|
||||
cd fn.example.io
|
||||
mkdir fn-example-io
|
||||
cd fn-example-io
|
||||
```
|
||||
|
||||
Now, we will create the project files, build a container, and
|
||||
|
@ -84,7 +85,6 @@ deploy the function as a Knative service.
|
|||
|
||||
```bash
|
||||
func create -l node
|
||||
func build
|
||||
func deploy
|
||||
```
|
||||
|
||||
|
@ -93,9 +93,15 @@ all of the defaults inferred from your environment, for example`$FUNC_REGISTRY`.
|
|||
When the command has completed, you can see the deployed function.
|
||||
|
||||
```bash
|
||||
kn service list
|
||||
NAME URL LATEST AGE CONDITIONS READY REASON
|
||||
fn-example-io http://fn-example-io.func.127.0.0.1.nip.io fn-example-io-ngswh-1 24s 3 OK / 3 True
|
||||
func describe
|
||||
Function name:
|
||||
fn-example-io
|
||||
Function is built in image:
|
||||
docker.io/developer/fn-example-io:latest
|
||||
Function is deployed in namespace:
|
||||
default
|
||||
Routes:
|
||||
http://fn-example-io-default.apps.functions.my-cluster.com
|
||||
```
|
||||
|
||||
Clicking on the URL will take you to the running function in your cluster. You
|
||||
|
@ -108,7 +114,7 @@ should see a simple response.
|
|||
You can add query parameters to the request to see those echoed in return.
|
||||
|
||||
```console
|
||||
curl "http://fn-example-io.func.127.0.0.1.nip.io?name=tiger"
|
||||
curl "http://fn-example-io-default.apps.functions.my-cluster.com?name=tiger"
|
||||
{"query":{"name":"tiger"},"name":"tiger"}
|
||||
```
|
||||
|
||||
|
@ -150,7 +156,7 @@ You might see a message such as this.
|
|||
Error: remover failed to delete the service: timeout: service 'fn-example-io' not ready after 30 seconds.
|
||||
```
|
||||
|
||||
If you do, just run `kn service list` to see if the function is still deployed.
|
||||
If you do, just run `func list` to see if the function is still deployed.
|
||||
It might just take a little time for it to be removed.
|
||||
|
||||
Now, let's clean up the current directory.
|
||||
|
@ -168,9 +174,9 @@ cluster, use the `create` command.
|
|||
func create -l node -t http
|
||||
```
|
||||
|
||||
You can also create a Quarkus or a Golang project by providing `quarkus` or `go`
|
||||
respectively to the `-l` flag. To create a project with a template for
|
||||
CloudEvents, provide `events` to the `-t` flag.
|
||||
You can also create a Quarkus, SpringBoot, Python or a Golang project by providing
|
||||
`quarkus`, `springboot`, `python` or `go` respectively to the `-l` flag.
|
||||
To create a project with a template for CloudEvents, provide `events` to the `-t` flag.
|
||||
|
||||
### `func build`
|
||||
|
||||
|
|
77
k8s/names.go
77
k8s/names.go
|
@ -1,77 +0,0 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// ToK8sAllowedName converts a name to a name that's allowed for k8s service.
|
||||
// k8s does not support service names with dots. So encode it such that
|
||||
// www.my-domain,com -> www-my--domain-com
|
||||
// Input errors if not a 1035 label.
|
||||
// "a DNS-1035 label must consist of lower case alphanumeric characters or '-',
|
||||
// start with an alphabetic character, and end with an alphanumeric character"
|
||||
func ToK8sAllowedName(in string) (string, error) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
result := string(out)
|
||||
|
||||
if errs := validation.IsDNS1035Label(result); len(errs) > 0 {
|
||||
return "", errors.New(strings.Join(errs, ","))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FromK8sAllowedName converts a name which has been encoded as
|
||||
// an allowed k8s name using the algorithm of ToK8sAllowedName back to the original.
|
||||
// www-my--domain-com -> www.my-domain.com
|
||||
// Input errors if not a 1035 label.
|
||||
func FromK8sAllowedName(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
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// +build !integration
|
||||
|
||||
package k8s
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestToK8sAllowedName ensures that a valid name is
|
||||
// encoded into k8s allowed name.
|
||||
func TestToK8sAllowedName(t *testing.T) {
|
||||
cases := []struct {
|
||||
In string
|
||||
Out string
|
||||
Err bool
|
||||
}{
|
||||
{"", "", true}, // invalid name
|
||||
{"*", "", true}, // invalid name
|
||||
{"example", "example", true},
|
||||
{"example.com", "example-com", false},
|
||||
{"my-domain.com", "my--domain-com", false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
out, err := ToK8sAllowedName(c.In)
|
||||
if err != nil && !c.Err {
|
||||
t.Fatalf("Unexpected error: %v, for '%v', want '%v', but got '%v'", err, c.In, c.Out, out)
|
||||
}
|
||||
if out != c.Out {
|
||||
t.Fatalf("expected '%v' to yield '%v', got '%v'", c.In, c.Out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromK8sAllowedName ensures that an allowed k8s name is correctly
|
||||
// decoded back into the original name.
|
||||
func TestFromK8sAllowedName(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 := FromK8sAllowedName(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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@ import (
|
|||
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||
|
||||
bosonFunc "github.com/boson-project/func"
|
||||
"github.com/boson-project/func/k8s"
|
||||
)
|
||||
|
||||
type Deployer struct {
|
||||
|
@ -40,27 +39,20 @@ func NewDeployer(namespaceOverride string) (deployer *Deployer, err error) {
|
|||
|
||||
func (d *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (err error) {
|
||||
|
||||
// k8s does not support service names with dots. so encode it such that
|
||||
// www.my-domain,com -> www-my--domain-com
|
||||
serviceName, err := k8s.ToK8sAllowedName(f.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := NewServingClient(d.Namespace)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = client.GetService(serviceName)
|
||||
_, err = client.GetService(f.Name)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
|
||||
// Let's create a new Service
|
||||
if d.Verbose {
|
||||
fmt.Printf("Creating Knative Service: %v\n", serviceName)
|
||||
fmt.Printf("Creating Knative Service: %v\n", f.Name)
|
||||
}
|
||||
service, err := generateNewService(serviceName, f.ImageWithDigest(), f.Runtime, f.EnvVars)
|
||||
service, err := generateNewService(f.Name, f.ImageWithDigest(), f.Runtime, f.EnvVars)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative deployer failed to generate the service: %v", err)
|
||||
return err
|
||||
|
@ -74,13 +66,13 @@ func (d *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (err error)
|
|||
if d.Verbose {
|
||||
fmt.Println("Waiting for Knative Service to become ready")
|
||||
}
|
||||
err, _ = client.WaitForService(serviceName, DefaultWaitingTimeout, wait.NoopMessageCallback())
|
||||
err, _ = client.WaitForService(f.Name, DefaultWaitingTimeout, wait.NoopMessageCallback())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative deployer failed to wait for the service to become ready: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
route, err := client.GetRoute(serviceName)
|
||||
route, err := client.GetRoute(f.Name)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative deployer failed to get the route: %v", err)
|
||||
return err
|
||||
|
@ -94,13 +86,13 @@ func (d *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (err error)
|
|||
}
|
||||
} else {
|
||||
// Update the existing Service
|
||||
err = client.UpdateServiceWithRetry(serviceName, updateService(f.ImageWithDigest(), f.EnvVars), 3)
|
||||
err = client.UpdateServiceWithRetry(f.Name, updateService(f.ImageWithDigest(), f.EnvVars), 3)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative deployer failed to update the service: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
route, err := client.GetRoute(serviceName)
|
||||
route, err := client.GetRoute(f.Name)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative deployer failed to get the route: %v", err)
|
||||
return err
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"knative.dev/eventing/pkg/apis/eventing/v1beta1"
|
||||
|
||||
bosonFunc "github.com/boson-project/func"
|
||||
"github.com/boson-project/func/k8s"
|
||||
)
|
||||
|
||||
type Describer struct {
|
||||
|
@ -31,11 +30,6 @@ func NewDescriber(namespaceOverride string) (describer *Describer, err error) {
|
|||
// www.example-site.com -> www-example--site-com
|
||||
func (d *Describer) Describe(name string) (description bosonFunc.Description, err error) {
|
||||
|
||||
serviceName, err := k8s.ToK8sAllowedName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
servingClient, err := NewServingClient(d.namespace)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -46,12 +40,12 @@ func (d *Describer) Describe(name string) (description bosonFunc.Description, er
|
|||
return
|
||||
}
|
||||
|
||||
service, err := servingClient.GetService(serviceName)
|
||||
service, err := servingClient.GetService(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
routes, err := servingClient.ListRoutes(v1.WithService(serviceName))
|
||||
routes, err := servingClient.ListRoutes(v1.WithService(name))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -87,11 +81,10 @@ func (d *Describer) Describe(name string) (description bosonFunc.Description, er
|
|||
}
|
||||
}
|
||||
|
||||
description.KService = serviceName
|
||||
description.Name = name
|
||||
description.Namespace = d.namespace
|
||||
description.Routes = routeURLs
|
||||
description.Subscriptions = subscriptions
|
||||
description.Name, err = k8s.FromK8sAllowedName(service.Name)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"knative.dev/pkg/apis"
|
||||
|
||||
bosonFunc "github.com/boson-project/func"
|
||||
"github.com/boson-project/func/k8s"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -46,13 +45,6 @@ func (l *Lister) List(context.Context) (items []bosonFunc.ListItem, err error) {
|
|||
|
||||
for _, service := range lst.Items {
|
||||
|
||||
// Convert the "subdomain-encoded" (i.e. kube-service-friendly) name
|
||||
// back out to a fully qualified service name.
|
||||
name, err := k8s.FromK8sAllowedName(service.Name)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
// get status
|
||||
ready := corev1.ConditionUnknown
|
||||
for _, con := range service.Status.Conditions {
|
||||
|
@ -63,10 +55,9 @@ func (l *Lister) List(context.Context) (items []bosonFunc.ListItem, err error) {
|
|||
}
|
||||
|
||||
listItem := bosonFunc.ListItem{
|
||||
Name: name,
|
||||
Name: service.Name,
|
||||
Namespace: service.Namespace,
|
||||
Runtime: service.Labels["boson.dev/runtime"],
|
||||
KService: service.Name,
|
||||
URL: service.Status.URL.String(),
|
||||
Ready: string(ready),
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/boson-project/func/k8s"
|
||||
)
|
||||
|
||||
const RemoveTimeout = 120 * time.Second
|
||||
|
@ -28,19 +26,14 @@ type Remover struct {
|
|||
|
||||
func (remover *Remover) Remove(ctx context.Context, name string) (err error) {
|
||||
|
||||
serviceName, err := k8s.ToK8sAllowedName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := NewServingClient(remover.Namespace)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Removing Knative Service: %v\n", serviceName)
|
||||
fmt.Printf("Removing Knative Service: %v\n", name)
|
||||
|
||||
err = client.DeleteService(serviceName, RemoveTimeout)
|
||||
err = client.DeleteService(name, RemoveTimeout)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("knative remover failed to delete the service: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// ValidateFunctionName validatest that the input name is a valid function name, ie. valid DNS-1123 label.
|
||||
// It must consist of lower case alphanumeric characters or '-' and start and end with an alphanumeric character
|
||||
// (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')
|
||||
func ValidateFunctionName(name string) error {
|
||||
|
||||
if errs := validation.IsDNS1123Label(name); len(errs) > 0 {
|
||||
// In case of invalid name the error is this:
|
||||
// "a DNS-1123 label must consist of lower case alphanumeric characters or '-',
|
||||
// and must start and end with an alphanumeric character (e.g. 'my-name',
|
||||
// or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"
|
||||
// Let's reuse it for our purposes, ie. replace "DNS-1123 label" substring with "function name"
|
||||
return errors.New(strings.Replace(strings.Join(errs, ""), "a DNS-1123 label", "Function name", 1))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// +build !integration
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
|
||||
// TestValidateFunctionName tests that only correct function names are accepted
|
||||
func TestValidateFunctionName(t *testing.T) {
|
||||
cases := []struct {
|
||||
In string
|
||||
Valid bool
|
||||
}{
|
||||
{"", false},
|
||||
{"*", false},
|
||||
{"-", false},
|
||||
{"example", true},
|
||||
{"example-com", true},
|
||||
{"example.com", false},
|
||||
{"-example-com", false},
|
||||
{"example-com-", false},
|
||||
{"Example", false},
|
||||
{"EXAMPLE", false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
err := ValidateFunctionName(c.In)
|
||||
if err != nil && c.Valid {
|
||||
t.Fatalf("Unexpected error: %v, for '%v'", err, c.In)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue