From 824941c06cc5f4c212659fb07f0ac4bcb9ef93c5 Mon Sep 17 00:00:00 2001 From: vyasgun Date: Thu, 18 Jan 2024 10:56:39 +0530 Subject: [PATCH] func write: write localsettings to .func/local.yaml --- cmd/deploy.go | 4 +- cmd/deploy_test.go | 6 +- pkg/functions/function.go | 56 +++++++++-- pkg/functions/function_test.go | 149 ++++++++++++++++++++++++++++ pkg/pipelines/tekton/gitlab_test.go | 2 +- schema/func_yaml-schema.json | 4 - 6 files changed, 205 insertions(+), 16 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index fac7fe111b..85338e88f5 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -175,7 +175,7 @@ EXAMPLES "Git revision (branch) to be used when deploying via the Git repository ($FUNC_GIT_BRANCH)") cmd.Flags().StringP("git-dir", "d", f.Build.Git.ContextDir, "Directory in the Git repository containing the function (default is the root) ($FUNC_GIT_DIR)") - cmd.Flags().BoolP("remote", "R", f.Deploy.Remote, + cmd.Flags().BoolP("remote", "R", f.Local.Remote, "Trigger a remote deployment. Default is to deploy and build from the local system ($FUNC_REMOTE)") cmd.Flags().String("pvc-size", f.Build.PVCSize, "When triggering a remote deployment, set a custom volume size to allocate for the build operation ($FUNC_PVC_SIZE)") @@ -506,8 +506,8 @@ func (c deployConfig) Configure(f fn.Function) (fn.Function, error) { f.Build.Git.ContextDir = c.GitDir f.Build.Git.Revision = c.GitBranch // TODO: should match; perhaps "refSpec" f.Deploy.Namespace = c.Namespace - f.Deploy.Remote = c.Remote f.Deploy.ServiceAccountName = c.ServiceAccountName + f.Local.Remote = c.Remote // PVCSize // If a specific value is requested, ensure it parses as a resource.Quantity diff --git a/cmd/deploy_test.go b/cmd/deploy_test.go index 02c0bbb167..1b6ae789ed 100644 --- a/cmd/deploy_test.go +++ b/cmd/deploy_test.go @@ -1433,7 +1433,7 @@ func TestDeploy_RemotePersists(t *testing.T) { if f, err = fn.NewFunction(root); err != nil { t.Fatal(err) } - if !f.Deploy.Remote { + if !f.Local.Remote { t.Fatalf("value of remote flag not persisted") } @@ -1449,7 +1449,7 @@ func TestDeploy_RemotePersists(t *testing.T) { if f, err = fn.NewFunction(root); err != nil { t.Fatal(err) } - if !f.Deploy.Remote { + if !f.Local.Remote { t.Fatalf("value of remote flag not persisted") } @@ -1464,7 +1464,7 @@ func TestDeploy_RemotePersists(t *testing.T) { if f, err = fn.NewFunction(root); err != nil { t.Fatal(err) } - if f.Deploy.Remote { + if f.Local.Remote { t.Fatalf("value of remote flag not persisted") } } diff --git a/pkg/functions/function.go b/pkg/functions/function.go index d7a09a7304..aed099c4e2 100644 --- a/pkg/functions/function.go +++ b/pkg/functions/function.go @@ -22,9 +22,18 @@ const ( // RunDataDir holds transient runtime metadata // By default it is excluded from source control. - RunDataDir = ".func" + RunDataDir = ".func" + RunDataLocalFile = "local.yaml" ) +// Local represents the transient runtime metadata which +// is only relevant to the local copy of the function +type Local struct { + // Remote indicates the deployment (and possibly build) process are to + // be triggered in a remote environment rather than run locally. + Remote bool `yaml:"remote,omitempty"` +} + // Function type Function struct { // SpecVersion at which this function is known to be compatible. @@ -87,6 +96,8 @@ type Function struct { // Deploy defines the deployment properties for a function Deploy DeploySpec `yaml:"deploy,omitempty"` + + Local Local `yaml:"-"` } // BuildSpec @@ -137,10 +148,6 @@ type DeploySpec struct { // Namespace into which the function is deployed on supported platforms. Namespace string `yaml:"namespace,omitempty"` - // Remote indicates the deployment (and possibly build) process are to - // be triggered in a remote environment rather than run locally. - Remote bool `yaml:"remote,omitempty"` - // Map containing user-supplied annotations // Example: { "division": "finance" } Annotations map[string]string `yaml:"annotations,omitempty"` @@ -257,6 +264,7 @@ func NewFunction(root string) (f Function, err error) { errorText += "\n" + "Migration: " + functionMigrationError.Error() return Function{}, errors.New(errorText) } + f.Local, err = f.newLocal() return } @@ -385,7 +393,23 @@ func (f Function) Write() (err error) { } // TODO: open existing file for writing, such that existing permissions // are preserved? - return os.WriteFile(filepath.Join(f.Root, FunctionFile), bb, 0644) + err = os.WriteFile(filepath.Join(f.Root, FunctionFile), bb, 0644) + if err != nil { + return + } + + // Write local settings + err = ensureRunDataDir(f.Root) + if err != nil { + return + } + if bb, err = yaml.Marshal(&f.Local); err != nil { + return + } + localConfigPath := filepath.Join(f.Root, RunDataDir, RunDataLocalFile) + + err = os.WriteFile(localConfigPath, bb, 0644) + return } type stampOptions struct{ journal bool } @@ -666,3 +690,23 @@ func (f Function) BuildStamp() string { } return string(b) } + +// localSettings returns the local settings set for the function +func (f Function) newLocal() (localConfig Local, err error) { + err = ensureRunDataDir(f.Root) + if err != nil { + return + } + localSettingsPath := filepath.Join(f.Root, RunDataDir, RunDataLocalFile) + if _, err = os.Stat(localSettingsPath); os.IsNotExist(err) { + err = nil + return + } + b, err := os.ReadFile(localSettingsPath) + if err != nil { + return + } + + err = yaml.Unmarshal(b, &localConfig) + return +} diff --git a/pkg/functions/function_test.go b/pkg/functions/function_test.go index 2722dce1bc..f1c2ad9a8b 100644 --- a/pkg/functions/function_test.go +++ b/pkg/functions/function_test.go @@ -13,6 +13,9 @@ import ( "testing" "time" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/google/go-cmp/cmp" fn "knative.dev/func/pkg/functions" @@ -398,3 +401,149 @@ func TestFunction_Stamp(t *testing.T) { t.Fatal("expected journal log not found") } } + +// TestFunction_Local checks if writing a function with custom Local spec +// stays the same for the current system. The test does the following: +// +// create a new function +// set Local.Remote to true +// write it to the disk +// load it again into a new function object +// +// The load should be successful and Local.Remote should be true +func TestFunction_Local(t *testing.T) { + root, rm := Mktemp(t) + defer rm() + fConfig := fn.Function{Root: root, Runtime: "go", Name: "f"} + client := fn.New(fn.WithBuilder(mock.NewBuilder()), fn.WithRegistry(TestRegistry)) + f, err := client.Init(fConfig) + if err != nil { + t.Fatal(err) + } + f.Local.Remote = true + + err = f.Write() + if err != nil { + t.Fatal(err) + } + + // Load the function from the same location + f, err = fn.NewFunction(root) + if err != nil { + t.Fatal(err) + } + + if !f.Local.Remote { + t.Fatal("expected remote flag to be set") + } +} + +// TestFunction_LocalTransient ensures that the Local field is transient and +// is not serialised in a way that affects other clones of the function. +// The test does the following: +// +// create a function (with Local.Remote set) +// push the function to a remote repo (locally setup for the test) +// clone the function from the remote repo into a new location +// +// The new function should not have Local.Remote set (as it is a transient field) +func TestFunction_LocalTransient(t *testing.T) { + + // Initialise a new function + root, rm := Mktemp(t) + defer rm() + + fConfig := fn.Function{Root: root, Runtime: "go", Name: "f", Image: "test:latest"} + client := fn.New(fn.WithBuilder(mock.NewBuilder())) + f, err := client.Init(fConfig) + if err != nil { + t.Fatal(err) + } + f.Local.Remote = true + + err = f.Write() + if err != nil { + t.Fatal(err) + } + + // Initialise the function directory as a git repo + repo, err := git.PlainInit(root, false) + if err != nil { + t.Fatal(err) + } + + // commit the function files + wt, err := repo.Worktree() + if err != nil { + t.Fatal(err) + } + if _, err = wt.Add("."); err != nil { + t.Fatal(err) + } + if _, err = wt.Commit("init", &git.CommitOptions{ + All: true, + AllowEmptyCommits: false, + Author: &object.Signature{ + Name: "xyz", + Email: "xyz@abc.com", + When: time.Now(), + }, + Committer: &object.Signature{ + Name: "xyz", + Email: "xyz@abc.com", + When: time.Now(), + }, + }); err != nil { + t.Fatal(err) + } + + // Create a remote and push the function + remotePath, remoteRm := Mktemp(t) + defer remoteRm() + if _, err = git.PlainInit(remotePath, true); err != nil { + t.Fatal(err) + } + + _, err = repo.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{remotePath}, + Mirror: false, + Fetch: nil, + }) + if err != nil { + t.Fatal(err) + } + + err = repo.Push(&git.PushOptions{ + RemoteName: "origin", + RemoteURL: remotePath, + InsecureSkipTLS: true, + }) + if err != nil { + t.Fatal() + } + + // Create a new directory to clone the function in + newRoot, newRm := Mktemp(t) + defer newRm() + + // Clone the pushed function + _, err = git.PlainClone(newRoot, false, &git.CloneOptions{ + URL: remotePath, + RemoteName: "origin", + InsecureSkipTLS: true, + }) + if err != nil { + t.Fatal(err) + } + + // Read the function from the new location + newFunc, err := fn.NewFunction(newRoot) + if err != nil { + t.Fatal(newFunc, err) + } + + if newFunc.Local.Remote { + t.Fatal("Remote not supposed to be set") + } +} diff --git a/pkg/pipelines/tekton/gitlab_test.go b/pkg/pipelines/tekton/gitlab_test.go index 580f8f617a..dac32320c4 100644 --- a/pkg/pipelines/tekton/gitlab_test.go +++ b/pkg/pipelines/tekton/gitlab_test.go @@ -85,9 +85,9 @@ func TestGitlab(t *testing.T) { PVCSize: "256Mi", }, Deploy: fn.DeploySpec{ - Remote: true, Namespace: ns, }, + Local: fn.Local{Remote: true}, } f = fn.NewFunctionWith(f) err = f.Write() diff --git a/schema/func_yaml-schema.json b/schema/func_yaml-schema.json index 8d9924a158..2d29fb0f17 100644 --- a/schema/func_yaml-schema.json +++ b/schema/func_yaml-schema.json @@ -56,10 +56,6 @@ "type": "string", "description": "Namespace into which the function is deployed on supported platforms." }, - "remote": { - "type": "boolean", - "description": "Remote indicates the deployment (and possibly build) process are to\nbe triggered in a remote environment rather than run locally." - }, "annotations": { "patternProperties": { ".*": {