add apply and update client methods (#1529)

Adds two meta-commands, 'Apply' and 'Update', to the client.
Renames 'Create' to 'Init' to avoid confusion with 'Update' (and to be more
linguistically similar to git commands), and adds it as an alias to the
create CLI command.
This commit is contained in:
Luke Kingland 2023-02-01 17:37:29 +09:00 committed by GitHub
parent 3edf2dd355
commit f9d17b0c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 66 deletions

View File

@ -407,8 +407,55 @@ func (c *Client) Runtimes() ([]string, error) {
// LIFECYCLE METHODS
// -----------------
// Apply (aka upsert)
//
// Invokes all lower-level methods as necessary to create a running function
// whose source code and metadata match that provided by the passed
// function instance.
func (c *Client) Apply(ctx context.Context, f Function) (err error) {
if f, err = NewFunction(f.Root); err != nil {
return
}
if !f.Initialized() {
return c.New(ctx, f)
} else {
return c.Update(ctx, f.Root)
}
}
// Update function
//
// Updates a function which has already been initialized to run the latest
// source code.
//
// Use Init, Build, Push and Deploy independently for lower level control.
func (c *Client) Update(ctx context.Context, root string) (err error) {
f, err := NewFunction(root)
if err != nil {
return
}
if !f.Initialized() {
return ErrNotInitialized
}
if err = c.Build(ctx, f.Root); err != nil {
return
}
if err = c.Push(ctx, f.Root); err != nil {
return
}
if err = c.Deploy(ctx, f.Root); err != nil {
return
}
return c.Route(f.Root)
}
// New function.
// Use Create, Build and Deploy independently for lower level control.
//
// Creates a new running function from the path indicated by the config
// Function. Used by Apply when the path is not yet an initialized function.
// Errors if the path is alrady an initialized function.
//
// Use Init, Build, Push, Deploy etc. independently for lower level control.
func (c *Client) New(ctx context.Context, cfg Function) (err error) {
c.progressListener.SetTotal(3)
// Always start a concurrent routine listening for context cancellation.
@ -421,8 +468,8 @@ func (c *Client) New(ctx context.Context, cfg Function) (err error) {
c.progressListener.Stopping()
}()
// Create function at path indidcated by Config
if err = c.Create(cfg); err != nil {
// Init the path as a new Function
if err = c.Init(cfg); err != nil {
return
}
@ -468,12 +515,15 @@ func (c *Client) New(ctx context.Context, cfg Function) (err error) {
return
}
// Create a new function from the given defaults.
// Initialize a new function with the given function struct defaults.
// Does not build/push/deploy. Local FS changes only. For higher-level
// control see New or Apply.
//
// <path> will default to the absolute path of the current working directory.
// <name> will default to the current working directory.
// When <name> is provided but <path> is not, a directory <name> is created
// in the current working directory and used for <path>.
func (c *Client) Create(cfg Function) (err error) {
func (c *Client) Init(cfg Function) (err error) {
// convert Root path to absolute
cfg.Root, err = filepath.Abs(cfg.Root)
cfg.SpecVersion = LastSpecVersion()

View File

@ -201,7 +201,7 @@ func TestRemoteRepositories(t *testing.T) {
fn.WithRegistry(DefaultRegistry),
fn.WithRepository("https://github.com/boson-project/test-templates"),
)
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: ".",
Runtime: "runtime",
Template: "template",

View File

@ -862,7 +862,7 @@ func TestClient_Remove_ByName(t *testing.T) {
fn.WithRegistry(TestRegistry),
fn.WithRemover(remover))
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
if err := client.Init(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
t.Fatal(err)
}
@ -963,7 +963,7 @@ func TestClient_Deploy_Image(t *testing.T) {
fn.WithDeployer(mock.NewDeployer()),
fn.WithRegistry("example.com/alice"))
err := client.Create(fn.Function{Name: "myfunc", Runtime: "go", Root: root})
err := client.Init(fn.Function{Name: "myfunc", Runtime: "go", Root: root})
if err != nil {
t.Fatal(err)
}
@ -1048,7 +1048,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) {
},
}
err := client.Create(f)
err := client.Init(f)
if err != nil {
t.Fatal(err)
}
@ -1113,7 +1113,7 @@ func TestClient_Deploy_UnbuiltErrors(t *testing.T) {
client := fn.New(fn.WithRegistry(TestRegistry))
// Initialize (half-create) a new function at root
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
if err := client.Init(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
t.Fatal(err)
}
@ -1535,7 +1535,7 @@ func TestClient_BuiltStamps(t *testing.T) {
}
// a freshly-created function should be !Built
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
if err := client.Init(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
t.Fatal(err)
}
if client.Built(root) {
@ -1559,7 +1559,7 @@ func TestClient_CreateMigration(t *testing.T) {
client := fn.New()
// create a new function
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
if err := client.Init(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
t.Fatal(err)
}
@ -1589,7 +1589,7 @@ func TestClient_BuiltDetects(t *testing.T) {
defer rm()
// Create and build a function
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
if err := client.Init(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
t.Fatal(err)
}
if err := client.Build(ctx, root); err != nil {

View File

@ -23,7 +23,7 @@ func TestBuild_ConfigApplied(t *testing.T) {
)
t.Setenv("XDG_CONFIG_HOME", home)
if err = fn.New().Create(f); err != nil {
if err = fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -100,7 +100,7 @@ func TestBuild_ConfigPrecidence(t *testing.T) {
root := fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry.example.com/global
f := fn.Function{Runtime: "go", Root: root, Name: "f"}
if err = fn.New().Create(f); err != nil {
if err = fn.New().Init(f); err != nil {
t.Fatal(err)
}
if err := NewBuildCmd(clientFn).Execute(); err != nil {
@ -117,7 +117,7 @@ func TestBuild_ConfigPrecidence(t *testing.T) {
root = fromTempDirectory(t)
t.Setenv("XDG_CONFIG_HOME", home) // sets registry.example.com/global
f = fn.Function{Runtime: "go", Root: root, Name: "f"}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
if err = NewBuildCmd(clientFn).Execute(); err != nil {
@ -137,7 +137,7 @@ func TestBuild_ConfigPrecidence(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", home) // sets registry=example.com/global
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
if err = NewBuildCmd(clientFn).Execute(); err != nil {
@ -156,7 +156,7 @@ func TestBuild_ConfigPrecidence(t *testing.T) {
t.Setenv("FUNC_REGISTRY", "example.com/env")
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
if err := NewBuildCmd(clientFn).Execute(); err != nil {
@ -175,7 +175,7 @@ func TestBuild_ConfigPrecidence(t *testing.T) {
t.Setenv("FUNC_REGISTRY", "example.com/env")
f = fn.Function{Runtime: "go", Root: root, Name: "f",
Registry: "example.com/function"}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
cmd := NewBuildCmd(clientFn)
@ -199,7 +199,7 @@ func TestBuild_ImageFlag(t *testing.T) {
)
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -229,7 +229,7 @@ func TestBuild_ImageFlag(t *testing.T) {
func TestBuild_RegistryOrImageRequired(t *testing.T) {
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -289,7 +289,7 @@ func TestBuild_Push(t *testing.T) {
Runtime: "go",
Registry: "example.com/alice",
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -391,7 +391,7 @@ func TestBuild_Registry(t *testing.T) {
root := fromTempDirectory(t)
test.f.Runtime = "go"
test.f.Name = "f"
if err := fn.New().Create(test.f); err != nil {
if err := fn.New().Init(test.f); err != nil {
t.Fatal(err)
}
cmd := NewBuildCmd(NewTestClient())
@ -419,7 +419,7 @@ func TestBuild_Registry(t *testing.T) {
func TestBuild_FunctionContext(t *testing.T) {
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root, Registry: TestRegistry}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root, Registry: TestRegistry}); err != nil {
t.Fatal(err)
}

View File

@ -73,6 +73,7 @@ EXAMPLES
`,
SuggestFor: []string{"vreate", "creaet", "craete", "new"},
PreRunE: bindEnv("language", "template", "repository", "confirm"),
Aliases: []string{"init"},
}
// Config
@ -131,7 +132,7 @@ func runCreate(cmd *cobra.Command, args []string, newClient ClientFactory) (err
}
// Create
err = client.Create(fn.Function{
err = client.Init(fn.Function{
Name: cfg.Name,
Root: cfg.Path,
Runtime: cfg.Runtime,

View File

@ -39,7 +39,7 @@ func TestDelete_Namespace(t *testing.T) {
Namespace: "deployed",
},
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
cmd = NewDeleteCmd(func(cc ClientConfig, options ...fn.Option) (*fn.Client, func()) {

View File

@ -32,7 +32,7 @@ func TestDeploy_Default(t *testing.T) {
Runtime: "go",
Registry: "example.com/alice",
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -56,7 +56,7 @@ func testRegistryOrImageRequired(cmdFn commandConstructor, t *testing.T) {
t.Helper()
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -91,7 +91,7 @@ func testImageAndRegistry(cmdFn commandConstructor, t *testing.T) {
t.Helper()
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -166,7 +166,7 @@ func testInvalidRegistry(cmdFn commandConstructor, t *testing.T) {
Name: "myFunc",
Runtime: "go",
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -197,7 +197,7 @@ func testRegistryLoads(cmdFn commandConstructor, t *testing.T) {
Runtime: "go",
Registry: "example.com/alice",
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -229,7 +229,7 @@ func testBuilderPersists(cmdFn commandConstructor, t *testing.T) {
t.Helper()
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
cmd := cmdFn(NewTestClient(fn.WithRegistry(TestRegistry)))
@ -317,7 +317,7 @@ func testBuilderValidated(cmdFn commandConstructor, t *testing.T) {
t.Helper()
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -381,7 +381,7 @@ func TestDeploy_RemoteBuildURLPermutations(t *testing.T) {
root := fromTempDirectory(t)
// Create a new Function in the temp directory
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -541,7 +541,7 @@ func Test_ImageWithDigestErrors(t *testing.T) {
root := fromTempDirectory(t)
// Create a new Function in the temp directory
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -603,7 +603,7 @@ func TestDeploy_Namespace(t *testing.T) {
}
// A function which will be repeatedly, mockingly deployed
if err := fn.New().Create(fn.Function{Root: root, Runtime: "go", Registry: TestRegistry}); err != nil {
if err := fn.New().Init(fn.Function{Root: root, Runtime: "go", Registry: TestRegistry}); err != nil {
t.Fatal(err)
}
@ -649,7 +649,7 @@ func TestDeploy_GitArgsPersist(t *testing.T) {
)
// Create a new Function in the temp directory
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -690,7 +690,7 @@ func TestDeploy_GitArgsUsed(t *testing.T) {
dir = "function"
)
// Create a new Function in the temp dir
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -727,7 +727,7 @@ func TestDeploy_GitArgsUsed(t *testing.T) {
func TestDeploy_GitURLBranch(t *testing.T) {
root := fromTempDirectory(t)
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -769,7 +769,7 @@ func TestDeploy_NamespaceDefaults(t *testing.T) {
t.Setenv("KUBECONFIG", kubeconfig)
// Create a new function
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -825,7 +825,7 @@ func TestDeploy_NamespaceUpdateWarning(t *testing.T) {
Namespace: "myns",
},
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -884,7 +884,7 @@ func TestDeploy_NamespaceRedeployWarning(t *testing.T) {
Root: root,
Deploy: fn.DeploySpec{Namespace: "funcns"},
}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}
@ -927,7 +927,7 @@ func TestDeploy_RemotePersists(t *testing.T) {
root := fromTempDirectory(t)
cmdFn := NewDeployCmd
if err := fn.New().Create(fn.Function{Runtime: "node", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "node", Root: root}); err != nil {
t.Fatal(err)
}
cmd := cmdFn(NewTestClient(fn.WithRegistry(TestRegistry)))

View File

@ -40,7 +40,7 @@ func TestDescribe_ByName(t *testing.T) {
func TestDescribe_ByProject(t *testing.T) {
root := fromTempDirectory(t)
err := fn.New().Create(fn.Function{
err := fn.New().Init(fn.Function{
Name: "testname",
Runtime: "go",
Registry: TestRegistry,
@ -111,7 +111,7 @@ func TestDescribe_Namespace(t *testing.T) {
Namespace: "deployed",
},
}
if err := client.Create(f); err != nil {
if err := client.Init(f); err != nil {
t.Fatal(err)
}
cmd = NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {

View File

@ -21,7 +21,7 @@ func TestInvoke(t *testing.T) {
var invoked int32
// Create a test function to be invoked
if err := fn.New().Create(fn.Function{Runtime: "go", Root: root}); err != nil {
if err := fn.New().Init(fn.Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
@ -80,7 +80,7 @@ func TestInvoke_Namespace(t *testing.T) {
// Create a Function in a non-active namespace
f := fn.Function{Runtime: "go", Root: root, Deploy: fn.DeploySpec{Namespace: "ns"}}
if err := fn.New().Create(f); err != nil {
if err := fn.New().Init(f); err != nil {
t.Fatal(err)
}

View File

@ -79,7 +79,7 @@ func TestFunction_WriteIdempotency(t *testing.T) {
Runtime: TestRuntime,
Root: root,
}
if err := client.Create(f); err != nil {
if err := client.Init(f); err != nil {
t.Fatal(err)
}
@ -124,7 +124,7 @@ func TestFunction_NameDefault(t *testing.T) {
Runtime: TestRuntime,
Root: root,
}
if err := client.Create(f); err != nil {
if err := client.Init(f); err != nil {
t.Fatal(err)
}

View File

@ -20,7 +20,7 @@ func TestInstances_LocalErrors(t *testing.T) {
defer rm()
// Create a function that will not be running
if err := New().Create(Function{Runtime: "go", Root: root}); err != nil {
if err := New().Init(Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}
// Load the function
@ -67,7 +67,7 @@ func TestInstance_RemoteErrors(t *testing.T) {
defer rm()
// Create a function that will not be running
if err := New().Create(Function{Runtime: "go", Root: root}); err != nil {
if err := New().Init(Function{Runtime: "go", Root: root}); err != nil {
t.Fatal(err)
}

View File

@ -105,7 +105,7 @@ func TestTemplates_Embedded(t *testing.T) {
client := fn.New(fn.WithRegistry(TestRegistry))
// write out a template
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: TestRuntime,
Template: "http",
@ -138,7 +138,7 @@ func TestTemplates_Custom(t *testing.T) {
// Create a function specifying a template from
// the custom provider's directory in the on-disk template repo.
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "customRuntime",
Template: "customTemplateRepo/customTemplate",
@ -173,7 +173,7 @@ func TestTemplates_Remote(t *testing.T) {
// Create a default function, which should override builtin and use
// that from the specified url (git repo)
err = client.Create(fn.Function{
err = client.Init(fn.Function{
Root: root,
Runtime: "go",
Template: "remote",
@ -200,7 +200,7 @@ func TestTemplates_Default(t *testing.T) {
// The runtime is specified, and explicitly includes a
// file for the default template of fn.DefaultTemplate
err := client.Create(fn.Function{Root: root, Runtime: TestRuntime})
err := client.Init(fn.Function{Root: root, Runtime: TestRuntime})
if err != nil {
t.Fatal(err)
}
@ -225,7 +225,7 @@ func TestTemplates_InvalidErrors(t *testing.T) {
var err error
// Test for error writing an invalid runtime
err = client.Create(fn.Function{
err = client.Init(fn.Function{
Root: root,
Runtime: "invalid",
})
@ -235,7 +235,7 @@ func TestTemplates_InvalidErrors(t *testing.T) {
os.Remove(filepath.Join(root, ".gitignore"))
// Test for error writing an invalid template
err = client.Create(fn.Function{
err = client.Init(fn.Function{
Root: root,
Runtime: TestRuntime,
Template: "invalid",
@ -261,7 +261,7 @@ func TestTemplates_ModeEmbedded(t *testing.T) {
// Write the embedded template that contains a file which
// needs to be executable (only such is mvnw in quarkus)
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "quarkus",
Template: "http",
@ -296,7 +296,7 @@ func TestTemplates_ModeCustom(t *testing.T) {
fn.WithRepositoriesPath("testdata/repositories"))
// Write executable from custom repo
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "test",
Template: "customTemplateRepo/tplb",
@ -335,7 +335,7 @@ func TestTemplates_ModeRemote(t *testing.T) {
fn.WithRepository(url))
// Write executable from custom repo
err = client.Create(fn.Function{
err = client.Init(fn.Function{
Root: root,
Runtime: "node",
Template: "remote",
@ -378,7 +378,7 @@ func TestTemplates_RuntimeManifestBuildEnvs(t *testing.T) {
fn.WithRepositoriesPath("testdata/repositories"))
// write out a template
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "manifestedRuntime",
Template: "customLanguagePackRepo/customTemplate",
@ -425,7 +425,7 @@ func TestTemplates_ManifestBuildEnvs(t *testing.T) {
fn.WithRepositoriesPath("testdata/repositories"))
// write out a template
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "manifestedRuntime",
Template: "customLanguagePackRepo/manifestedTemplate",
@ -472,7 +472,7 @@ func TestTemplates_RepositoryManifestBuildEnvs(t *testing.T) {
fn.WithRepositoriesPath("testdata/repositories"))
// write out a template
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "customRuntime",
Template: "customLanguagePackRepo/customTemplate",
@ -516,7 +516,7 @@ func TestTemplates_ManifestInvocationHints(t *testing.T) {
fn.WithRegistry(TestRegistry),
fn.WithRepositoriesPath("testdata/repositories"))
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "manifestedRuntime",
Template: "customLanguagePackRepo/manifestedTemplate",
@ -548,7 +548,7 @@ func TestTemplates_ManifestRemoved(t *testing.T) {
fn.WithRepositoriesPath("testdata/repositories"))
// write out a template
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "manifestedRuntime",
Template: "customLanguagePackRepo/manifestedTemplate",
@ -584,7 +584,7 @@ func TestTemplates_InvocationDefault(t *testing.T) {
// The customTemplateRepo explicitly does not
// include manifests as it exemplifies an entirely default template repo.
err := client.Create(fn.Function{
err := client.Init(fn.Function{
Root: root,
Runtime: "customRuntime",
Template: "customTemplateRepo/customTemplate",