Skip to content
Open
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
76 changes: 51 additions & 25 deletions cmd/deck/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,40 +785,66 @@ func handleProwJobs(ja *jobs.JobAgent, log *logrus.Entry) http.HandlerFunc {
setHeadersNoCaching(w)
jobs := ja.ProwJobs()
omit := r.URL.Query().Get("omit")
omitSet := sets.New(strings.Split(omit, ",")...)

if set := sets.New[string](strings.Split(omit, ",")...); set.Len() > 0 {
for i := range jobs {
jobs[i].ManagedFields = nil
if set.Has(Annotations) {
jobs[i].Annotations = nil
}
if set.Has(Labels) {
jobs[i].Labels = nil
}
if set.Has(DecorationConfig) {
jobs[i].Spec.DecorationConfig = nil
org := r.URL.Query().Get("org")
repo := r.URL.Query().Get("repo")
owner := r.URL.Query().Get("owner")

ownerMatch := func(job prowapi.ProwJob, owner string) bool {
if job.Spec.Refs == nil {
return false
}
for _, pull := range job.Spec.Refs.Pulls {
if pull.Author == owner {
return true
}
if set.Has(PodSpec) {
// when we omit the podspec, we don't set it completely to nil
// instead, we set it to a new podspec that just has an empty container for each container that exists in the actual podspec
// this is so we can determine how many containers there are for a given prowjob without fetching all of the podspec details
// this is necessary for prow/cmd/deck/static/prow/pkg.ts to determine whether the logIcon should link to a log endpoint or to spyglass
if jobs[i].Spec.PodSpec != nil {
emptyContainers := []coreapi.Container{}
for range jobs[i].Spec.PodSpec.Containers {
emptyContainers = append(emptyContainers, coreapi.Container{})
}
jobs[i].Spec.PodSpec = &coreapi.PodSpec{
Containers: emptyContainers,
}
}
return false
}

finalJobs := make([]prowapi.ProwJob, 0)
for i := range jobs {
if org != "" && (jobs[i].Spec.Refs == nil || jobs[i].Spec.Refs.Org != org) {
continue
}
if repo != "" && (jobs[i].Spec.Refs == nil || jobs[i].Spec.Refs.Repo != repo) {
continue
}
if owner != "" && !ownerMatch(jobs[i], owner) {
continue
}
jobs[i].ManagedFields = nil
if omitSet.Has(Annotations) {
jobs[i].Annotations = nil
}
if omitSet.Has(Labels) {
jobs[i].Labels = nil
}
if omitSet.Has(DecorationConfig) {
jobs[i].Spec.DecorationConfig = nil
}
if omitSet.Has(PodSpec) {
// when we omit the podspec, we don't set it completely to nil
// instead, we set it to a new podspec that just has an empty container for each container that exists in the actual podspec
// this is so we can determine how many containers there are for a given prowjob without fetching all of the podspec details
// this is necessary for prow/cmd/deck/static/prow/pkg.ts to determine whether the logIcon should link to a log endpoint or to spyglass
if jobs[i].Spec.PodSpec != nil {
emptyContainers := []coreapi.Container{}
for range jobs[i].Spec.PodSpec.Containers {
emptyContainers = append(emptyContainers, coreapi.Container{})
}
jobs[i].Spec.PodSpec = &coreapi.PodSpec{
Containers: emptyContainers,
}
}
}
finalJobs = append(finalJobs, jobs[i])
}

jd, err := json.Marshal(struct {
Items []prowapi.ProwJob `json:"items"`
}{jobs})
}{finalJobs})
if err != nil {
log.WithError(err).Error("Error marshaling jobs.")
jd = []byte("{}")
Expand Down
180 changes: 180 additions & 0 deletions cmd/deck/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"net/url"
"os"
"reflect"
"slices"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -426,6 +427,185 @@ func TestProwJob(t *testing.T) {
}
}

// TestHandleProwJobsWithFilter checks if, given a list of prowjobs, the filters
// are respected and just a subset of jobs are returned.
func TestHandleProwJobsWithFilter(t *testing.T) {
kc := fkc{
{
ObjectMeta: metav1.ObjectMeta{
Name: "fullref",
Labels: map[string]string{
"goodbye": "world",
},
},
Spec: prowapi.ProwJobSpec{
Agent: prowapi.KubernetesAgent,
Job: "job",
Refs: &prowapi.Refs{
Org: "amazingk8s",
Repo: "pizzacontroller",
Pulls: []prowapi.Pull{
{
Author: "someone",
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "differentorg",
Labels: map[string]string{
"goodbye": "world",
},
},
Spec: prowapi.ProwJobSpec{
Agent: prowapi.KubernetesAgent,
Job: "job",
Refs: &prowapi.Refs{
Org: "otherk8s",
Repo: "pizzacontroller",
Pulls: []prowapi.Pull{
{
Author: "someone",
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "nullref",
},
Spec: prowapi.ProwJobSpec{
Agent: prowapi.KubernetesAgent,
Job: "job",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "noowner",
},
Spec: prowapi.ProwJobSpec{
Agent: prowapi.KubernetesAgent,
Job: "job",
Refs: &prowapi.Refs{
Org: "amazingk8s",
Repo: "pizzacontroller",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "multiowner",
},
Spec: prowapi.ProwJobSpec{
Agent: prowapi.KubernetesAgent,
Job: "job",
Refs: &prowapi.Refs{
Org: "amazingk8s",
Repo: "pizzacontroller",
Pulls: []prowapi.Pull{
{
Author: "someone",
},
{
Author: "anotherone",
},
},
},
},
},
}

type testCase struct {
Name string
Owner string
Org string
Repo string
ExpectedJobs []string
}

testCases := []testCase{
{
Name: "no filter should return all the tests",
ExpectedJobs: []string{"fullref", "nullref", "noowner", "multiowner", "differentorg"},
},
{
Name: "owner filter should return just jobs with the right owner",
Owner: "anotherone",
ExpectedJobs: []string{"multiowner"},
},
{
Name: "owner and org filter should return just jobs with the right owner and org",
Owner: "someone",
Org: "amazingk8s",
ExpectedJobs: []string{"fullref", "multiowner"},
},
{
Name: "owner, org and repo filter should return just jobs the right job",
Owner: "someone",
Org: "otherk8s",
Repo: "pizzacontroller",
ExpectedJobs: []string{"differentorg"},
},
}

fakeJa := jobs.NewJobAgent(context.Background(), kc, false, true, []string{}, map[string]jobs.PodLogClient{}, fca{}.Config)
fakeJa.Start()

handler := handleProwJobs(fakeJa, logrus.WithField("handler", "/prowjobs.js"))

for _, tc := range testCases {

queryS := make(url.Values)
if tc.Org != "" {
queryS.Add("org", tc.Org)
}
if tc.Repo != "" {
queryS.Add("repo", tc.Repo)
}
if tc.Owner != "" {
queryS.Add("owner", tc.Owner)
}

req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/prowjobs.js?%s", queryS.Encode()), nil)
if err != nil {
t.Errorf("Error making request: %v", err)
}

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Bad error code: %d", rr.Code)
}
resp := rr.Result()
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Errorf("Error reading response body: %v", err)
}
type prowjobItems struct {
Items []prowapi.ProwJob `json:"items"`
}
var res prowjobItems
if err := json.Unmarshal(body, &res); err != nil {
t.Errorf("Error unmarshalling: %v", err)
}
slices.Sort(tc.ExpectedJobs)
returnedJobs := make([]string, len(res.Items))
for i := range res.Items {
returnedJobs[i] = res.Items[i].Name
}
slices.Sort(returnedJobs)
if !slices.Equal(tc.ExpectedJobs, returnedJobs) {
t.Errorf("TEST %s: returned invalid jobs, expecting %+v returned %+v", tc.Name, tc.ExpectedJobs, returnedJobs)
}

}

}

type fakeAuthenticatedUserIdentifier struct {
login string
}
Expand Down