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

Commit

Permalink
fix: Handling of directories in the init container (#309)
Browse files Browse the repository at this point in the history
* fix: Handling of directories in the init container
* feat: Flush logs of nginx in integration tests
  • Loading branch information
Raffy23 authored Jul 8, 2022
1 parent 5187bd9 commit 7bd83dc
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 31 deletions.
13 changes: 9 additions & 4 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,21 @@ jobs:
- name: Dump k8s debug info
if: always()
run: |
# Force a flush on the nginx logs
kubectl -n keptn exec deployments/api-gateway-nginx -- kill -USR1 1
sync
# Create debug archive
mkdir k8s_debug
kubectl describe nodes > k8s_debug/k8s_describe_nodes.txt
kubectl cluster-info dump > k8s_debug/k8s_cluster_info_dump.txt
kubectl get all -n keptn -o json > k8s_debug/k8s_keptn_objects.json
kubectl get configmaps,deployments,pods,networkpolicy,serviceaccounts,role,rolebindings,events -n ${JES_NAMESPACE} -o json > k8s_debug/k8s_jes_objects.json
kubectl logs -n keptn -l app.kubernetes.io/instance=keptn --prefix=true --previous=false --all-containers > k8s_debug/k8s_keptn_logs.txt || true
kubectl logs -n ${JES_NAMESPACE} deployment/job-executor-service --prefix=true --previous=false --all-containers > k8s_debug/k8s_jes_logs.txt || true
kubectl logs deployment/keptn-gitea-provisioner-service --prefix=true --previous=false --all-containers > k8s_debug/k8s_gitea_provisioner_logs.txt || true
kubectl logs -n keptn -l app.kubernetes.io/instance=keptn --prefix=true --previous=false --all-containers --tail=-1 > k8s_debug/k8s_keptn_logs.txt || true
kubectl logs -n ${JES_NAMESPACE} deployment/job-executor-service --prefix=true --previous=false --all-containers --tail=-1 > k8s_debug/k8s_jes_logs.txt || true
kubectl logs deployment/keptn-gitea-provisioner-service --prefix=true --previous=false --all-containers --tail=-1 > k8s_debug/k8s_gitea_provisioner_logs.txt || true
kubectl get statefulsets,configmaps,pods,networkpolicy,serviceaccounts,role,rolebindings,events,services -n ${GITEA_NAMESPACE} -o json > k8s_debug/k8s_objects_gitea.json
kubectl logs statefulsets/gitea --prefix=true --previous=false --all-containers -n ${GITEA_NAMESPACE} > k8s_debug/k8s_logs_gitea.txt || true
kubectl logs statefulsets/gitea --prefix=true --previous=false --all-containers -n ${GITEA_NAMESPACE} --tail=-1 > k8s_debug/k8s_logs_gitea.txt || true
# Upload the k8s debug archive, so we can use it for investigating
- name: Upload k8s debug archive
Expand Down
8 changes: 3 additions & 5 deletions pkg/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ func MountFiles(actionName string, taskName string, fs afero.Fs, configService k
}

for _, resourcePath := range task.Files {
fileNotFound := true

allServiceResources, err := configService.GetAllKeptnResources(fs, resourcePath)
if err != nil {
return fmt.Errorf("could not retrieve resources for task '%v': %v", taskName, err)
}

// If the given resource is a folder, all files contained in the folder have to be copied over
// to the filesystem of the workload
for resourceURI, resourceContent := range allServiceResources {

// Our mount starts with /keptn
dir := filepath.Join("/keptn", filepath.Dir(resourceURI))
fullFilePath := filepath.Join("/keptn", resourceURI)
Expand Down Expand Up @@ -70,10 +69,9 @@ func MountFiles(actionName string, taskName string, fs afero.Fs, configService k
}

log.Printf("successfully moved file %s to %s", resourceURI, fullFilePath)
fileNotFound = false
}

if fileNotFound {
if len(allServiceResources) == 0 {
return fmt.Errorf("could not find file or directory %s for task %s", resourcePath, taskName)
}
}
Expand Down
44 changes: 30 additions & 14 deletions pkg/keptn/config_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ func (k *configServiceImpl) GetKeptnResource(fs afero.Fs, resource string) ([]by
scope.Project(k.eventProperties.Project)
scope.Stage(k.eventProperties.Stage)
scope.Service(k.eventProperties.Service)
scope.Resource(resource)

// NOTE: No idea why, but the API requires a double query escape for a path element and does not accept leading /
// while emitting absolute paths in the response ...
scope.Resource(url.QueryEscape(strings.TrimPrefix(resource, "/")))

options := api.ResourcesGetResourceOptions{}
if k.eventProperties.GitCommitID != "" {
Expand Down Expand Up @@ -97,35 +100,48 @@ func (k *configServiceImpl) GetAllKeptnResources(fs afero.Fs, resource string) (
return k.getKeptnResourcesFromLocal(fs, resource)
}

keptnResources := make(map[string][]byte)

// Check for an exact match in the resources
keptnResourceContent, err := k.GetKeptnResource(fs, resource)
if err == nil {
keptnResources[resource] = keptnResourceContent
return keptnResources, nil
}

// NOTE:
// Since no exact file has been found, we have to assume that the given resource is a directory.
// Directories don't really exist in the API, so we have to use a HasPrefix match here

scope := api.NewResourceScope()
scope.Project(k.eventProperties.Project)
scope.Stage(k.eventProperties.Stage)
scope.Service(k.eventProperties.Service)

// Get all resources from Keptn in the current service
// Get all files from Keptn to enumerate what is in the directory
requestedResources, err := k.resourceHandler.GetAllServiceResources(context.Background(),
k.eventProperties.Project, k.eventProperties.Stage, k.eventProperties.Service,
api.ResourcesGetAllServiceResourcesOptions{},
)
if err != nil {
return nil, fmt.Errorf("resources not found: %s", err)
return nil, fmt.Errorf("unable to list all resources: %w", err)
}

// Create a path from the / and append a / to the end to match only files in that directory
resourceDirectoryName := resource + "/"
if !strings.HasPrefix(resourceDirectoryName, "/") {
resourceDirectoryName = "/" + resourceDirectoryName
}

// Go over the resources and fetch the content of each resource with the given gitCommitID:
keptnResources := make(map[string][]byte)
for _, serviceResource := range requestedResources {
// match against with and without starting slash
// Note: this makes it possible to include directories, maybe a glob might be a better idea
resourceURIWithoutSlash := strings.Replace(*serviceResource.ResourceURI, "/", "", 1)
if strings.HasPrefix(*serviceResource.ResourceURI, resource) || strings.HasPrefix(
resourceURIWithoutSlash, resource,
) {
if strings.HasPrefix(*serviceResource.ResourceURI, resourceDirectoryName) {

// Query resource with the specified git commit id:
keptnResourceContent, err := k.GetKeptnResource(fs, *serviceResource.ResourceURI)
if err != nil {
return nil, fmt.Errorf("could not find file %s for version %s",
*serviceResource.ResourceURI, k.eventProperties.GitCommitID,
)
return nil, fmt.Errorf("unable to fetch resource %s: %w", *serviceResource.ResourceURI, err)
}

keptnResources[*serviceResource.ResourceURI] = keptnResourceContent
}
}
Expand Down
43 changes: 35 additions & 8 deletions pkg/keptn/config_service_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package keptn

import (
api "github.com/keptn/go-utils/pkg/api/utils/v2"
"fmt"
"net/url"
"path"
"strings"
"testing"

"github.com/keptn/go-utils/pkg/api/models"
api "github.com/keptn/go-utils/pkg/api/utils/v2"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

keptnfake "keptn-contrib/job-executor-service/pkg/keptn/fake"

"github.com/golang/mock/gomock"
"github.com/keptn/go-utils/pkg/api/models"
"github.com/spf13/afero"
)

Expand All @@ -27,8 +30,9 @@ const stage = "dev"
const gitCommitID = "6caf78d2c978f7f787"

func TestGetAllKeptnResources(t *testing.T) {
locustBasic := "locust/basic.py"
locustFunctional := "locust/functional.py"
locustBasic := "/locust/basic.py"
locustFunctional := "/locust/functional.py"
ignoredFile := "/locustIGNORED"

resourceHandlerMock := CreateResourceHandlerMock(t)
resourceHandlerMock.EXPECT().GetAllServiceResources(gomock.Any(), project, stage, service, gomock.Any()).Times(1).Return(
Expand All @@ -43,14 +47,29 @@ func TestGetAllKeptnResources(t *testing.T) {
ResourceContent: "",
ResourceURI: &locustFunctional,
},
{
Metadata: nil,
ResourceContent: "",
ResourceURI: &ignoredFile,
},
}, nil,
)

folderScope := api.NewResourceScope()
folderScope.Project(project)
folderScope.Stage(stage)
folderScope.Service(service)
folderScope.Resource("locust")

resourceHandlerMock.EXPECT().GetResource(gomock.Any(), *folderScope, gomock.Any()).Times(1).Return(
nil, fmt.Errorf("internal Server Error - Not a file"),
)

scope1 := api.NewResourceScope()
scope1.Project(project)
scope1.Stage(stage)
scope1.Service(service)
scope1.Resource(locustBasic)
scope1.Resource(url.QueryEscape(strings.TrimPrefix(locustBasic, "/")))

resourceHandlerMock.EXPECT().GetResource(gomock.Any(), *scope1, gomock.Any()).Times(1).Return(
&models.Resource{
Expand All @@ -64,7 +83,7 @@ func TestGetAllKeptnResources(t *testing.T) {
scope2.Project(project)
scope2.Stage(stage)
scope2.Service(service)
scope2.Resource(locustFunctional)
scope2.Resource(url.QueryEscape(strings.TrimPrefix(locustFunctional, "/")))

resourceHandlerMock.EXPECT().GetResource(gomock.Any(), *scope2, gomock.Any()).Times(1).Return(
&models.Resource{
Expand All @@ -74,6 +93,14 @@ func TestGetAllKeptnResources(t *testing.T) {
}, nil,
)

ignoredFileScope := api.NewResourceScope()
ignoredFileScope.Project(project)
ignoredFileScope.Stage(stage)
ignoredFileScope.Service(service)
ignoredFileScope.Resource(url.QueryEscape(strings.TrimPrefix(locustFunctional, "/")))

resourceHandlerMock.EXPECT().GetResource(gomock.Any(), *ignoredFileScope, gomock.Any()).Times(0)

event := EventProperties{
Project: project,
Stage: stage,
Expand All @@ -89,11 +116,11 @@ func TestGetAllKeptnResources(t *testing.T) {

val, ok := keptnResources[locustBasic]
assert.True(t, ok)
assert.Equal(t, string(val), locustBasic)
assert.Equal(t, locustBasic, string(val))

val, ok = keptnResources[locustFunctional]
assert.True(t, ok)
assert.Equal(t, string(val), locustFunctional)
assert.Equal(t, locustFunctional, string(val))
}

func TestGetAllKeptnResourcesLocal(t *testing.T) {
Expand Down
15 changes: 15 additions & 0 deletions test/data/e2e/folder.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v2
actions:
- name: "Files e2e test"
events:
- name: "sh.keptn.event.deployment.triggered"
tasks:
- name: "Print sha1sum of files"
image: "alpine"
files:
- helm
cmd:
- sh
args:
- "-c"
- 'find /keptn -type f -name "*" -exec sha1sum {} \;'
97 changes: 97 additions & 0 deletions test/e2e/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,103 @@ func TestResourceFiles(t *testing.T) {
)
}

func TestResourceFolder(t *testing.T) {
if !isE2ETestingAllowed() {
t.Skipf("Skipping %s, not allowed by environment\n", t.Name())
}

testEnv, err := newTestEnvironment(
"../events/e2e/folder.triggered.json",
"../shipyard/e2e/folder.deployment.yaml",
"../data/e2e/folder.config.yaml",
)

require.NoError(t, err)

err = testEnv.SetupTestEnvironment()
require.NoError(t, err)

// Make sure project is delete after the tests are completed
defer testEnv.Cleanup()

// Generate and upload some resource files:
files := map[string]randomResourceFile{
"small.file": newRandomResourceFile(t, 32),
"helm/chart.archive": newRandomResourceFile(t, 32),
"helm/secrets.txt": newRandomResourceFile(t, 32),
"helm/values.yaml": newRandomResourceFile(t, 32),
"helm-abc": newRandomResourceFile(t, 32),
"helmDEFG": newRandomResourceFile(t, 32),
}

for path, file := range files {
err := testEnv.API.AddServiceResource(testEnv.EventData.Project, testEnv.EventData.Stage,
testEnv.EventData.Service, path, file.content)

require.NoErrorf(t, err, "unable to create file %s with %d bytes", path, file.size)
}

// Send the event to keptn
keptnContext, err := testEnv.API.SendEvent(testEnv.Event)
require.NoError(t, err)

// Checking if the job executor service responded with a .started event
requireWaitForEvent(t,
testEnv.API,
2*time.Minute,
1*time.Second,
keptnContext,
"sh.keptn.event.deployment.started",
func(_ *models.KeptnContextExtendedCE) bool {
return true
},
)

// If the started event was sent by the job executor we wait for a .finished with the following data:
expectedEventData := eventData{
Project: testEnv.EventData.Project,
Result: "pass",
Service: testEnv.EventData.Service,
Stage: testEnv.EventData.Stage,
Status: "succeeded",
}

filesRegex, err := regexp.Compile(`(?P<hash>[a-f0-9]+) {2}/keptn/(?P<file>[/a-z.]+)\n`)

requireWaitForEvent(t,
testEnv.API,
2*time.Minute,
1*time.Second,
keptnContext,
"sh.keptn.event.deployment.finished",
func(event *models.KeptnContextExtendedCE) bool {
responseEventData, err := parseKeptnEventData(event)
require.NoError(t, err)

// Gather all files from the log output
matches := filesRegex.FindAllStringSubmatch(responseEventData.Message, -1)
foundFiles := make(map[string]string)
for _, match := range matches {
foundFiles[match[2]] = match[1]
}

// transform the expected files into the same format
expectedFiles := map[string]string{
"helm/chart.archive": files["helm/chart.archive"].sha1,
"helm/secrets.txt": files["helm/secrets.txt"].sha1,
"helm/values.yaml": files["helm/values.yaml"].sha1,
}

responseEventData.Message = ""

// Assert that logging content and response data is as we expect
assert.Equal(t, expectedFiles, foundFiles)
assert.Equal(t, expectedEventData, *responseEventData)
return true
},
)
}

type randomResourceFile struct {
size int
content string
Expand Down
10 changes: 10 additions & 0 deletions test/events/e2e/folder.triggered.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"data": {
"project": "e2e-project",
"service": "e2e-service",
"stage": "e2e"
},
"source": "e2e-test",
"specversion": "1.0",
"type": "sh.keptn.event.e2e.jes.triggered"
}
11 changes: 11 additions & 0 deletions test/shipyard/e2e/folder.deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: "spec.keptn.sh/0.2.2"
kind: "Shipyard"
metadata:
name: "e2e-deployment-shipyard"
spec:
stages:
- name: "e2e"
sequences:
- name: "jes"
tasks:
- name: "deployment"

0 comments on commit 7bd83dc

Please sign in to comment.