-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
injecitng sidecar and parsing logs to extract results
- Loading branch information
1 parent
d53dc1b
commit 2828651
Showing
13 changed files
with
650 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
Copyright 2019 The Tekton Authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package sidecarlogresults | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/tektoncd/pipeline/pkg/apis/config" | ||
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
// ErrorReasonMaxResultSizeExceeded indicates that the result exceeded its maximum allowed size | ||
// var ErrorReasonMaxResultSizeExceeded = fmt.Errorf("%s", "MaxResultSizeExceeded") | ||
var ErrorReasonMaxResultSizeExceeded = "MaxResultSizeExceeded" | ||
|
||
// SidecarLogResult holds fields for storing extracted results | ||
type SidecarLogResult struct { | ||
Name string | ||
Value string | ||
} | ||
|
||
// GetResultsFromSidecarLogs extracts results from the logs of the results sidecar | ||
func GetResultsFromSidecarLogs(ctx context.Context, clientset kubernetes.Interface, namespace string, name string, container string) ([]v1beta1.PipelineResourceResult, error) { | ||
sidecarLogResults := []v1beta1.PipelineResourceResult{} | ||
p, _ := clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
if p.Status.Phase == corev1.PodPending { | ||
return sidecarLogResults, nil | ||
} | ||
podLogOpts := corev1.PodLogOptions{Container: container} | ||
req := clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts) | ||
sidecarLogs, err := req.Stream(ctx) | ||
if err != nil { | ||
return sidecarLogResults, err | ||
} | ||
defer sidecarLogs.Close() | ||
maxResultLimit := config.FromContextOrDefaults(ctx).FeatureFlags.MaxResultSize | ||
return extractResultsFromLogs(sidecarLogs, sidecarLogResults, maxResultLimit) | ||
} | ||
|
||
func extractResultsFromLogs(logs io.Reader, sidecarLogResults []v1beta1.PipelineResourceResult, maxResultLimit int) ([]v1beta1.PipelineResourceResult, error) { | ||
scanner := bufio.NewScanner(logs) | ||
buf := make([]byte, maxResultLimit) | ||
scanner.Buffer(buf, maxResultLimit) | ||
for scanner.Scan() { | ||
result, err := parseResults(scanner.Bytes(), maxResultLimit) | ||
if err != nil { | ||
return nil, err | ||
} | ||
sidecarLogResults = append(sidecarLogResults, result) | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
if errors.Is(err, bufio.ErrTooLong) { | ||
return sidecarLogResults, fmt.Errorf("%s", ErrorReasonMaxResultSizeExceeded) | ||
} | ||
return nil, err | ||
} | ||
return sidecarLogResults, nil | ||
} | ||
|
||
func parseResults(resultBytes []byte, maxResultLimit int) (v1beta1.PipelineResourceResult, error) { | ||
result := v1beta1.PipelineResourceResult{} | ||
if len(resultBytes) > maxResultLimit { | ||
return result, fmt.Errorf("%s", ErrorReasonMaxResultSizeExceeded) | ||
} | ||
|
||
var res SidecarLogResult | ||
if err := json.Unmarshal(resultBytes, &res); err != nil { | ||
return result, fmt.Errorf("Invalid result %w", err) | ||
} | ||
result = v1beta1.PipelineResourceResult{ | ||
Key: res.Name, | ||
Value: res.Value, | ||
ResultType: v1beta1.TaskRunResultType, | ||
} | ||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package sidecarlogresults | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" | ||
"github.com/tektoncd/pipeline/test/diff" | ||
corev1 "k8s.io/api/core/v1" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
fakekubeclientset "k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
func TestExtractResultsFromLogs(t *testing.T) { | ||
inputResults := []SidecarLogResult{ | ||
{ | ||
Name: "result1", | ||
Value: "foo", | ||
}, { | ||
Name: "result2", | ||
Value: "bar", | ||
}, | ||
} | ||
podLogs := "" | ||
for _, r := range inputResults { | ||
res, _ := json.Marshal(&r) | ||
podLogs = fmt.Sprintf("%s%s\n", podLogs, string(res)) | ||
} | ||
logs := strings.NewReader(podLogs) | ||
|
||
results, err := extractResultsFromLogs(logs, []v1beta1.PipelineResourceResult{}, 4096) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
want := []v1beta1.PipelineResourceResult{ | ||
{ | ||
Key: "result1", | ||
Value: "foo", | ||
ResultType: v1beta1.TaskRunResultType, | ||
}, { | ||
Key: "result2", | ||
Value: "bar", | ||
ResultType: v1beta1.TaskRunResultType, | ||
}, | ||
} | ||
if d := cmp.Diff(want, results); d != "" { | ||
t.Fatal(diff.PrintWantGot(d)) | ||
} | ||
} | ||
|
||
func TestExtractResultsFromLogs_Failure(t *testing.T) { | ||
inputResults := []SidecarLogResult{ | ||
{ | ||
Name: "result1", | ||
Value: strings.Repeat("v", 4098), | ||
}, | ||
} | ||
podLogs := "" | ||
for _, r := range inputResults { | ||
res, _ := json.Marshal(&r) | ||
podLogs = fmt.Sprintf("%s%s\n", podLogs, string(res)) | ||
} | ||
logs := strings.NewReader(podLogs) | ||
|
||
_, err := extractResultsFromLogs(logs, []v1beta1.PipelineResourceResult{}, 4096) | ||
if err.Error() != ErrorReasonMaxResultSizeExceeded { | ||
t.Fatal(fmt.Sprintf("Expexted error %v but got %v", ErrorReasonMaxResultSizeExceeded, err)) | ||
} | ||
} | ||
|
||
func TestParseResults(t *testing.T) { | ||
results := []SidecarLogResult{ | ||
{ | ||
Name: "result1", | ||
Value: "foo", | ||
}, { | ||
Name: "result2", | ||
Value: `{"IMAGE_URL":"ar.com", "IMAGE_DIGEST":"sha234"}`, | ||
}, { | ||
Name: "result3", | ||
Value: `["hello","world"]`, | ||
}, | ||
} | ||
podLogs := []string{} | ||
for _, r := range results { | ||
res, _ := json.Marshal(&r) | ||
podLogs = append(podLogs, string(res)) | ||
} | ||
want := []v1beta1.PipelineResourceResult{{ | ||
Key: "result1", | ||
Value: "foo", | ||
ResultType: 1, | ||
}, { | ||
Key: "result2", | ||
Value: `{"IMAGE_URL":"ar.com", "IMAGE_DIGEST":"sha234"}`, | ||
ResultType: 1, | ||
}, { | ||
Key: "result3", | ||
Value: `["hello","world"]`, | ||
ResultType: 1, | ||
}} | ||
stepResults := []v1beta1.PipelineResourceResult{} | ||
for _, plog := range podLogs { | ||
res, err := parseResults([]byte(plog), 4096) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
stepResults = append(stepResults, res) | ||
} | ||
if d := cmp.Diff(want, stepResults); d != "" { | ||
t.Fatal(diff.PrintWantGot(d)) | ||
} | ||
} | ||
|
||
func TestParseResults_Failure(t *testing.T) { | ||
result := SidecarLogResult{ | ||
Name: "result2", | ||
Value: strings.Repeat("k", 4098), | ||
} | ||
res1, _ := json.Marshal("result1 v1") | ||
res2, _ := json.Marshal(&result) | ||
podLogs := []string{string(res1), string(res2)} | ||
want := []string{ | ||
"Invalid result json: cannot unmarshal string into Go value of type sidecarlogresults.SidecarLogResult", | ||
ErrorReasonMaxResultSizeExceeded, | ||
} | ||
got := []string{} | ||
for _, plog := range podLogs { | ||
_, err := parseResults([]byte(plog), 4096) | ||
got = append(got, err.Error()) | ||
} | ||
if d := cmp.Diff(want, got); d != "" { | ||
t.Fatal(diff.PrintWantGot(d)) | ||
} | ||
} | ||
|
||
func TestGetResultsFromSidecarLogs(t *testing.T) { | ||
for _, c := range []struct { | ||
desc string | ||
podPhase v1.PodPhase | ||
wantError bool | ||
}{{ | ||
desc: "pod pending to start", | ||
podPhase: corev1.PodPending, | ||
wantError: false, | ||
}, { | ||
desc: "pod running extract logs", | ||
podPhase: corev1.PodRunning, | ||
wantError: true, | ||
}} { | ||
t.Run(c.desc, func(t *testing.T) { | ||
ctx := context.Background() | ||
clientset := fakekubeclientset.NewSimpleClientset() | ||
pod := &v1.Pod{ | ||
TypeMeta: metav1.TypeMeta{ | ||
Kind: "Pod", | ||
APIVersion: "v1", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "pod", | ||
Namespace: "foo", | ||
}, | ||
Spec: v1.PodSpec{ | ||
Containers: []v1.Container{ | ||
{ | ||
Name: "container", | ||
Image: "image", | ||
}, | ||
}, | ||
}, | ||
Status: v1.PodStatus{ | ||
Phase: c.podPhase, | ||
}, | ||
} | ||
pod, err := clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) | ||
if err != nil { | ||
t.Errorf("Error occured while creating pod %s: %s", pod.Name, err.Error()) | ||
} | ||
|
||
// Fake logs are not formatted properly so there will be an error | ||
_, err = GetResultsFromSidecarLogs(ctx, clientset, "foo", "pod", "container") | ||
if err != nil && !c.wantError { | ||
t.Fatalf("did not expect an error but got: %v", err) | ||
} | ||
if c.wantError && err == nil { | ||
t.Fatal("dxpected to get an error but did not") | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
Copyright 2019 The Tekton Authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package pipeline | ||
|
||
// ReservedResultsSidecarName is the name of the results sidecar that outputs the results to stdout | ||
// when the results-from feature-flag is set to "sidecar-logs". | ||
const ReservedResultsSidecarName = "tekton-log-results" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.