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

Commit

Permalink
refactor: Include task names in log output and catch missing service …
Browse files Browse the repository at this point in the history
…account error

Signed-off-by: Raphael Ludwig <[email protected]>
  • Loading branch information
Raffy23 committed May 2, 2022
1 parent f5bec13 commit 7f43233
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 26 deletions.
62 changes: 48 additions & 14 deletions pkg/eventhandler/eventhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"strconv"
"strings"
"time"

"github.com/keptn/go-utils/pkg/lib/keptn"
Expand Down Expand Up @@ -56,6 +57,7 @@ type K8s interface {
jobName string, maxPollDuration time.Duration, pollIntervalInSeconds time.Duration, namespace string,
) error
GetLogsOfPod(jobName string, namespace string) (string, error)
ExistsServiceAccount(saName string, namespace string) bool
}

// EventHandler contains all information needed to process an event
Expand Down Expand Up @@ -147,7 +149,7 @@ func (eh *EventHandler) startK8sJob(action *config.Action, jsonEventData interfa
if err != nil {
log.Printf("Error while connecting to cluster: %s\n", err.Error())
if !action.Silent {
sendTaskFailedEvent(eh.Keptn, "", eh.ServiceName, err, "")
sendJobFailedEvent(eh.Keptn, "", eh.ServiceName, err)
}
return
}
Expand All @@ -157,15 +159,32 @@ func (eh *EventHandler) startK8sJob(action *config.Action, jsonEventData interfa
start: time.Now(),
}

// To execute all tasks atomically, we check all images
// before we start executing a single task of a job
// To execute all tasks atomically, we check all images before we start executing a single task of a job
// Additionally we want to check if the job configuration is sound (like validating the specified serviceAccounts)
for _, task := range action.Tasks {

namespace := eh.JobSettings.JobNamespace
if len(task.Namespace) > 0 {
namespace = task.Namespace
}

if !eh.ImageFilter.IsImageAllowed(task.Image) {
errorText := fmt.Sprintf("Forbidden: Image %s does not match configured image allowlist.\n", task.Image)

log.Printf(errorText)
if !action.Silent {
sendTaskFailedEvent(eh.Keptn, "", eh.ServiceName, errors.New(errorText), "")
sendTaskFailedEvent(eh.Keptn, task.Name, eh.ServiceName, errors.New(errorText), "")
}

return
}

if task.ServiceAccount != nil && !eh.K8s.ExistsServiceAccount(*task.ServiceAccount, namespace) {
errorText := fmt.Sprintf("Error: service account %s does not exist!\n", *task.ServiceAccount)

log.Printf(errorText)
if !action.Silent {
sendTaskFailedEvent(eh.Keptn, task.Name, eh.ServiceName, errors.New(errorText), "")
}

return
Expand All @@ -192,7 +211,7 @@ func (eh *EventHandler) startK8sJob(action *config.Action, jsonEventData interfa
if err != nil {
log.Printf("Error while creating job: %s\n", err)
if !action.Silent {
sendTaskFailedEvent(eh.Keptn, jobName, eh.ServiceName, err, "")
sendTaskFailedEvent(eh.Keptn, task.Name, eh.ServiceName, err, "")
}
return
}
Expand All @@ -211,14 +230,14 @@ func (eh *EventHandler) startK8sJob(action *config.Action, jsonEventData interfa
if jobErr != nil {
log.Printf("Error while creating job: %s\n", jobErr.Error())
if !action.Silent {
sendTaskFailedEvent(eh.Keptn, jobName, eh.ServiceName, jobErr, logs)
sendTaskFailedEvent(eh.Keptn, task.Name, eh.ServiceName, jobErr, logs)
}
return
}

allJobLogs = append(
allJobLogs, jobLogs{
name: jobName,
name: task.Name,
logs: logs,
},
)
Expand All @@ -233,13 +252,13 @@ func (eh *EventHandler) startK8sJob(action *config.Action, jsonEventData interfa
}
}

func sendTaskFailedEvent(myKeptn *keptnv2.Keptn, jobName string, serviceName string, err error, logs string) {
func sendTaskFailedEvent(myKeptn *keptnv2.Keptn, taskName string, serviceName string, err error, logs string) {
var message string

if logs != "" {
message = fmt.Sprintf("Job %s failed: %s\n\nLogs: \n%s", jobName, err, logs)
message = fmt.Sprintf("Task '%s' failed: %s\n\nLogs: \n%s", taskName, err.Error(), logs)
} else {
message = fmt.Sprintf("Job %s failed: %s", jobName, err)
message = fmt.Sprintf("Task '%s' failed: %s", taskName, err.Error())
}

_, err = myKeptn.SendTaskFinishedEvent(
Expand All @@ -255,18 +274,33 @@ func sendTaskFailedEvent(myKeptn *keptnv2.Keptn, jobName string, serviceName str
}
}

func sendJobFailedEvent(myKeptn *keptnv2.Keptn, jobName string, serviceName string, err error) {
_, err = myKeptn.SendTaskFinishedEvent(
&keptnv2.EventData{
Status: keptnv2.StatusErrored,
Result: keptnv2.ResultFailed,
Message: fmt.Sprintf("Job %s failed: %s", jobName, err.Error()),
}, serviceName,
)

if err != nil {
log.Printf("Error while sending started event: %s\n", err)
}
}

func sendTaskFinishedEvent(myKeptn *keptnv2.Keptn, serviceName string, jobLogs []jobLogs, data dataForFinishedEvent) {
var message string
var logMessage strings.Builder

for _, jobLogs := range jobLogs {
message += fmt.Sprintf("Job %s finished successfully!\n\nLogs:\n%s\n\n", jobLogs.name, jobLogs.logs)
logMessage.WriteString(
fmt.Sprintf("Task '%s' finished successfully!\n\nLogs:\n%s\n\n", jobLogs.name, jobLogs.logs),
)
}

eventData := &keptnv2.EventData{

Status: keptnv2.StatusSucceeded,
Result: keptnv2.ResultPass,
Message: message,
Message: logMessage.String(),
}

var err error
Expand Down
14 changes: 14 additions & 0 deletions pkg/eventhandler/fake/eventhandlers_mock.go

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

10 changes: 10 additions & 0 deletions pkg/k8sutils/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,13 @@ func (k8s *K8sImpl) generateEnvFromSecret(env config.Env, namespace string) ([]v

return generatedEnv, nil
}

// ExistsServiceAccount returns true of the given service account exists in the namespace
func (k8s *K8sImpl) ExistsServiceAccount(saName string, namespace string) bool {
_, err := k8s.clientset.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), saName, metav1.GetOptions{})
if err != nil {
return false
}

return true
}
25 changes: 13 additions & 12 deletions pkg/k8sutils/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ func (k8s *K8sImpl) GetLogsOfPod(jobName string, namespace string) (string, erro
logsOfContainer = fmt.Sprintf("Unable to query logs of container: %s", err.Error())
}

// Build the final logging output for the container
logs.WriteString(buildLogOutputForContainer(container, logsOfContainer))
logs.WriteString("\n")
// If the container did not put out any logs, we skip it entirely to prevent polluting the
// log output too much by appending a lot of empty lines for each container
if logsOfContainer != "" {
logs.WriteString(buildLogOutputForContainer(pod, container, logsOfContainer))
logs.WriteString("\n")
}
}
}

Expand Down Expand Up @@ -140,27 +143,23 @@ func getTerminatedContainersWithStatusOfPod(pod v1.Pod) []containerStatus {
// format. Depending on the status the output changes slightly (output will be empty of no logs are produced):
//
// - Normal output:
// Container <container.name>:
// Container <container.name> of pod <pod.name>:
// <logsOfContainer>
//
// - In case of an error:
// Container <container.name> terminated with an error (Reason: <reason> [, Message: <message> |, ExitCode: <code>]):
// Container <container.name> of pod <pod.name> terminated with an error (Reason: <reason> [, Message: <message> |, ExitCode: <code>]):
// <logsOfContainer>
//
func buildLogOutputForContainer(container containerStatus, logsOfContainer string) string {
func buildLogOutputForContainer(pod v1.Pod, container containerStatus, logsOfContainer string) string {
var logs strings.Builder

// If the container did not put out any logs, we skip it entirely to prevent polluting the
// log output too much by appending <no logs available for container> for each container
if logsOfContainer == "" {
return ""
}

// Prepend the container name at the beginning, so we are able to separate logs of different containers
// and display a termination error at the beginning, may be more interesting than the logs of the container
if container.status.Reason != "Completed" {
logs.WriteString("Container ")
logs.WriteString(container.name)
logs.WriteString(" of pod ")
logs.WriteString(pod.Name)
logs.WriteString(" terminated with an error (Reason: ")
logs.WriteString(container.status.Reason)

Expand All @@ -180,6 +179,8 @@ func buildLogOutputForContainer(container containerStatus, logsOfContainer strin
} else {
logs.WriteString("Container ")
logs.WriteString(container.name)
logs.WriteString(" of pod ")
logs.WriteString(pod.Name)
logs.WriteString(":\n")
}

Expand Down

0 comments on commit 7f43233

Please sign in to comment.