Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Surface artifacts through sidecar container logs. #7883

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 17 additions & 3 deletions cmd/sidecarlogresults/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ func main() {
var resultsDir string
var resultNames string
var stepResultsStr string
var stepNames string

flag.StringVar(&resultsDir, "results-dir", pipeline.DefaultResultPath, "Path to the results directory. Default is /tekton/results")
flag.StringVar(&resultNames, "result-names", "", "comma separated result names to expect from the steps running in the pod. eg. foo,bar,baz")
flag.StringVar(&stepResultsStr, "step-results", "", "json containing a map of step Name as key and list of result Names. eg. {\"stepName\":[\"foo\",\"bar\",\"baz\"]}")
flag.StringVar(&stepNames, "step-names", "", "comma separated step names. eg. foo,bar,baz")
flag.Parse()
if resultNames == "" {
log.Fatal("result-names were not provided")

var expectedResults []string
// strings.Split returns [""] instead of [] for empty string, we don't want pass [""] to other methods.
if len(resultNames) > 0 {
expectedResults = strings.Split(resultNames, ",")
}
expectedResults := strings.Split(resultNames, ",")
expectedStepResults := map[string][]string{}
if err := json.Unmarshal([]byte(stepResultsStr), &expectedStepResults); err != nil {
log.Fatal(err)
Expand All @@ -48,4 +53,13 @@ func main() {
if err != nil {
log.Fatal(err)
}

var names []string
if len(stepNames) > 0 {
names = strings.Split(stepNames, ",")
}
err = sidecarlogresults.LookForArtifacts(os.Stdout, names, pod.RunDir)
if err != nil {
log.Fatal(err)
}
}
4 changes: 2 additions & 2 deletions examples/v1/taskruns/alpha/produce-consume-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ spec:
"name":"input-artifacts",
"values":[
{
"uri":"git:jjjsss",
"uri":"pkg:example.github.com/inputs",
"digest":{
"sha256":"b35cacccfdb1e24dc497d15d553891345fd155713ffe647c281c583269eaaae0"
}
Expand All @@ -30,7 +30,7 @@ spec:
"name":"image",
"values":[
{
"uri":"pkg:balba",
"uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c",
"digest":{
"sha256":"df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48",
"sha1":"95588b8f34c31eb7d62c92aaa4e6506639b06ef2"
Expand Down
60 changes: 57 additions & 3 deletions internal/sidecarlogresults/sidecarlogresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"strings"

"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/result"
"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
Expand All @@ -36,12 +38,15 @@ import (

// ErrSizeExceeded indicates that the result exceeded its maximum allowed size
var ErrSizeExceeded = errors.New("results size exceeds configured limit")
var stepDir = pipeline.StepsDir

type SidecarLogResultType string

const (
taskResultType SidecarLogResultType = "task"
stepResultType SidecarLogResultType = "step"
taskResultType SidecarLogResultType = "task"
stepResultType SidecarLogResultType = "step"

stepArtifactType SidecarLogResultType = "stepArtifact"
sidecarResultNameSeparator string = "."
)

Expand Down Expand Up @@ -197,6 +202,37 @@ func LookForResults(w io.Writer, runDir string, resultsDir string, resultNames [
return nil
}

// LookForArtifacts searches for and processes artifacts within a specified run directory.
// It looks for "provenance.json" files within the "artifacts" subdirectory of each named step.
// If the provenance file exists, the function extracts artifact information, formats it into a
// JSON string, and encodes it for output alongside relevant metadata (step name, artifact type).
func LookForArtifacts(w io.Writer, names []string, runDir string) error {
if err := waitForStepsToFinish(runDir); err != nil {
return err
}

for _, name := range names {
p := filepath.Join(stepDir, name, "artifacts", "provenance.json")
if exist, err := fileExists(p); err != nil {
return err
} else if !exist {
continue
}
subRes, err := extractArtifactsFromFile(p)
if err != nil {
return err
}
values, err := json.Marshal(&subRes)
if err != nil {
return err
}
if err := encode(w, SidecarLogResult{Name: name, Value: string(values), Type: stepArtifactType}); err != nil {
return err
}
}
return nil
}

// GetResultsFromSidecarLogs extracts results from the logs of the results sidecar
func GetResultsFromSidecarLogs(ctx context.Context, clientset kubernetes.Interface, namespace string, name string, container string, podPhase corev1.PodPhase) ([]result.RunResult, error) {
sidecarLogResults := []result.RunResult{}
Expand Down Expand Up @@ -250,8 +286,10 @@ func parseResults(resultBytes []byte, maxResultLimit int) (result.RunResult, err
resultType = result.TaskRunResultType
case stepResultType:
resultType = result.StepResultType
case stepArtifactType:
resultType = result.StepArtifactsResultType
default:
return result.RunResult{}, fmt.Errorf("invalid sidecar result type %v. Must be %v or %v", res.Type, taskResultType, stepResultType)
return result.RunResult{}, fmt.Errorf("invalid sidecar result type %v. Must be %v or %v or %v", res.Type, taskResultType, stepResultType, stepArtifactType)
}
runResult = result.RunResult{
Key: res.Name,
Expand All @@ -260,3 +298,19 @@ func parseResults(resultBytes []byte, maxResultLimit int) (result.RunResult, err
}
return runResult, nil
}

func parseArtifacts(fileContent []byte) (v1.Artifacts, error) {
var as v1.Artifacts
if err := json.Unmarshal(fileContent, &as); err != nil {
return as, fmt.Errorf("invalid artifacts : %w", err)
}
return as, nil
}

func extractArtifactsFromFile(filename string) (v1.Artifacts, error) {
b, err := os.ReadFile(filename)
if err != nil {
return v1.Artifacts{}, fmt.Errorf("error reading the results file %w", err)
}
return parseArtifacts(b)
}