Skip to content

Commit 0258626

Browse files
authored
feat: decouple function name from function domain (#127)
* decouple function name from function domain Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>
1 parent 02309a2 commit 0258626

15 files changed

+126
-312
lines changed

client.go

+7-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const (
1111
DefaultRegistry = "docker.io"
1212
DefaultRuntime = "go"
1313
DefaultTrigger = "http"
14-
DefaultMaxRecursion = 5 // when determining a name from path
1514
)
1615

1716
// Client for managing Function instances.
@@ -127,7 +126,6 @@ func New(options ...Option) *Client {
127126
lister: &noopLister{output: os.Stdout},
128127
dnsProvider: &noopDNSProvider{output: os.Stdout},
129128
progressListener: &noopProgressListener{},
130-
domainSearchLimit: DefaultMaxRecursion, // no recursion limit deriving domain by default.
131129
}
132130

133131
// Apply passed options, which take ultimate precidence.
@@ -306,6 +304,12 @@ func (c *Client) Create(cfg Function) (err error) {
306304
// Initialize creates a new Function project locally using the settings
307305
// provided on a Function object.
308306
func (c *Client) Initialize(cfg Function) (err error) {
307+
308+
// Create project root directory, if it doesn't already exist
309+
if err = os.MkdirAll(cfg.Root, 0755); err != nil {
310+
return
311+
}
312+
309313
// Create Function of the given root path.
310314
f, err := NewFunction(cfg.Root)
311315
if err != nil {
@@ -320,15 +324,8 @@ func (c *Client) Initialize(cfg Function) (err error) {
320324

321325
f.Image = cfg.Image
322326

323-
// Set the name to that provided, defaulting to path derivation if empty.
327+
// Set the name to that provided.
324328
f.Name = cfg.Name
325-
if cfg.Name == "" {
326-
f.Name = pathToDomain(f.Root, c.domainSearchLimit)
327-
if f.Name == "" {
328-
err = errors.New("Function name must be deriveable from path or explicitly provided")
329-
return
330-
}
331-
}
332329

333330
// Assert runtime was provided, or default.
334331
f.Runtime = cfg.Runtime

client_test.go

+21-57
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,14 @@ func TestCreateWritesTemplate(t *testing.T) {
5656
// TestCreateInitializedAborts ensures that a directory which contains an initialized
5757
// function does not reinitialize
5858
func TestCreateInitializedAborts(t *testing.T) {
59-
root := "testdata/example.com/testCreateInitializedAborts" // contains only a .faas.config
59+
root := "testdata/example.com/testCreateInitializedAborts"
60+
defer os.RemoveAll(root)
61+
6062
client := faas.New()
63+
if err := client.Initialize(faas.Function{Root: root}); err != nil {
64+
t.Fatal(err)
65+
}
66+
6167
if err := client.Initialize(faas.Function{Root: root}); err == nil {
6268
t.Fatal("error expected initilizing a path already containing an initialized Function")
6369
}
@@ -67,6 +73,15 @@ func TestCreateInitializedAborts(t *testing.T) {
6773
// files aborts.
6874
func TestCreateNonemptyDirectoryAborts(t *testing.T) {
6975
root := "testdata/example.com/testCreateNonemptyDirectoryAborts" // contains only a single visible file.
76+
if err := os.MkdirAll(root, 0744); err != nil {
77+
t.Fatal(err)
78+
}
79+
defer os.RemoveAll(root)
80+
_, err := os.Create(root + "/file.txt")
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
7085
client := faas.New()
7186
if err := client.Initialize(faas.Function{Root: root}); err == nil {
7287
t.Fatal("error expected initilizing a Function in a nonempty directory")
@@ -212,57 +227,6 @@ func TestUnsupportedRuntime(t *testing.T) {
212227
}
213228
}
214229

215-
// TestDeriveDomain ensures that the name of the service is a domain derived
216-
// from the current path if possible.
217-
// see unit tests on the pathToDomain for more detailed logic.
218-
func TestDeriveName(t *testing.T) {
219-
// Create the root Function directory
220-
root := "testdata/example.com/testDeriveDomain"
221-
if err := os.MkdirAll(root, 0700); err != nil {
222-
t.Fatal(err)
223-
}
224-
defer os.RemoveAll(root)
225-
226-
client := faas.New(faas.WithRepository(TestRepository))
227-
if err := client.Create(faas.Function{Root: root}); err != nil {
228-
t.Fatal(err)
229-
}
230-
231-
f, err := faas.NewFunction(root)
232-
if err != nil {
233-
t.Fatal(err)
234-
}
235-
236-
if f.Name != "testDeriveDomain.example.com" {
237-
t.Fatalf("unexpected function name '%v'", f.Name)
238-
}
239-
}
240-
241-
// TestDeriveSubdomans ensures that a subdirectory structure is interpreted as
242-
// multilevel subdomains when calculating a derived name for a service.
243-
func TestDeriveSubdomains(t *testing.T) {
244-
// Create the test Function root
245-
root := "testdata/example.com/region1/testDeriveSubdomains"
246-
if err := os.MkdirAll(root, 0700); err != nil {
247-
t.Fatal(err)
248-
}
249-
defer os.RemoveAll(root)
250-
251-
client := faas.New(faas.WithRepository(TestRepository))
252-
if err := client.Create(faas.Function{Root: root}); err != nil {
253-
t.Fatal(err)
254-
}
255-
256-
f, err := faas.NewFunction(root)
257-
if err != nil {
258-
t.Fatal(err)
259-
}
260-
261-
if f.Name != "testDeriveSubdomains.region1.example.com" {
262-
t.Fatalf("unexpected function name '%v'", f.Name)
263-
}
264-
}
265-
266230
// TestNamed ensures that an explicitly passed name is used in leau of the
267231
// path derived name when provided, and persists through instantiations.
268232
func TestNamed(t *testing.T) {
@@ -387,8 +351,8 @@ func TestDeriveImageDefaultRegistry(t *testing.T) {
387351
func TestCreateDelegates(t *testing.T) {
388352
var (
389353
root = "testdata/example.com/testCreateDelegates" // .. in which to initialize
390-
expectedName = "testCreateDelegates.example.com" // expected to be derived
391-
expectedImage = "quay.io/alice/testCreateDelegates.example.com:latest"
354+
expectedName = "testCreateDelegates" // expected to be derived
355+
expectedImage = "quay.io/alice/testCreateDelegates:latest"
392356
builder = mock.NewBuilder()
393357
pusher = mock.NewPusher()
394358
deployer = mock.NewDeployer()
@@ -501,8 +465,8 @@ func TestRun(t *testing.T) {
501465
func TestUpdate(t *testing.T) {
502466
var (
503467
root = "testdata/example.com/testUpdate"
504-
expectedName = "testUpdate.example.com"
505-
expectedImage = "quay.io/alice/testUpdate.example.com:latest"
468+
expectedName = "testUpdate"
469+
expectedImage = "quay.io/alice/testUpdate:latest"
506470
builder = mock.NewBuilder()
507471
pusher = mock.NewPusher()
508472
updater = mock.NewUpdater()
@@ -580,7 +544,7 @@ func TestUpdate(t *testing.T) {
580544
func TestRemoveByPath(t *testing.T) {
581545
var (
582546
root = "testdata/example.com/testRemoveByPath"
583-
expectedName = "testRemoveByPath.example.com"
547+
expectedName = "testRemoveByPath"
584548
remover = mock.NewRemover()
585549
)
586550

cmd/create.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ func init() {
2020
createCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM")
2121
createCmd.Flags().StringP("image", "i", "", "Optional full image name, in form [registry]/[namespace]/[name]:[tag] for example quay.io/myrepo/project.name:latest (overrides --repository) - $FAAS_IMAGE")
2222
createCmd.Flags().StringP("namespace", "n", "", "Override namespace into which the Function is deployed (on supported platforms). Default is to use currently active underlying platform setting - $FAAS_NAMESPACE")
23-
createCmd.Flags().StringP("path", "p", cwd(), "Path to the new project directory - $FAAS_PATH")
2423
createCmd.Flags().StringP("repository", "r", "", "Repository for built images, ex 'docker.io/myuser' or just 'myuser'. Optional if --image provided. - $FAAS_REPOSITORY")
2524
createCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. - $FAAS_RUNTIME")
2625
createCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "templates"), "Extensible templates path. - $FAAS_TEMPLATES")
@@ -38,18 +37,18 @@ func init() {
3837
}
3938

4039
var createCmd = &cobra.Command{
41-
Use: "create <name>",
40+
Use: "create <path>",
4241
Short: "Create a new Function, including initialization of local files and deployment.",
4342
SuggestFor: []string{"cerate", "new"},
44-
PreRunE: bindEnv("image", "namespace", "path", "repository", "runtime", "templates", "trigger", "confirm"),
43+
PreRunE: bindEnv("image", "namespace", "repository", "runtime", "templates", "trigger", "confirm"),
4544
RunE: runCreate,
4645
}
4746

4847
func runCreate(cmd *cobra.Command, args []string) (err error) {
4948
config := newCreateConfig(args).Prompt()
5049

5150
function := faas.Function{
52-
Name: config.Name,
51+
Name: config.initConfig.Name,
5352
Root: config.initConfig.Path,
5453
Runtime: config.initConfig.Runtime,
5554
Trigger: config.Trigger,
@@ -106,23 +105,24 @@ func newCreateConfig(args []string) createConfig {
106105
}
107106
}
108107

109-
// Prompt the user with value of config members, allowing for interaractive changes.
108+
// Prompt the user with value of config members, allowing for interactive changes.
110109
// Skipped if not in an interactive terminal (non-TTY), or if --confirm (agree to
111110
// all prompts) was not explicitly set.
112111
func (c createConfig) Prompt() createConfig {
113-
name := deriveName(c.Name, c.initConfig.Path)
114112
if !interactiveTerminal() || !c.initConfig.Confirm {
115113
// Just print the basics if not confirming
116114
fmt.Printf("Project path: %v\n", c.initConfig.Path)
117-
fmt.Printf("Project name: %v\n", name)
115+
fmt.Printf("Function name: %v\n", c.initConfig.Name)
118116
fmt.Printf("Runtime: %v\n", c.Runtime)
119117
fmt.Printf("Trigger: %v\n", c.Trigger)
120118
return c
121119
}
120+
121+
derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.initConfig.Path, prompt.WithRequired(true)))
122122
return createConfig{
123123
initConfig: initConfig{
124-
Path: prompt.ForString("Project path", c.initConfig.Path),
125-
Name: prompt.ForString("Project name", name, prompt.WithRequired(true)),
124+
Name: derivedName,
125+
Path: derivedPath,
126126
Runtime: prompt.ForString("Runtime", c.Runtime),
127127
Trigger: prompt.ForString("Trigger", c.Trigger),
128128
// Templates intentionally omitted from prompt for being an edge case.

cmd/init.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
func init() {
1515
root.AddCommand(initCmd)
1616
initCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM")
17-
initCmd.Flags().StringP("path", "p", cwd(), "Path to the new project directory - $FAAS_PATH")
1817
initCmd.Flags().StringP("runtime", "l", faas.DefaultRuntime, "Function runtime language/framework. - $FAAS_RUNTIME")
1918
initCmd.Flags().StringP("templates", "", filepath.Join(configPath(), "templates"), "Extensible templates path. - $FAAS_TEMPLATES")
2019
initCmd.Flags().StringP("trigger", "t", faas.DefaultTrigger, "Function trigger (ex: 'http','events') - $FAAS_TRIGGER")
@@ -25,10 +24,10 @@ func init() {
2524
}
2625

2726
var initCmd = &cobra.Command{
28-
Use: "init <name>",
27+
Use: "init <path>",
2928
Short: "Initialize a new Function project",
3029
SuggestFor: []string{"inti", "new"},
31-
PreRunE: bindEnv("path", "runtime", "templates", "trigger", "confirm"),
30+
PreRunE: bindEnv("runtime", "templates", "trigger", "confirm"),
3231
RunE: runInit,
3332
// TODO: autocomplate Functions for runtime and trigger.
3433
}
@@ -49,10 +48,10 @@ func runInit(cmd *cobra.Command, args []string) error {
4948
}
5049

5150
type initConfig struct {
52-
// Name of the service in DNS-compatible format (ex myfunc.example.com)
51+
// Name of the Function.
5352
Name string
5453

55-
// Path to files on disk. Defaults to current working directory.
54+
// Absolute path to Function on disk.
5655
Path string
5756

5857
// Runtime language/framework.
@@ -78,13 +77,15 @@ type initConfig struct {
7877
// newInitConfig returns a config populated from the current execution context
7978
// (args, flags and environment variables)
8079
func newInitConfig(args []string) initConfig {
81-
var name string
80+
var path string
8281
if len(args) > 0 {
83-
name = args[0] // If explicitly provided, use.
82+
path = args[0] // If explicitly provided, use.
8483
}
84+
85+
derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(path)
8586
return initConfig{
86-
Name: deriveName(name, viper.GetString("path")), // args[0] or derived
87-
Path: viper.GetString("path"),
87+
Name: derivedName,
88+
Path: derivedPath,
8889
Runtime: viper.GetString("runtime"),
8990
Templates: viper.GetString("templates"),
9091
Trigger: viper.GetString("trigger"),
@@ -96,19 +97,19 @@ func newInitConfig(args []string) initConfig {
9697
// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to
9798
// all prompts) was set (default).
9899
func (c initConfig) Prompt() initConfig {
99-
name := deriveName(c.Name, c.Path)
100100
if !interactiveTerminal() || !c.Confirm {
101101
// Just print the basics if not confirming
102102
fmt.Printf("Project path: %v\n", c.Path)
103-
fmt.Printf("Project name: %v\n", name)
103+
fmt.Printf("Function name: %v\n", c.Name)
104104
fmt.Printf("Runtime: %v\n", c.Runtime)
105105
fmt.Printf("Trigger: %v\n", c.Trigger)
106106
return c
107107
}
108+
109+
derivedName, derivedPath := deriveNameAndAbsolutePathFromPath(prompt.ForString("Project path", c.Path, prompt.WithRequired(true)))
108110
return initConfig{
109-
// TODO: Path should be prompted for and set prior to name attempting path derivation. Test/fix this if necessary.
110-
Path: prompt.ForString("Project path", c.Path),
111-
Name: prompt.ForString("Project name", name, prompt.WithRequired(true)),
111+
Name: derivedName,
112+
Path: derivedPath,
112113
Runtime: prompt.ForString("Runtime", c.Runtime),
113114
Trigger: prompt.ForString("Trigger", c.Trigger),
114115
// Templates intentiopnally omitted from prompt for being an edge case.

cmd/root.go

+27-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strings"
78

89
"github.com/mitchellh/go-homedir"
910
"github.com/ory/viper"
@@ -165,21 +166,42 @@ func functionWithOverrides(root, namespace, image string) (f faas.Function, err
165166

166167
// deriveName returns the explicit value (if provided) or attempts to derive
167168
// from the given path. Path is defaulted to current working directory, where
168-
// a function configuration, if it exists and contains a name, is used. Lastly
169-
// derivation using the path us used.
169+
// a Function configuration, if it exists and contains a name, is used.
170170
func deriveName(explicitName string, path string) string {
171171
// If the name was explicitly provided, use it.
172172
if explicitName != "" {
173173
return explicitName
174174
}
175+
175176
// If the directory at path contains an initialized Function, use the name therein
176177
f, err := faas.NewFunction(path)
177178
if err == nil && f.Name != "" {
178179
return f.Name
179180
}
180-
maxRecursion := faas.DefaultMaxRecursion
181-
derivedName, _ := faas.DerivedName(path, maxRecursion)
182-
return derivedName
181+
182+
return ""
183+
}
184+
185+
// deriveNameAndAbsolutePathFromPath returns resolved Function name and absolute path
186+
// to the Function project root. The input parameter path could be one of:
187+
// 'relative/path/to/foo', '/absolute/path/to/foo', 'foo' or ''
188+
func deriveNameAndAbsolutePathFromPath(path string) (string, string) {
189+
var absPath string
190+
191+
// If path is not specifed, we would like to use current working dir
192+
if path == "" {
193+
path = cwd()
194+
}
195+
196+
// Expand the passed Function name to its absolute path
197+
absPath, err := filepath.Abs(path)
198+
if err != nil {
199+
return "", ""
200+
}
201+
202+
// Get the name of the Function, which equals to name of the current directory
203+
pathParts := strings.Split(strings.TrimRight(path, string(os.PathSeparator)), string(os.PathSeparator))
204+
return pathParts[len(pathParts)-1], absPath
183205
}
184206

185207
// deriveImage returns the same image name which will be used if no explicit

docs/commands.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## `init`
44

5-
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 user can specify the runtime and trigger with flags.
5+
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.
66

77
Similar `kn` command: none.
88

0 commit comments

Comments
 (0)