Skip to content

Commit

Permalink
Add export command with GitHubActions support
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhuGongpu committed Jun 30, 2020
1 parent 1193dc7 commit 631e977
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 0 deletions.
101 changes: 101 additions & 0 deletions internal/cmdexport/cmdexport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// package cmdexport contains the export command.
package cmdexport

import (
"fmt"
"os"

"github.com/GoogleContainerTools/kpt/internal/cmdexport/orchestrators"
"github.com/GoogleContainerTools/kpt/internal/cmdexport/types"
"github.com/GoogleContainerTools/kpt/internal/docs/generated/fndocs"
"github.com/spf13/cobra"
)

// The `kpt fn export` command.
func ExportCommand() *cobra.Command {
return GetExportRunner().Command
}

// Create a ExportRunner instance and wire it to the corresponding Command.
func GetExportRunner() *ExportRunner {
r := &ExportRunner{PipelineConfig: &types.PipelineConfig{}}
c := &cobra.Command{
Use: "export orchestrator DIR/",
Short: fndocs.ExportShort,
Long: fndocs.ExportLong,
Example: fndocs.ExportExamples,
// Validate and parse args.
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("accepts %d args, received %d", 2, len(args))
}

r.Orchestrator, r.Dir = args[0], args[1]

switch r.Orchestrator {
case "github-actions":
{
r.Pipeline = new(orchestrators.GitHubActions)
}
default:
return fmt.Errorf("unsupported orchestrator %v", r.Orchestrator)
}

return nil
},
RunE: r.runE,
}

c.Flags().StringSliceVar(
&r.FnPaths, "fn-path", []string{},
"read functions from these directories instead of the configuration directory.")
c.Flags().StringVar(
&r.OutputFilePath, "output", "",
"specify the filename of the generated pipeline. If omitted, the default output is stdout")

r.Command = c

return r
}

// The ExportRunner wraps the user's input and runs the command.
type ExportRunner struct {
Orchestrator string
OutputFilePath string
Command *cobra.Command
*types.PipelineConfig
Pipeline orchestrators.Pipeline
}

// Generate the pipeline and write it into a file or stdout.
func (r *ExportRunner) runE(c *cobra.Command, args []string) error {
pipeline := r.Pipeline.Init(r.PipelineConfig).Generate()

if r.OutputFilePath != "" {
fo, err := os.Create(r.OutputFilePath)

if err != nil {
return err
}

c.SetOut(fo)
}

_, err := c.OutOrStdout().Write(pipeline)

return err
}
142 changes: 142 additions & 0 deletions internal/cmdexport/cmdexport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdexport

import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"gotest.tools/assert"
)

var tempDir, _ = ioutil.TempDir("", "kpt-fn-export-test")

type TestCase struct {
description string
params []string
expected string
err string
}

var testCases = []TestCase{
{
description: "fails on an unsupported orchestrator",
params: []string{"random-orchestrator", "."},
err: "unsupported orchestrator random-orchestrator",
},
{
description: "exports a GitHub Actions pipeline",
params: []string{"github-actions", "."},
expected: `
name: kpt
on:
push:
branches:
- master
jobs:
Kpt:
runs-on: ubuntu-latest
steps:
- name: Run all kpt functions
uses: docker://gongpu/kpt:latest
with:
args: fn run .
`,
},
{
description: "exports a GitHub Actions pipeline with --output",
params: []string{
"github-actions",
".",
"--output",
filepath.Join(tempDir, "main.yaml"),
},
expected: `
name: kpt
on:
push:
branches:
- master
jobs:
Kpt:
runs-on: ubuntu-latest
steps:
- name: Run all kpt functions
uses: docker://gongpu/kpt:latest
with:
args: fn run .
`,
},
{
description: "exports a GitHub Actions pipeline with --fn-path",
params: []string{"github-actions", ".", "--fn-path", "functions/"},
expected: `
name: kpt
on:
push:
branches:
- master
jobs:
Kpt:
runs-on: ubuntu-latest
steps:
- name: Run all kpt functions
uses: docker://gongpu/kpt:latest
with:
args: fn run . --fn-path functions/
`,
},
}

func TestCmdExport(t *testing.T) {
for i := range testCases {
testCase := testCases[i]

t.Run(testCase.description, func(t *testing.T) {
r := GetExportRunner()
r.Command.SetArgs(testCase.params)

b := &bytes.Buffer{}
// out will be overridden during execution if OutputFilePath is present.
r.Command.SetOut(b)

err := r.Command.Execute()

if testCase.err != "" {
assert.Error(t, err, testCase.err)
} else {
assert.NilError(t, err)

expected := strings.TrimLeft(testCase.expected, "\n")
var actual string
if r.OutputFilePath == "" {
actual = b.String()
} else {
content, _ := ioutil.ReadFile(r.OutputFilePath)

actual = string(content)
}

assert.Equal(t, expected, actual)
}
})
}

_ = os.RemoveAll(tempDir)
}
89 changes: 89 additions & 0 deletions internal/cmdexport/orchestrators/githubactions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package orchestrators

import (
"fmt"
"strings"

"github.com/GoogleContainerTools/kpt/internal/cmdexport/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// Represent a GitHub Actions workflow.
// @see https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
type GitHubActions struct {
Name string `yaml:",omitempty"`
On map[string]GitHubActionsTrigger `yaml:",omitempty"`
Jobs map[string]GitHubActionsJob `yaml:",omitempty"`
}

type GitHubActionsTrigger struct {
Branches []string `yaml:",omitempty"`
}

type GitHubActionsJob struct {
RunsOn string `yaml:"runs-on,omitempty"`
Steps []GitHubActionsStep `yaml:",omitempty"`
}

type GitHubActionsStep struct {
Name string `yaml:",omitempty"`
Uses string `yaml:",omitempty"`
With GitHubActionStepArgs `yaml:",omitempty"`
}

type GitHubActionStepArgs struct {
Args string `yaml:",omitempty"`
}

func (p *GitHubActions) Init(config *types.PipelineConfig) Pipeline {
var runFnCommand = fmt.Sprintf("fn run %s", config.Dir)

if fnPaths := config.FnPaths; len(fnPaths) > 0 {
runFnCommand = fmt.Sprintf(
"%s --fn-path %s",
runFnCommand,
strings.Join(fnPaths, " "),
)
}

p.Name = "kpt"
p.On = map[string]GitHubActionsTrigger{
"push": {Branches: []string{"master"}},
}
p.Jobs = map[string]GitHubActionsJob{
"Kpt": {
RunsOn: "ubuntu-latest",
Steps: []GitHubActionsStep{
{
Name: "Run all kpt functions",
Uses: "docker://" + KptImage,
With: GitHubActionStepArgs{
Args: runFnCommand,
},
},
},
},
}

return p
}

func (p *GitHubActions) Generate() []byte {
data, _ := yaml.Marshal(p)

return data
}
26 changes: 26 additions & 0 deletions internal/cmdexport/orchestrators/pipeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package orchestrators

import "github.com/GoogleContainerTools/kpt/internal/cmdexport/types"

// TODO: update
const KptImage = "gongpu/kpt:latest"

// Pipeline is an abstraction of different workflow orchestrators.
type Pipeline interface {
Init(config *types.PipelineConfig) Pipeline
Generate() []byte
}
21 changes: 21 additions & 0 deletions internal/cmdexport/types/PipelineConfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

// Configuration of a pipeline.
type PipelineConfig struct {
Dir string
FnPaths []string
}
Loading

0 comments on commit 631e977

Please sign in to comment.