Skip to content

Commit

Permalink
kind/feat: surface artifacts through sidecar container
Browse files Browse the repository at this point in the history
  • Loading branch information
ericzzzzzzz committed Apr 22, 2024
1 parent 4fe52d4 commit 8efcd61
Show file tree
Hide file tree
Showing 16 changed files with 752 additions and 83 deletions.
2 changes: 1 addition & 1 deletion cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func main() {
flag.StringVar(&opts.Images.ShellImage, "shell-image", "", "The container image containing a shell")
flag.StringVar(&opts.Images.ShellImageWin, "shell-image-win", "", "The container image containing a windows shell")
flag.StringVar(&opts.Images.WorkingDirInitImage, "workingdirinit-image", "", "The container image containing our working dir init binary.")

flag.StringVar(&opts.Images.SidecarLogArtifactsImage, "sidecarlogartifacts-image", "", "The container image containing the binary for accessing artifacts.")
// This parses flags.
cfg := injection.ParseAndGetRESTConfigOrDie()

Expand Down
44 changes: 44 additions & 0 deletions cmd/sidecarlogartifacts/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2024 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 main

import (
"encoding/json"
"flag"
"log"
"os"
"strings"

"github.com/tektoncd/pipeline/internal/sidecarlogartifacts"
"github.com/tektoncd/pipeline/pkg/pod"
)

func main() {
var stepNames string
flag.StringVar(&stepNames, "step-names", "", "comma separated step names to expect from the steps running in the pod. eg. foo,bar,baz")
flag.Parse()
if stepNames == "" {
log.Fatal("step-names were not provided")
}
names := strings.Split(stepNames, ",")
artifacts, err := sidecarlogartifacts.LookForArtifacts(names, pod.RunDir)
if err != nil {
log.Fatal(err)
}
err = json.NewEncoder(os.Stdout).Encode(artifacts)

if err != nil {
log.Fatal(err)
}
}
1 change: 1 addition & 0 deletions config/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ spec:
"-entrypoint-image", "ko://github.com/tektoncd/pipeline/cmd/entrypoint",
"-nop-image", "ko://github.com/tektoncd/pipeline/cmd/nop",
"-sidecarlogresults-image", "ko://github.com/tektoncd/pipeline/cmd/sidecarlogresults",
"-sidecarlogartifacts-image", "ko://github.com/tektoncd/pipeline/cmd/sidecarlogartifacts",
"-workingdirinit-image", "ko://github.com/tektoncd/pipeline/cmd/workingdirinit",

# The shell image must allow root in order to create directories and copy files to PVCs.
Expand Down
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
141 changes: 141 additions & 0 deletions internal/sidecarlogartifacts/sidecarlogartifacts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright 2024 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 sidecarlogartifacts

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"github.com/tektoncd/pipeline/pkg/apis/pipeline"
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"

corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)

// for testing
var stepDir = pipeline.StepsDir

func fileExists(filename string) (bool, error) {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, fmt.Errorf("error checking for file existence %w", err)
}
return !info.IsDir(), nil
}

func waitForStepsToFinish(runDir string) error {
steps := make(map[string]bool)
files, err := os.ReadDir(runDir)
if err != nil {
return fmt.Errorf("error parsing the run dir %w", err)
}
for _, file := range files {
steps[filepath.Join(runDir, file.Name(), "out")] = true
}
for len(steps) > 0 {
for stepFile := range steps {
// check if there is a post file without error
time.Sleep(200 * time.Millisecond)
exists, err := fileExists(stepFile)
if err != nil {
return fmt.Errorf("error checking for out file's existence %w", err)
}
if exists {
delete(steps, stepFile)
continue
}

// This is the same as results sidecar, it checks if there is a post file with error
// if err is nil then either the out.err file does not exist or it does and there was no issue
// in either case, existence of out.err marks that the step errored and the following steps will
// not run. We want the function to break out with nil error in that case so that
// the existing results can be logged.
if exists, err = fileExists(stepFile + ".err"); exists || err != nil {
return err
}
}
}
return 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{}, err
}
return parseArtifacts(b)
}

type SidecarArtifacts map[string]v1.Artifacts

// GetArtifactsFromSidecarLogs retrieves artifacts from sidecar logs in a Kubernetes pod.
// It returns a SidecarArtifacts map (step name -> v1.Artifacts) and an error. If the pod is in the
// Pending phase, an empty map is returned without error.
func GetArtifactsFromSidecarLogs(ctx context.Context, clientset kubernetes.Interface, namespace string, name string, container string, podPhase corev1.PodPhase) (SidecarArtifacts, error) {
sidecarArtifacts := SidecarArtifacts{}
if podPhase == corev1.PodPending {
return sidecarArtifacts, nil
}
podLogOpts := corev1.PodLogOptions{Container: container}
req := clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts)
stream, err := req.Stream(ctx)
if err != nil {
return sidecarArtifacts, err
}
err = json.NewDecoder(stream).Decode(&sidecarArtifacts)
if err != nil {
return sidecarArtifacts, err
}

return sidecarArtifacts, nil
}

// LookForArtifacts searches for provenance.json files in the specified run directory
// and extracts artifacts from them.
func LookForArtifacts(names []string, runDir string) (SidecarArtifacts, error) {
err := waitForStepsToFinish(runDir)
if err != nil {
return nil, err
}
artifacts := SidecarArtifacts{}
for _, name := range names {
p := filepath.Join(stepDir, name, "artifacts", "provenance.json")
if exist, err := fileExists(p); err != nil {
return artifacts, err
} else if !exist {
continue
}
subRes, err := extractArtifactsFromFile(p)
if err != nil {
return SidecarArtifacts{}, err
}
artifacts[name] = v1.Artifacts{Inputs: subRes.Inputs, Outputs: subRes.Outputs}
}
return artifacts, nil
}

0 comments on commit 8efcd61

Please sign in to comment.