Skip to content
This repository has been archived by the owner on Jan 19, 2024. It is now read-only.

[WIP] Feature/poc GitHub actions #69

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/job-executor-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type envConfig struct {
DefaultResourceRequestsMemory string `envconfig:"DEFAULT_RESOURCE_REQUESTS_MEMORY"`
// Respond with .finished event if no configuration found
AlwaysSendFinishedEvent string `envconfig:"ALWAYS_SEND_FINISHED_EVENT"`
// Container Registry
ContainerRegistry string `envconfig:"CONTAINER_REGISTRY"`
}

// ServiceName specifies the current services name (e.g., used as source when sending CloudEvents)
Expand Down Expand Up @@ -104,6 +106,7 @@ func processKeptnCloudEvent(ctx context.Context, event cloudevents.Event) error
InitContainerImage: env.InitContainerImage,
DefaultResourceRequirements: DefaultResourceRequirements,
AlwaysSendFinishedEvent: false,
ContainerRegistry: env.ContainerRegistry,
},
}

Expand Down
44 changes: 44 additions & 0 deletions deploy/registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: registry
name: registry
spec:
replicas: 1
selector:
matchLabels:
app: registry
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: registry
spec:
containers:
- image: registry:2
name: registry
ports:
- containerPort: 5000
resources: {}
status: {}

---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: registry
name: registry
spec:
ports:
- port: 5000
protocol: TCP
targetPort: 5000
selector:
app: registry
status:
loadBalancer: {}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4 // indirect
golang.org/x/tools v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gotest.tools v2.2.0+incompatible
honnef.co/go/tools v0.1.3 // indirect
k8s.io/api v0.22.0
Expand Down
25 changes: 20 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"fmt"
"gopkg.in/yaml.v2"
"keptn-sandbox/job-executor-service/pkg/github/model"
"regexp"

"github.com/PaesslerAG/jsonpath"
Expand All @@ -18,10 +19,11 @@ type Config struct {

// Action contains a action within the config which needs to be triggered
type Action struct {
Name string `yaml:"name"`
Events []Event `yaml:"events"`
Tasks []Task `yaml:"tasks"`
Silent bool `yaml:"silent"`
Name string `yaml:"name"`
Events []Event `yaml:"events"`
Tasks []Task `yaml:"tasks"`
Silent bool `yaml:"silent"`
Steps []model.Step `yaml:"steps"`
}

// Event defines a keptn event which determines if an Action should be triggered
Expand Down Expand Up @@ -85,7 +87,20 @@ func NewConfig(yamlContent []byte) (*Config, error) {
return nil, fmt.Errorf("apiVersion %v is not supported, use %v", *config.APIVersion, supportedAPIVersion)
}

return &config, nil
err = config.checkConfigs()

return &config, err
}

func (c *Config) checkConfigs() error {

// check if the actions don't contain steps and tasks -> that should not be valid
for _, action := range c.Actions {
if len(action.Steps) > 0 && len(action.Tasks) > 0 {
return fmt.Errorf("action %v contains steps and tasks", action.Name)
}
}
return nil
}

// IsEventMatch indicated whether a given event matches the config
Expand Down
7 changes: 7 additions & 0 deletions pkg/eventhandler/eventhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,10 @@ func TestStartK8sJobSilent(t *testing.T) {
err = fakeEventSender.AssertSentEventTypes([]string{})
assert.NilError(t, err)
}

func TestGetGithubProjectName(t *testing.T) {

eh := EventHandler{}
assert.Equal(t, "aquasecurity/trivy-action", eh.getGithubProjectName("aquasecurity/trivy-action@master"))
assert.Equal(t, "aquasecurity/trivy-action", eh.getGithubProjectName("aquasecurity/trivy-action"))
}
77 changes: 77 additions & 0 deletions pkg/eventhandler/eventhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"encoding/json"
"fmt"
"keptn-sandbox/job-executor-service/pkg/config"
"keptn-sandbox/job-executor-service/pkg/github"
"keptn-sandbox/job-executor-service/pkg/k8sutils"
"log"
"math"
"strconv"
"strings"

cloudevents "github.com/cloudevents/sdk-go/v2" // make sure to use v2 cloudevents here
keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"
Expand Down Expand Up @@ -75,11 +77,86 @@ func (eh *EventHandler) HandleEvent() error {
log.Printf("Match found for event %s of type %s. Starting k8s job to run action '%s'", eh.Event.Context.GetID(), eh.Event.Type(), action.Name)

k8s := k8sutils.NewK8s(eh.JobSettings.JobNamespace)
err = eh.handleGithubAction(k8s, configuration)
if err != nil {
log.Printf("An error occurred while handling GitHub action: %s", err)
return err
}

eh.startK8sJob(k8s, action, eventAsInterface)

return nil
}

func (eh *EventHandler) handleGithubAction(k8s k8sutils.K8s, configuration *config.Config) error {

_, err := eh.Keptn.SendTaskStartedEvent(eh.EventData, eh.ServiceName)
if err != nil {
return fmt.Errorf("Error while sending started event: %s\n", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[golint] reported by reviewdog 🐶
error strings should not be capitalized or end with punctuation or a newline

}

err = k8s.ConnectToCluster()
if err != nil {
return fmt.Errorf("Error while connecting to cluster: %s\n", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[golint] reported by reviewdog 🐶
error strings should not be capitalized or end with punctuation or a newline

sendTaskFailedEvent(eh.Keptn, "", eh.ServiceName, err, "")
}

for _, action := range configuration.Actions {
for _, step := range action.Steps {
githubProjectName := eh.getGithubProjectName(step.Uses)

// TODO call the functionality from Thomas (build and push image)
imageLocation, err := k8s.CreateImageBuilder(step.Name, step, eh.JobSettings.ContainerRegistry)
if err != nil {
return err
}

defer func() {
err = k8s.DeleteK8sJob(step.Name)
if err != nil {
log.Printf("Error while deleting job: %s\n", err.Error())
}
}()

jobErr := k8s.AwaitK8sJobDone(step.Name, defaultMaxPollCount, pollIntervalInSeconds)
if jobErr != nil {
log.Println(err)
}

err, githubAction := github.GetActionYaml(githubProjectName)
if err != nil {
return err
}

args, err := github.PrepareArgs(step.With, githubAction.Inputs, githubAction.Runs.Args)
if err != nil {
return err
}

task := config.Task{
Name: githubAction.Name,
Files: nil,
Image: imageLocation,
Cmd: nil,
Args: args,
Env: nil,
}

action.Tasks = append(action.Tasks, task)
}
}
return nil
}

func (eh *EventHandler) getGithubProjectName(uses string) string {
githubProjectName := uses
index := strings.LastIndex(githubProjectName, "@")
if index > 0 {
githubProjectName = githubProjectName[:index]
}
return githubProjectName
}

func (eh *EventHandler) createEventPayloadAsInterface() (map[string]interface{}, error) {

var eventDataAsInterface interface{}
Expand Down
39 changes: 39 additions & 0 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package github
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[gofmt] reported by reviewdog 🐶
file pkg/github/github.go is not gofmted


import (
"fmt"
"keptn-sandbox/job-executor-service/pkg/github/model"
"log"
"strings"
)

func PrepareArgs(with map[string]string, inputs map[string]model.Input, args []string) ([]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[golint] reported by reviewdog 🐶
exported function PrepareArgs should have comment or be unexported

var filledArgs []string

for inputKey, inputValue := range inputs {
argKey := fmt.Sprintf("inputs.%s", inputKey)
log.Printf("argKey: %v", argKey)

for _, arg := range args {
if strings.Contains(arg, argKey) {
log.Printf("matched argKey: %v", argKey)

argValue := inputValue.Default
if withValue, ok := with[inputKey]; ok {
argValue = withValue
} else {
if inputValue.Required {
return nil, fmt.Errorf("required input '%s' not provided", inputKey)
}
}

splittedArg := strings.Split(arg, "$")
arg := strings.TrimSpace(splittedArg[0])
filledArgs = append(filledArgs, arg)
filledArgs = append(filledArgs, argValue)
}
}
}

return filledArgs, nil
}
43 changes: 43 additions & 0 deletions pkg/github/github_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package github
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[gofmt] reported by reviewdog 🐶
file pkg/github/github_test.go is not gofmted


import (
"gotest.tools/assert"
"keptn-sandbox/job-executor-service/pkg/github/model"
"log"
"testing"
)

func TestPrepareArgs(t *testing.T) {
with := map[string]string{"scan-type": "banana", "format": "cucumber"}
inputs := map[string]model.Input{
"scan-type": {
Required: false,
Default: "",
},
"format": {
Required: false,
Default: "",
},
"template": {
Required: false,
Default: "table",
},
}
args := []string{"-a ${{ inputs.scan-type }}", "-b ${{ inputs.format }}", "-c ${{ inputs.template }}"}
k8sArgs, err := PrepareArgs(with, inputs, args)
assert.NilError(t, err)
log.Printf("%v", k8sArgs)
}

func TestPrepareArgs_RequiredInput(t *testing.T) {
with := map[string]string{}
inputs := map[string]model.Input{
"scan-type": {
Required: true,
Default: "",
},
}
args := []string{"-a ${{ inputs.scan-type }}"}
_, err := PrepareArgs(with, inputs, args)
assert.Error(t, err, "required input 'scan-type' not provided")
}
63 changes: 63 additions & 0 deletions pkg/github/model/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package model
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[gofmt] reported by reviewdog 🐶
file pkg/github/model/action.go is not gofmted


import (
"gopkg.in/yaml.v3"
"io"
)

// ActionRunsUsing is the type of runner for the action
type ActionRunsUsing string

const (
// ActionRunsUsingNode12 for running with node12
ActionRunsUsingNode12 = "node12"
// ActionRunsUsingDocker for running with docker
ActionRunsUsingDocker = "docker"
// ActionRunsUsingComposite for running composite
ActionRunsUsingComposite = "composite"
)

// ActionRuns are a field in Action
type ActionRuns struct {
Using ActionRunsUsing `yaml:"using"`
Env map[string]string `yaml:"env"`
Main string `yaml:"main"`
Image string `yaml:"image"`
Entrypoint []string `yaml:"entrypoint"`
Args []string `yaml:"args"`
Steps []Step `yaml:"steps"`
}

// Action describes a metadata file for GitHub actions. The metadata filename must be either action.yml or action.yaml. The data in the metadata file defines the inputs, outputs and main entrypoint for your action.
type Action struct {
Name string `yaml:"name"`
Author string `yaml:"author"`
Description string `yaml:"description"`
Inputs map[string]Input `yaml:"inputs"`
Outputs map[string]Output `yaml:"outputs"`
Runs ActionRuns `yaml:"runs"`
Branding struct {
Color string `yaml:"color"`
Icon string `yaml:"icon"`
} `yaml:"branding"`
}

// Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids.
type Input struct {
Description string `yaml:"description"`
Required bool `yaml:"required"`
Default string `yaml:"default"`
}

// Output parameters allow you to declare data that an action sets. Actions that run later in a workflow can use the output data set in previously run actions. For example, if you had an action that performed the addition of two inputs (x + y = z), the action could output the sum (z) for other actions to use as an input.
type Output struct {
Description string `yaml:"description"`
Value string `yaml:"value"`
}

// ReadAction reads an action from a reader
func ReadAction(in io.Reader) (*Action, error) {
a := new(Action)
err := yaml.NewDecoder(in).Decode(a)
return a, err
}
Loading