Skip to content
This repository was archived by the owner on Jun 14, 2019. It is now read-only.
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
8 changes: 6 additions & 2 deletions TEMPLATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,13 @@ overall result of the job.
## `Pod`'s annotations

`ci-operator.openshift.io/wait-for-container-artifacts`:
Will wait for a completion of a container, that has been specified in its value,
before gathering the artifacts.
Comma-separated list of container names for which ci-operator will wait until complete,
to gather the artifacts.

`ci-operator.openshift.io/always-show-output`:
Will output the logs of all the containers in the pod, no matter what the exit code was.
The value should be `true` to enable this feature.

`ci-operator.openshift.io/containers-logged-on-failure`:
Comma-separated list of container names for which ci-operator will collect and log their output if pod fails.
By default, only the logs from the failed containers will be output.
75 changes: 61 additions & 14 deletions pkg/steps/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type templateExecutionStep struct {
}

const (
showOutputAnnotation string = "ci-operator.openshift.io/always-show-output"
showOutputAnnotation string = "ci-operator.openshift.io/always-show-output"
showContainerOutputAnnotation string = "ci-operator.openshift.io/containers-logged-on-failure"
)

func (s *templateExecutionStep) Inputs(ctx context.Context, dry bool) (api.InputDefinition, error) {
Expand Down Expand Up @@ -592,6 +593,11 @@ func waitForPodCompletionOrTimeout(podClient coreclientset.PodInterface, name st
return false, nil
}
if podJobIsFailed(pod) {
if pod.ObjectMeta.Annotations[showOutputAnnotation] != "true" {
if err := outputAnnotatedContainerLogs(podClient, pod); err != nil {
log.Printf("%v", err)
}
}
return false, appendLogToError(fmt.Errorf("the pod %s/%s failed after %s (failed containers: %s): %s", pod.Namespace, pod.Name, podDuration(pod).Truncate(time.Second), strings.Join(failedContainerNames(pod), ", "), podReason(pod)), podMessages(pod))
}

Expand All @@ -617,6 +623,11 @@ func waitForPodCompletionOrTimeout(podClient coreclientset.PodInterface, name st
return false, nil
}
if podJobIsFailed(pod) {
if pod.ObjectMeta.Annotations[showOutputAnnotation] != "true" {
if err := outputAnnotatedContainerLogs(podClient, pod); err != nil {
log.Printf("%v", err)
}
}
return false, appendLogToError(fmt.Errorf("the pod %s/%s failed after %s (failed containers: %s): %s", pod.Namespace, pod.Name, podDuration(pod).Truncate(time.Second), strings.Join(failedContainerNames(pod), ", "), podReason(pod)), podMessages(pod))
}
continue
Expand Down Expand Up @@ -793,10 +804,7 @@ func failedContainerNames(pod *coreapi.Pod) []string {
}

func podLogNewContainers(podClient coreclientset.PodInterface, pod *coreapi.Pod, completed map[string]time.Time, notifier ContainerNotifier) {
var statuses []coreapi.ContainerStatus
statuses = append(statuses, pod.Status.InitContainerStatuses...)
statuses = append(statuses, pod.Status.ContainerStatuses...)

statuses := gatherContainerStatuses(pod)
for _, status := range statuses {
if _, ok := completed[status.Name]; ok {
continue
Expand All @@ -813,15 +821,8 @@ func podLogNewContainers(podClient coreclientset.PodInterface, pod *coreapi.Pod,
continue
}

if s, err := podClient.GetLogs(pod.Name, &coreapi.PodLogOptions{
Container: status.Name,
}).Stream(); err == nil {
if _, err := io.Copy(os.Stdout, s); err != nil {
log.Printf("error: Unable to copy log output from failed pod container %s: %v", status.Name, err)
}
s.Close()
} else {
log.Printf("error: Unable to retrieve logs from failed pod container %s: %v", status.Name, err)
if err := printContainerLogsToStdout(podClient, status.Name, pod.Name); err != nil {
log.Printf("%v", err)
}

if status.State.Terminated.ExitCode != 0 {
Expand All @@ -836,3 +837,49 @@ func podLogNewContainers(podClient coreclientset.PodInterface, pod *coreapi.Pod,
notifier.Complete(pod.Name)
}
}

func printContainerLogsToStdout(podClient coreclientset.PodInterface, statusName, podName string) error {
if s, err := podClient.GetLogs(podName, &coreapi.PodLogOptions{
Container: statusName,
}).Stream(); err == nil {
if _, err := io.Copy(os.Stdout, s); err != nil {
return fmt.Errorf("error: Unable to copy log output from failed pod container %s: %v", statusName, err)
}
s.Close()
} else {
return fmt.Errorf("error: Unable to retrieve logs from failed pod container %s: %v", statusName, err)
}
return nil
}

func getContainersMap(containers string) map[string]struct{} {
s := strings.Split(containers, ",")
c := make(map[string]struct{}, len(s))
for _, container := range s {
c[strings.TrimSpace(container)] = struct{}{}
}
return c
}

func outputAnnotatedContainerLogs(podClient coreclientset.PodInterface, pod *coreapi.Pod) error {
statuses := gatherContainerStatuses(pod)
containersToOutput := getContainersMap(pod.ObjectMeta.Annotations[showContainerOutputAnnotation])
for _, status := range statuses {
if s := status.State.Terminated; s != nil {
if _, exists := containersToOutput[status.Name]; exists {
log.Printf("Container %s logs:", status.Name)
if err := printContainerLogsToStdout(podClient, status.Name, pod.Name); err != nil {
return fmt.Errorf("%v", err)
}
}
}
}
return nil
}

func gatherContainerStatuses(pod *coreapi.Pod) []coreapi.ContainerStatus {
var statuses []coreapi.ContainerStatus
statuses = append(statuses, pod.Status.InitContainerStatuses...)
statuses = append(statuses, pod.Status.ContainerStatuses...)
return statuses
}