Skip to content

Commit

Permalink
feat: allow on-cluster-build without VCS
Browse files Browse the repository at this point in the history
Signed-off-by: Matej Vasek <mvasek@redhat.com>
  • Loading branch information
matejvasek committed Oct 11, 2022
1 parent 9bd4865 commit 2425081
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 29 deletions.
9 changes: 0 additions & 9 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,6 @@ func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err

// Perform the deployment either remote or local.
if config.Remote {
if f.Build.Git.URL == "" {
return ErrURLRequired // Provides CLI-specific help text
}
// Invoke a remote build/push/deploy pipeline
// Returned is the function with fields like Registry and Image populated.
if f, err = client.RunPipeline(cmd.Context(), f); err != nil {
Expand Down Expand Up @@ -736,9 +733,3 @@ var ErrRegistryRequired = errors.New(`A container registry is required. For exa
--registry docker.io/myusername
To run the command in an interactive mode, use --confirm (-c)`)

var ErrURLRequired = errors.New(`The function is not associated with a Git repository, and needs one in order to perform a remote deployment. For example:
--remote --git-url=https://git.example.com/namespace/myFunction
To run the deploy command in an interactive mode, use --confirm (-c)`)
9 changes: 0 additions & 9 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,15 +426,6 @@ func TestDeploy_RemoteBuildURLPermutations(t *testing.T) {
if remote != "" && remote != "false" { // default "" is == false.
// REMOTE Assertions

// TODO: (enhancement) allow triggering remote deploy without Git.
// This would tar up the local filesystem and send it to the cluster
// build and deploy. For now URL is required when triggering remote.
if url == "" && err == nil {
t.Fatal("error expected when --remote without a --git-url")
} else {
return // test successfully confirmed this error case
}

if !pipeliner.RunInvoked { // Remote deployer should be triggered
t.Error("remote was not invoked")
}
Expand Down
71 changes: 71 additions & 0 deletions pipelines/tekton/pipeplines_provider.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package tekton

import (
"archive/tar"
"context"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"sync"

"github.com/google/go-containerregistry/pkg/authn"
Expand Down Expand Up @@ -112,6 +118,16 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) error {
}
}

if f.Build.Git.URL == "" {
// Use direct upload to PVC if Git is not set up.
content := sourcesAsTarStream(f)
defer content.Close()
err = k8s.UploadToVolume(ctx, content, getPipelinePvcName(f), pp.namespace)
if err != nil {
return fmt.Errorf("cannot upload sources to the PVC: %w", err)
}
}

_, err = client.Pipelines(pp.namespace).Create(ctx, generatePipeline(f, labels), metav1.CreateOptions{})
if err != nil {
if !errors.IsAlreadyExists(err) {
Expand Down Expand Up @@ -183,6 +199,61 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) error {
return nil
}

// Creates tar stream with the function sources as they were in "./source" directory.
func sourcesAsTarStream(f fn.Function) *io.PipeReader {
pr, pw := io.Pipe()
go func() {
tw := tar.NewWriter(pw)
err := filepath.Walk(f.Root, func(p string, fi fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("walk error: %w", err)
}

relp, err := filepath.Rel(f.Root, p)
if err != nil {
return fmt.Errorf("cannot get relative path: %w", err)
}

if relp == "." {
return nil
}

hdr, err := tar.FileInfoHeader(fi, "")
if err != nil {
return fmt.Errorf("cannot create a tar header: %w", err)
}
// "source" is expected path in workspace pvc
hdr.Name = path.Join("source", filepath.ToSlash(relp))

err = tw.WriteHeader(hdr)
if err != nil {
return fmt.Errorf("cannot write header to tar stream: %w", err)
}

if fi.Mode().IsRegular() || (fi.Mode()&fs.ModeSymlink != 0) {
var file io.ReadCloser
file, err = os.Open(p)
if err != nil {
return fmt.Errorf("cannot open source file: %w", err)
}
defer file.Close()
_, err = io.Copy(tw, file)
if err != nil {
return fmt.Errorf("cannot copy source file content: %w", err)
}
}
return nil
})
if err != nil {
_ = pw.CloseWithError(fmt.Errorf("error while creating tar stream from sources: %w", err))
} else {
_ = tw.Close()
_ = pw.Close()
}
}()
return pr
}

func (pp *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {

l := k8slabels.SelectorFromSet(k8slabels.Set(map[string]string{fnlabels.FunctionNameKey: f.Name}))
Expand Down
20 changes: 13 additions & 7 deletions pipelines/tekton/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,33 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe
// Deploy step that uses an image produced by S2I builds needs explicit reference to the image
referenceImageFromPreviousTaskResults := false

var tasks []pplnv1beta1.PipelineTask
var buildPreReq []string

if f.Build.Git.URL != "" {
// If Git is set up create fetch task,
// otherwise sources have been already uploaded to workspace PVC.
buildPreReq = []string{taskNameFetchSources}
tasks = append(tasks, taskFetchSources())
}

if f.Build.Builder == builders.Pack {
// ----- Buildpacks related properties
workspaces = append(workspaces, pplnv1beta1.PipelineWorkspaceDeclaration{Name: "cache-workspace", Description: "Directory where Buildpacks cache is stored."})
taskBuild = taskBuildpacks(taskNameFetchSources)
taskBuild = taskBuildpacks(buildPreReq)

} else if f.Build.Builder == builders.S2I {
// ----- S2I build related properties

params = append(params, pplnv1beta1.ParamSpec{Name: "s2iImageScriptsUrl", Description: "URL containing the default assemble and run scripts for the builder image.",
Default: pplnv1beta1.NewArrayOrString("image:///usr/libexec/s2i")})

taskBuild = taskS2iBuild(taskNameFetchSources)
taskBuild = taskS2iBuild(buildPreReq)
referenceImageFromPreviousTaskResults = true
}

// ----- Pipeline definition
tasks := pplnv1beta1.PipelineTaskList{
taskFetchSources(),
taskBuild,
taskDeploy(taskNameBuild, referenceImageFromPreviousTaskResults),
}
tasks = append(tasks, taskBuild, taskDeploy(taskNameBuild, referenceImageFromPreviousTaskResults))

return &pplnv1beta1.Pipeline{
ObjectMeta: v1.ObjectMeta{
Expand Down
8 changes: 4 additions & 4 deletions pipelines/tekton/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ func taskFetchSources() pplnv1beta1.PipelineTask {
}
}

func taskBuildpacks(runAfter string) pplnv1beta1.PipelineTask {
func taskBuildpacks(runAfter []string) pplnv1beta1.PipelineTask {
return pplnv1beta1.PipelineTask{
Name: taskNameBuild,
TaskRef: &pplnv1beta1.TaskRef{
Name: "func-buildpacks",
},
RunAfter: []string{runAfter},
RunAfter: runAfter,
Workspaces: []pplnv1beta1.WorkspacePipelineTaskBinding{
{
Name: "source",
Expand All @@ -61,7 +61,7 @@ func taskBuildpacks(runAfter string) pplnv1beta1.PipelineTask {
}

}
func taskS2iBuild(runAfter string) pplnv1beta1.PipelineTask {
func taskS2iBuild(runAfter []string) pplnv1beta1.PipelineTask {
params := []pplnv1beta1.Param{
{Name: "IMAGE", Value: *pplnv1beta1.NewArrayOrString("$(params.imageName)")},
{Name: "PATH_CONTEXT", Value: *pplnv1beta1.NewArrayOrString("$(params.contextDir)")},
Expand All @@ -77,7 +77,7 @@ func taskS2iBuild(runAfter string) pplnv1beta1.PipelineTask {
TaskRef: &pplnv1beta1.TaskRef{
Name: "func-s2i",
},
RunAfter: []string{runAfter},
RunAfter: runAfter,
Workspaces: []pplnv1beta1.WorkspacePipelineTaskBinding{
{
Name: "source",
Expand Down

0 comments on commit 2425081

Please sign in to comment.