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

feat: Add global and stage job configuration lookup #338

Merged
merged 5 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

- [Features](#features)
- [Getting started](#getting-started)
- [Job Configuration](#job-configuration)
- [Specifying the working directory](#specifying-the-working-directory)
- [Event Matching](#event-matching)
- [Kubernetes Job](#kubernetes-job)
Expand Down Expand Up @@ -35,6 +36,30 @@

To get started with job-executor-service, please follow the [Quickstart](../README.md#quickstart).

### Job Configuration

The job-executor-service can be configured with the `job/config.yaml` configuration file, which should be uploaded as
resource to Keptn. The job-executor-service will scan the resources in the following order to determine which configuration
should be used:
- **Service**: A resource that is stored on the service level will always used by the job executor service if available
- **Stage**: If no service resource is found, the stage resources are searched for a job configuration
- **Project**: If no other resources are found the job executor will fallback to a project wide configuration
*(Note: the latest version of the project wide configuration file will be fetched!)*

If the job executor service can't find a configuration file, it will respond with an error event, which can be viewed
in the uniform page of the Keptn bridge.

A typical job configuration usually contains one or more actions that will be triggered when a specific event is
received:
```yaml
apiVersion: v2
actions:
- name: "Print files"
events:
- name: "sh.keptn.event.sample.triggered"
tasks:
- ...
```

### Specifying the working directory

Expand Down
42 changes: 36 additions & 6 deletions pkg/config/fake/reader_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 31 additions & 4 deletions pkg/config/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,51 @@ const jobConfigResourceName = "job/config.yaml"

//go:generate mockgen -destination=fake/reader_mock.go -package=fake . KeptnResourceService

// KeptnResourceService defines the contract used by JobConfigReader to retrieve a resource from keptn (using project,
// service, stage from context)
// KeptnResourceService defines the contract used by JobConfigReader to retrieve a resource from Keptn
// The project, service, stage environment variables are taken from the context of the ResourceService (Event)
type KeptnResourceService interface {
GetResource(resource string, gitCommitID string) ([]byte, error)
// GetServiceResource returns the service level resource
GetServiceResource(resource string, gitCommitID string) ([]byte, error)

// GetProjectResource returns the resource that was defined on project level
GetProjectResource(resource string, gitCommitID string) ([]byte, error)

// GetStageResource returns the resource that was defined in the stage
GetStageResource(resource string, gitCommitID string) ([]byte, error)
}

// JobConfigReader retrieves and parses job configuration from Keptn
type JobConfigReader struct {
Keptn KeptnResourceService
}

// FindJobConfigResource searches for the job configuration resource in the service, stage and then the project
// and returns the content of the first resource that is found
func (jcr *JobConfigReader) FindJobConfigResource(gitCommitID string) ([]byte, error) {
if config, err := jcr.Keptn.GetServiceResource(jobConfigResourceName, gitCommitID); err == nil {
return config, nil
}

if config, err := jcr.Keptn.GetStageResource(jobConfigResourceName, gitCommitID); err == nil {
return config, nil
}

// NOTE: Since the resource service uses different branches, the commitID may not be in the main
// branch and therefore it's not possible to query the project fallback configuration!
if config, err := jcr.Keptn.GetProjectResource(jobConfigResourceName, ""); err == nil {
return config, nil
}

return nil, fmt.Errorf("unable to find job configuration")
}

// GetJobConfig retrieves job/config.yaml resource from keptn and parses it into a Config struct.
// Additionally, also the SHA1 hash of the retrieved configuration will be returned.
// In case of error retrieving the resource or parsing the yaml it will return (nil,
// error) with the original error correctly wrapped in the local one
func (jcr *JobConfigReader) GetJobConfig(gitCommitID string) (*Config, string, error) {

resource, err := jcr.Keptn.GetResource(jobConfigResourceName, gitCommitID)
resource, err := jcr.FindJobConfigResource(gitCommitID)
if err != nil {
return nil, "", fmt.Errorf("error retrieving job config: %w", err)
}
Expand Down
106 changes: 102 additions & 4 deletions pkg/config/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"fmt"
"testing"

"github.com/golang/mock/gomock"
Expand All @@ -16,12 +17,16 @@ func TestConfigRetrievalFailed(t *testing.T) {

mockKeptnResourceService := fake.NewMockKeptnResourceService(mockCtrl)
retrievalError := errors.New("error getting resource")
mockKeptnResourceService.EXPECT().GetResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(nil, retrievalError)
mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(nil, retrievalError)
mockKeptnResourceService.EXPECT().GetStageResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(nil, retrievalError)

// NOTE: fetching project resources works only without a git commit id, because of branches !
mockKeptnResourceService.EXPECT().GetProjectResource("job/config.yaml", "").Return(nil, retrievalError)

sut := JobConfigReader{Keptn: mockKeptnResourceService}

config, _, err := sut.GetJobConfig("c25692cb4fe4068fbdc2")
assert.ErrorIs(t, err, retrievalError)
assert.Error(t, err)
assert.Nil(t, config)
}

Expand All @@ -35,7 +40,7 @@ func TestMalformedConfig(t *testing.T) {
has_nothing_to_do:
with_job_executor: true
`
mockKeptnResourceService.EXPECT().GetResource("job/config.yaml", "").Return(
mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "").Return(
[]byte(yamlConfig),
nil,
)
Expand Down Expand Up @@ -64,7 +69,7 @@ func TestGetConfigHappyPath(t *testing.T) {
cmd:
- echo "Hello World!"
`
mockKeptnResourceService.EXPECT().GetResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
[]byte(yamlConfig),
nil,
)
Expand All @@ -75,3 +80,96 @@ func TestGetConfigHappyPath(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, config)
}

func TestJobConfigReader_FindJobConfigResource(t *testing.T) {

t.Run("Find in service", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
mockKeptnResourceService := fake.NewMockKeptnResourceService(mockCtrl)

sut := JobConfigReader{Keptn: mockKeptnResourceService}

mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
[]byte("test"),
nil,
)

result, err := sut.FindJobConfigResource("c25692cb4fe4068fbdc2")
assert.NoError(t, err)
assert.Equal(t, result, []byte("test"))
})

t.Run("Find in stage", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
mockKeptnResourceService := fake.NewMockKeptnResourceService(mockCtrl)

sut := JobConfigReader{Keptn: mockKeptnResourceService}

mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
nil,
fmt.Errorf("some error"),
)

mockKeptnResourceService.EXPECT().GetStageResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
[]byte("test1"),
nil,
)

result, err := sut.FindJobConfigResource("c25692cb4fe4068fbdc2")
assert.NoError(t, err)
assert.Equal(t, result, []byte("test1"))
})

t.Run("Find in project", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
mockKeptnResourceService := fake.NewMockKeptnResourceService(mockCtrl)

sut := JobConfigReader{Keptn: mockKeptnResourceService}

mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
nil,
fmt.Errorf("some error"),
)

mockKeptnResourceService.EXPECT().GetStageResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
nil,
fmt.Errorf("some error"),
)

mockKeptnResourceService.EXPECT().GetProjectResource("job/config.yaml", "").Return(
[]byte("abc"),
nil,
)

result, err := sut.FindJobConfigResource("c25692cb4fe4068fbdc2")
assert.NoError(t, err)
assert.Equal(t, result, []byte("abc"))
})

t.Run("Not job config", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
mockKeptnResourceService := fake.NewMockKeptnResourceService(mockCtrl)

sut := JobConfigReader{Keptn: mockKeptnResourceService}

mockKeptnResourceService.EXPECT().GetServiceResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
nil,
fmt.Errorf("some error"),
)

mockKeptnResourceService.EXPECT().GetStageResource("job/config.yaml", "c25692cb4fe4068fbdc2").Return(
nil,
fmt.Errorf("some error"),
)

mockKeptnResourceService.EXPECT().GetProjectResource("job/config.yaml", "").Return(
nil,
fmt.Errorf("some error"),
)

result, err := sut.FindJobConfigResource("c25692cb4fe4068fbdc2")
assert.Error(t, err)
assert.Nil(t, result)
})

}
8 changes: 1 addition & 7 deletions pkg/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package file

import (
"fmt"
"keptn-contrib/job-executor-service/pkg/config"
"keptn-contrib/job-executor-service/pkg/keptn"
"log"
"path/filepath"
Expand All @@ -13,16 +12,11 @@ import (
// MountFiles requests all specified files of a task from the keptn configuration service and copies them to /keptn
func MountFiles(actionName string, taskName string, fs afero.Fs, configService keptn.ConfigService) error {

resource, err := configService.GetKeptnResource(fs, "job/config.yaml")
configuration, err := configService.GetJobConfiguration()
if err != nil {
return fmt.Errorf("could not find config for job-executor-service: %v", err)
}

configuration, err := config.NewConfig(resource)
if err != nil {
return fmt.Errorf("could not parse config: %s", err)
}

found, action := configuration.FindActionByName(actionName)
if !found {
return fmt.Errorf("no action found with name '%s'", actionName)
Expand Down
Loading