Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelsauter committed Jul 28, 2023
1 parent d649596 commit 3368333
Show file tree
Hide file tree
Showing 34 changed files with 1,741 additions and 121 deletions.
69 changes: 69 additions & 0 deletions cmd/taskdoc/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Package taskdoc implements documentation rendering for tasks.
// It is intended to be run via `go run`, passing a task YAML manifest
// and a description in Asciidoctor format. The combined result will be
// written to the specified destination.
//
// Example invocation:
//
// go run github.com/opendevstack/ods-pipeline/cmd/taskdoc \
// -task tasks/my-task.yaml \
// -description build/docs/my-task.adoc \
// -destination docs/my-task.adoc
//
// By default, taskdoc will use the template located at
// docs/tasks/template.adoc.tmpl to produce the resulting file. Another
// template can be specified via -template:
//
// go run github.com/opendevstack/ods-pipeline/cmd/taskdoc \
// -task tasks/my-task.yaml \
// -description build/docs/my-task.adoc \
// -template /path/to/my-custom-template.adoc.tmpl \
// -destination docs/my-task.adoc
package main

import (
"flag"
"log"
"os"
"text/template"

"github.com/opendevstack/ods-pipeline/internal/projectpath"
"github.com/opendevstack/ods-pipeline/pkg/taskdoc"
)

func main() {
taskFile := flag.String("task", "", "Task manifest")
descriptionFile := flag.String("description", "", "Description snippet")
templateFile := flag.String("template", projectpath.RootedPath("docs/tasks/template.adoc.tmpl"), "Template file")
destinationFile := flag.String("destination", "", "Destination file")
flag.Parse()
if err := render(*taskFile, *descriptionFile, *templateFile, *destinationFile); err != nil {
log.Fatal(err)
}
}

func render(taskFile, descriptionFile, templateFile, destinationFile string) error {
t, err := os.ReadFile(taskFile)
if err != nil {
return err
}
d, err := os.ReadFile(descriptionFile)
if err != nil {
return err
}
tmpl, err := template.ParseFiles(templateFile)
if err != nil {
return err
}

task, err := taskdoc.ParseTask(t, d)
if err != nil {
return err
}

w, err := os.Create(destinationFile)
if err != nil {
return err
}
return taskdoc.RenderTaskDocumentation(w, tmpl, task)
}
69 changes: 69 additions & 0 deletions cmd/taskmanifest/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Package taskmanifest implements manifest rendering for tasks.
// It is intended to be run via `go run`, passing a task YAML template
// and data to be rendered. The combined result will be
// written to the specified destination. The -data flag can be passed
// multiple times and may specify any key-value combination, which can then
// be consumed in the template through Go's text/template package. E.g.
// passing -data Foo=bar will replace {{.Foo}} in the template with bar.
//
// Example invocation:
//
// go run github.com/opendevstack/ods-pipeline/cmd/taskmanifest \
// -data ImageRepository=ghcr.io/my-org/my-repo \
// -data Version=latest \
// -template build/tasks/my-task.yaml \
// -destination tasks/my-task.yaml
package main

import (
"flag"
"fmt"
"log"
"os"
"strings"
"text/template"

"github.com/opendevstack/ods-pipeline/pkg/taskmanifest"
"github.com/opendevstack/ods-pipeline/pkg/tektontaskrun"
)

func main() {
templateFile := flag.String("template", "", "Template file")
destinationFile := flag.String("destination", "", "Destination file")
cc := tektontaskrun.NewClusterConfig()
mf := &MapFlag{v: cc.DefaultTaskTemplateData()}
flag.Var(mf, "data", "Key-value pairs")
flag.Parse()
if err := render(*templateFile, *destinationFile, mf.v); err != nil {
log.Fatal(err)
}
}

func render(templateFile, destinationFile string, data map[string]string) error {
tmpl, err := template.ParseFiles(templateFile)
if err != nil {
return err
}

w, err := os.Create(destinationFile)
if err != nil {
return err
}
return taskmanifest.RenderTask(w, tmpl, data)
}

type MapFlag struct {
v map[string]string
}

func (mf *MapFlag) String() string {
return fmt.Sprintf("%v", mf.v)
}
func (mf *MapFlag) Set(v string) error {
key, value, ok := strings.Cut(v, "=")
if !ok {
return fmt.Errorf("must have = sign")
}
mf.v[key] = value
return nil
}
60 changes: 48 additions & 12 deletions internal/docs/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package docs

import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"text/template"

"github.com/opendevstack/ods-pipeline/internal/command"
Expand Down Expand Up @@ -37,20 +38,11 @@ func renderTemplate(targetDir, targetFilename string, data Task) error {
if err != nil {
return err
}
templateFilename := filepath.Join(targetDir, "template.adoc.tmpl")
templateFileParts := strings.Split(templateFilename, "/")
templateDisplayname := templateFileParts[len(templateFileParts)-1]
_, err = targetFile.WriteString(
"// Document generated by internal/documentation/tasks.go from " + templateDisplayname + "; DO NOT EDIT.\n\n",
)
tmpl, err := template.ParseFiles(filepath.Join(targetDir, "template.adoc.tmpl"))
if err != nil {
return err
}
tmpl, err := template.ParseFiles(templateFilename)
if err != nil {
return err
}
return tmpl.Execute(targetFile, data)
return RenderTaskDocumentation(targetFile, tmpl, &data)
}

func parseTasks(helmTemplateOutput []byte) ([]*tekton.Task, error) {
Expand Down Expand Up @@ -131,3 +123,47 @@ func RenderTasks(tasksSourceDir, descriptionsSourceDir, targetDir string) error
}
return nil
}

func ParseTask(f []byte, desc []byte) (*Task, error) {
var t tekton.Task
err := yaml.Unmarshal(f, &t)
if err != nil {
return nil, err
}
if t.Name == "" {
return nil, errors.New("encountered empty name, something is wrong with the task")
}
task := &Task{
Name: t.Name,
Description: string(desc),
Params: []Param{},
Results: []Result{},
}
for _, p := range t.Spec.Params {
defaultValue := ""
if p.Default != nil {
defaultValue = p.Default.StringVal
}
task.Params = append(task.Params, Param{
Name: p.Name,
Default: defaultValue,
Description: p.Description,
})
}
for _, r := range t.Spec.Results {
task.Results = append(task.Results, Result{
Name: r.Name,
Description: r.Description,
})
}
return task, nil
}

func RenderTaskDocumentation(w io.Writer, tmpl *template.Template, task *Task) error {
if _, err := w.Write(
[]byte("// File is generated; DO NOT EDIT.\n\n"),
); err != nil {
return err
}
return tmpl.Execute(w, task)
}
4 changes: 4 additions & 0 deletions internal/projectpath/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ var (
// Root folder of this project
Root = filepath.Join(filepath.Dir(b), "../..")
)

func RootedPath(path string) string {
return filepath.Join(Root, path)
}
40 changes: 40 additions & 0 deletions pkg/odstasktest/assertions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package odstasktest

import (
"os"
"path/filepath"
"strings"
"testing"
)

// AssertFilesExist checks that all files named by wantFiles exist in wsDir.
// Any files that do not exist will report a test error.
func AssertFilesExist(t *testing.T, wsDir string, wantFiles ...string) {
for _, wf := range wantFiles {
filename := filepath.Join(wsDir, wf)
if _, err := os.Stat(filename); os.IsNotExist(err) {
t.Errorf("Want %s, but got nothing", filename)
}
}
}

// AssertFileContent checks that the file named by filename in the directory
// wsDir has the exact context specified by want.
func AssertFileContent(t *testing.T, wsDir, filename, want string) {
got, err := getTrimmedFileContent(filepath.Join(wsDir, filename))
if err != nil {
t.Errorf("get content of %s: %s", filename, err)
return
}
if got != want {
t.Errorf("got '%s', want '%s' in file %s", got, want, filename)
}
}

func getTrimmedFileContent(filename string) (string, error) {
content, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return strings.TrimSpace(string(content)), nil
}
77 changes: 77 additions & 0 deletions pkg/odstasktest/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Package odstasktest implements ODS Pipeline specific functionality to run
Tekton tasks in a KinD cluster on top of package tektontaskrun.
odstasktest is intended to be used as a library for testing ODS Pipeline
tasks using Go.
Example usage:
package test
import (
"log"
"os"
"path/filepath"
"testing"
ott "github.com/opendevstack/ods-pipeline/pkg/odstasktest"
ttr "github.com/opendevstack/ods-pipeline/pkg/tektontaskrun"
)
var (
namespaceConfig *ttr.NamespaceConfig
rootPath = "../.."
)
func TestMain(m *testing.M) {
cc, err := ttr.StartKinDCluster(
ttr.LoadImage(ttr.ImageBuildConfig{
Dockerfile: "build/images/Dockerfile.my-task",
ContextDir: rootPath,
}),
)
if err != nil {
log.Fatal("Could not start KinD cluster: ", err)
}
nc, cleanup, err := ttr.SetupTempNamespace(
cc,
ott.StartNexus(),
ott.InstallODSPipeline(),
ttr.InstallTaskFromPath(
filepath.Join(rootPath, "build/tasks/my-task.yaml"),
nil,
),
)
if err != nil {
log.Fatal("Could not setup temporary namespace: ", err)
}
defer cleanup()
namespaceConfig = nc
os.Exit(m.Run())
}
func TestMyTask(t *testing.T) {
if err := ttr.RunTask(
ttr.InNamespace(namespaceConfig.Name),
ttr.UsingTask("my-task"),
ttr.WithStringParams(map[string]string{
"go-os": runtime.GOOS,
"go-arch": runtime.GOARCH,
}),
ott.WithGitSourceWorkspace(t, "../testdata/workspaces/go-sample-app"),
ttr.AfterRun(func(config *ttr.TaskRunConfig, run *tekton.TaskRun) {
ott.AssertFilesExist(
t, config.WorkspaceConfigs["source"].Dir,
"docker/Dockerfile",
"docker/app",
)
}),
); err != nil {
t.Fatal(err)
}
}
// further tests here ...
*/
package odstasktest
45 changes: 45 additions & 0 deletions pkg/odstasktest/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package odstasktest

import (
"flag"
"fmt"
"os"
"path/filepath"

"github.com/opendevstack/ods-pipeline/internal/command"
"github.com/opendevstack/ods-pipeline/internal/projectpath"
ttr "github.com/opendevstack/ods-pipeline/pkg/tektontaskrun"
)

var privateCertFlag = flag.Bool("ods-private-cert", false, "Whether to use a private cert")

// InstallODSPipeline installs the ODS Pipeline Helm chart in the namespace
// given in NamespaceConfig.
func InstallODSPipeline() ttr.NamespaceOpt {
flag.Parse()
return func(cc *ttr.ClusterConfig, nc *ttr.NamespaceConfig) error {
return installCDNamespaceResources(nc.Name, "pipeline", *privateCertFlag)
}
}

func installCDNamespaceResources(ns, serviceaccount string, privateCert bool) error {
scriptArgs := []string{filepath.Join(projectpath.Root, "scripts/install-inside-kind.sh"), "-n", ns, "-s", serviceaccount, "--no-diff"}
// if testing.Verbose() {
// scriptArgs = append(scriptArgs, "-v")
// }
if privateCert {
// Insert as first flag because install-inside-kind.sh won't recognize it otherwise.
scriptArgs = append(
[]string{fmt.Sprintf("--private-cert=%s", filepath.Join(projectpath.Root, "test/testdata/private-cert/tls.crt"))},
scriptArgs...,
)
}

return command.Run(
"sh",
scriptArgs,
[]string{},
os.Stdout,
os.Stderr,
)
}
Loading

0 comments on commit 3368333

Please sign in to comment.