<commit_before>/*
Copyright 2017 The Kubernetes 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 config

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/ghodss/yaml"

	"k8s.io/test-infra/prow/kube"
)

// Loaded at TestMain.
var c *Config

func TestMain(m *testing.M) {
	conf, err := Load("../config.yaml")
	if err != nil {
		fmt.Printf("Could not load config: %v", err)
		os.Exit(1)
	}
	c = conf
	os.Exit(m.Run())
}

func replace(j *Presubmit, ks *Presubmit) error {
	name := strings.Replace(j.Name, "pull-kubernetes", "pull-security-kubernetes", -1)
	if name != ks.Name {
		return fmt.Errorf("%s should match %s", name, ks.Name)
	}
	j.Name = name
	j.RerunCommand = strings.Replace(j.RerunCommand, "pull-kubernetes", "pull-security-kubernetes", -1)
	j.Trigger = strings.Replace(j.Trigger, "pull-kubernetes", "pull-security-kubernetes", -1)
	j.Context = strings.Replace(j.Context, "pull-kubernetes", "pull-security-kubernetes", -1)

	if j.Agent == "kubernetes" {
		if len(j.Spec.Containers) != 1 {
			return fmt.Errorf("expected a single container for %s", name)
		}
		for i, arg := range j.Spec.Containers[0].Args {
			// handle --repo substitution for main repo
			if strings.HasPrefix(arg, "--repo=k8s.io/kubernetes") || strings.HasPrefix(arg, "--repo=k8s.io/$(REPO_NAME)") {
				j.Spec.Containers[0].Args[i] = strings.Replace(arg, "k8s.io/", "github.com/kubernetes-security/", 1)

				// handle upload bucket
			} else if strings.HasPrefix(arg, "--upload=") {
				j.Spec.Containers[0].Args[i] = "--upload=gs://kubernetes-security-jenkins/pr-logs"
			}
		}
	}

	j.re = ks.re
	if len(j.RunAfterSuccess) != len(ks.RunAfterSuccess) {
		return fmt.Errorf("length of RunAfterSuccess should match. - %s", name)
	}

	for i := range j.RunAfterSuccess {
		if err := replace(&j.RunAfterSuccess[i], &ks.RunAfterSuccess[i]); err != nil {
			return err
		}
	}

	return nil
}

func checkContext(t *testing.T, repo string, p Presubmit) {
	if p.Name != p.Context {
		t.Errorf("Context does not match job name: %s in %s", p.Name, repo)
	}
	for _, c := range p.RunAfterSuccess {
		checkContext(t, repo, c)
	}
}

func TestContextMatches(t *testing.T) {
	for repo, presubmits := range c.Presubmits {
		for _, p := range presubmits {
			checkContext(t, repo, p)
		}
	}
}

func checkRetest(t *testing.T, repo string, presubmits []Presubmit) {
	for _, p := range presubmits {
		expected := fmt.Sprintf("/test %s", p.Name)
		if p.RerunCommand != expected {
			t.Errorf("%s in %s rerun_command: %s != expected: %s", repo, p.Name, p.RerunCommand, expected)
		}
		checkRetest(t, repo, p.RunAfterSuccess)
	}
}

func TestRetestMatchJobsName(t *testing.T) {
	for repo, presubmits := range c.Presubmits {
		checkRetest(t, repo, presubmits)
	}
}

type SubmitQueueConfig struct {
	// this is the only field we need for the tests below
	RequiredRetestContexts string `json:"required-retest-contexts"`
}

func findRequired(t *testing.T, presubmits []Presubmit) []string {
	var required []string
	for _, p := range presubmits {
		if !p.AlwaysRun {
			continue
		}
		for _, r := range findRequired(t, p.RunAfterSuccess) {
			required = append(required, r)
		}
		if p.SkipReport {
			continue
		}
		required = append(required, p.Context)
	}
	return required
}

func TestRequiredRetestContextsMatch(t *testing.T) {
	b, err := ioutil.ReadFile("../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml")
	if err != nil {
		t.Fatalf("Could not load submit queue configmap: %v", err)
	}
	sqc := &SubmitQueueConfig{}
	if err = yaml.Unmarshal(b, sqc); err != nil {
		t.Fatalf("Could not parse submit queue configmap: %v", err)
	}
	required := strings.Split(sqc.RequiredRetestContexts, ",")

	running := findRequired(t, c.Presubmits["kubernetes/kubernetes"])

	for _, r := range required {
		found := false
		for _, s := range running {
			if s == r {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("Required context: %s does not always run: %s", r, running)
		}
	}
}

func TestConfigSecurityJobsMatch(t *testing.T) {
	conf, err := Load("../config.yaml")
	if err != nil {
		t.Fatalf("fail to load config for TestConfigSecurityJobsMatch : %v", err)
	}
	kp := conf.Presubmits["kubernetes/kubernetes"]
	sp := conf.Presubmits["kubernetes-security/kubernetes"]
	if len(kp) != len(sp) {
		t.Fatalf("length of kubernetes/kubernetes presubmits %d does not equal length of kubernetes-security/kubernetes presubmits %d", len(kp), len(sp))
	}
	for i, j := range kp {
		if err := replace(&j, &sp[i]); err != nil {
			t.Fatalf("[replace] : %v", err)
		}

		if !reflect.DeepEqual(j, sp[i]) {
			t.Fatalf("kubernetes/kubernetes prow config jobs do not match kubernetes-security/kubernetes jobs:\n%#v\nshould match: %#v", j, sp[i])
		}
	}
}

// checkDockerSocketVolumes returns an error if any volume uses a hostpath
// to the docker socket. we do not want to allow this
func checkDockerSocketVolumes(volumes []kube.Volume) error {
	for _, volume := range volumes {
		if volume.HostPath != nil && volume.HostPath.Path == "/var/run/docker.sock" {
			return errors.New("job uses HostPath with docker socket")
		}
	}
	return nil
}

// Make sure jobs are not using the docker socket as a host path
func TestJobDoesNotHaveDockerSocket(t *testing.T) {
	for _, pres := range c.Presubmits {
		for _, presubmit := range pres {
			if presubmit.Spec != nil {
				if err := checkDockerSocketVolumes(presubmit.Spec.Volumes); err != nil {
					t.Errorf("Error in presubmit: %v", err)
				}
			}
		}
	}

	for _, posts := range c.Postsubmits {
		for _, postsubmit := range posts {
			if postsubmit.Spec != nil {
				if err := checkDockerSocketVolumes(postsubmit.Spec.Volumes); err != nil {
					t.Errorf("Error in postsubmit: %v", err)
				}
			}
		}
	}

	for _, periodic := range c.Periodics {
		if periodic.Spec != nil {
			if err := checkDockerSocketVolumes(periodic.Spec.Volumes); err != nil {
				t.Errorf("Error in periodic: %v", err)
			}
		}
	}
}

func checkBazelPortContainer(c kube.Container, cache bool) error {
	if !cache {
		if len(c.Ports) != 0 {
			return errors.New("job does not use --cache-ssd and so should not set ports in spec")
		}
		return nil
	}

	if len(c.Ports) != 1 {
		return errors.New("job uses --cache-ssd and so needs to set ports in spec")
	} else if c.Ports[0].ContainerPort != 9999 {
		return errors.New("job uses --cache-ssd and so needs to have ContainerPort 9999")
	} else if c.Ports[0].HostPort != 9999 {
		return errors.New("job uses --cache-ssd and so needs to have HostPort 9999")
	}
	return nil
}

func checkBazelPortPresubmit(presubmits []Presubmit) error {
	for _, presubmit := range presubmits {
		if presubmit.Spec == nil {
			continue
		}
		hasCache := false
		for _, volume := range presubmit.Spec.Volumes {
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range presubmit.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", presubmit.Name, err)
			}
		}

		if err := checkBazelPortPresubmit(presubmit.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", presubmit.Name, err)
		}
	}

	return nil
}

func checkBazelPortPostsubmit(postsubmits []Postsubmit) error {
	for _, postsubmit := range postsubmits {
		hasCache := false
		for _, volume := range postsubmit.Spec.Volumes {
			// TODO(bentheelder): rewrite these tests and the entire caching layout...
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range postsubmit.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", postsubmit.Name, err)
			}
		}

		if err := checkBazelPortPostsubmit(postsubmit.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", postsubmit.Name, err)
		}
	}

	return nil
}

func checkBazelPortPeriodic(periodics []Periodic) error {
	for _, periodic := range periodics {
		hasCache := false
		for _, volume := range periodic.Spec.Volumes {
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range periodic.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", periodic.Name, err)
			}
		}

		if err := checkBazelPortPeriodic(periodic.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", periodic.Name, err)
		}
	}

	return nil
}

// Set the HostPort to 9999 for all bazel pods so that they are forced
// onto different nodes. Once pod affinity is GA, use that instead.
// Until https://github.com/kubernetes/community/blob/master/contributors/design-proposals/local-storage-overview.md
func TestBazelJobHasContainerPort(t *testing.T) {
	for _, pres := range c.Presubmits {
		if err := checkBazelPortPresubmit(pres); err != nil {
			t.Errorf("Error in presubmit: %v", err)
		}
	}

	for _, posts := range c.Postsubmits {
		if err := checkBazelPortPostsubmit(posts); err != nil {
			t.Errorf("Error in postsubmit: %v", err)
		}
	}

	if err := checkBazelPortPeriodic(c.Periodics); err != nil {
		t.Errorf("Error in periodic: %v", err)
	}
}

// Load the config and extract all jobs, including any child jobs inside
// RunAfterSuccess fields.
func allJobs() ([]Presubmit, []Postsubmit, []Periodic, error) {
	pres := []Presubmit{}
	posts := []Postsubmit{}
	peris := []Periodic{}

	{ // Find all presubmit jobs, including child jobs.
		q := []Presubmit{}

		for _, p := range c.Presubmits {
			for _, p2 := range p {
				q = append(q, p2)
			}
		}

		for len(q) > 0 {
			pres = append(pres, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	{ // Find all postsubmit jobs, including child jobs.
		q := []Postsubmit{}

		for _, p := range c.Postsubmits {
			for _, p2 := range p {
				q = append(q, p2)
			}
		}

		for len(q) > 0 {
			posts = append(posts, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	{ // Find all periodic jobs, including child jobs.
		q := []Periodic{}
		for _, p := range c.Periodics {
			q = append(q, p)
		}

		for len(q) > 0 {
			peris = append(peris, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	return pres, posts, peris, nil
}

// Validate any containers using a bazelbuild image, returning which bazelbuild tags are used.
// In particular ensure that:
//   * Presubmit, postsubmit jobs specify at least one --repo flag, the first of which uses PULL_REFS and REPO_NAME vars
//   * Prow injected vars like REPO_NAME, PULL_REFS, etc are only used on non-periodic jobs
//   * Deprecated --branch, --pull flags are not used
//   * Required --service-account, --upload, --git-cache, --job, --clean flags are present
func checkBazelbuildSpec(t *testing.T, name string, spec *kube.PodSpec, periodic bool) map[string]int {
	img := "gcr.io/k8s-testimages/bazelbuild"
	tags := map[string]int{}
	if spec == nil {
		return tags
	}
	// Tags look something like vDATE-SHA or vDATE-SHA-BAZELVERSION.
	// We want to match only on the date + sha
	tagRE := regexp.MustCompile(`^([^-]+-[^-]+)(-[^-]+)?$`)
	for _, c := range spec.Containers {
		parts := strings.SplitN(c.Image, ":", 2)
		var i, tag string // image:tag
		i = parts[0]
		if i != img {
			continue
		}
		if len(parts) == 1 {
			tag = "latest"
		} else {
			submatches := tagRE.FindStringSubmatch(parts[1])
			if submatches != nil {
				tag = submatches[1]
			} else {
				t.Errorf("bazelbuild tag '%s' doesn't match expected format", parts[1])
			}
		}
		tags[tag]++

		found := map[string][]string{}
		for _, a := range c.Args {
			parts := strings.SplitN(a, "=", 2)
			k := parts[0]
			v := "true"
			if len(parts) == 2 {
				v = parts[1]
			}
			found[k] = append(found[k], v)

			// Require --flag=FOO for easier processing
			if k == "--repo" && len(parts) == 1 {
				t.Errorf("%s: use --repo=FOO not --repo foo", name)
			}
		}

		if _, ok := found["--pull"]; ok {
			t.Errorf("%s: uses deprecated --pull arg, use --repo=org/repo=$(PULL_REFS) instead", name)
		}
		if _, ok := found["--branch"]; ok {
			t.Errorf("%s: uses deprecated --branch arg, use --repo=org/repo=$(PULL_REFS) instead", name)
		}

		for _, f := range []string{
			"--service-account",
			"--upload",
			"--git-cache",
			"--job",
			"--clean",
		} {
			if _, ok := found[f]; !ok {
				t.Errorf("%s: missing %s flag", name, f)
			}
		}

		if v, ok := found["--repo"]; !ok {
			t.Errorf("%s: missing %s flag", name, "--repo")
		} else {
			firstRepo := true
			hasRefs := false
			hasName := false
			for _, r := range v {
				hasRefs = hasRefs || strings.Contains(r, "$(PULL_REFS)")
				hasName = hasName || strings.Contains(r, "$(REPO_NAME)")
				if !firstRepo {
					t.Errorf("%s: has too many --repo. REMOVE THIS CHECK BEFORE MERGE", name)
				}
				for _, d := range []string{
					"$(REPO_NAME)",<commit_msg>remove --git-cache from the bazel job validation<commit_after>/*
Copyright 2017 The Kubernetes 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 config

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/ghodss/yaml"

	"k8s.io/test-infra/prow/kube"
)

// Loaded at TestMain.
var c *Config

func TestMain(m *testing.M) {
	conf, err := Load("../config.yaml")
	if err != nil {
		fmt.Printf("Could not load config: %v", err)
		os.Exit(1)
	}
	c = conf
	os.Exit(m.Run())
}

func replace(j *Presubmit, ks *Presubmit) error {
	name := strings.Replace(j.Name, "pull-kubernetes", "pull-security-kubernetes", -1)
	if name != ks.Name {
		return fmt.Errorf("%s should match %s", name, ks.Name)
	}
	j.Name = name
	j.RerunCommand = strings.Replace(j.RerunCommand, "pull-kubernetes", "pull-security-kubernetes", -1)
	j.Trigger = strings.Replace(j.Trigger, "pull-kubernetes", "pull-security-kubernetes", -1)
	j.Context = strings.Replace(j.Context, "pull-kubernetes", "pull-security-kubernetes", -1)

	if j.Agent == "kubernetes" {
		if len(j.Spec.Containers) != 1 {
			return fmt.Errorf("expected a single container for %s", name)
		}
		for i, arg := range j.Spec.Containers[0].Args {
			// handle --repo substitution for main repo
			if strings.HasPrefix(arg, "--repo=k8s.io/kubernetes") || strings.HasPrefix(arg, "--repo=k8s.io/$(REPO_NAME)") {
				j.Spec.Containers[0].Args[i] = strings.Replace(arg, "k8s.io/", "github.com/kubernetes-security/", 1)

				// handle upload bucket
			} else if strings.HasPrefix(arg, "--upload=") {
				j.Spec.Containers[0].Args[i] = "--upload=gs://kubernetes-security-jenkins/pr-logs"
			}
		}
	}

	j.re = ks.re
	if len(j.RunAfterSuccess) != len(ks.RunAfterSuccess) {
		return fmt.Errorf("length of RunAfterSuccess should match. - %s", name)
	}

	for i := range j.RunAfterSuccess {
		if err := replace(&j.RunAfterSuccess[i], &ks.RunAfterSuccess[i]); err != nil {
			return err
		}
	}

	return nil
}

func checkContext(t *testing.T, repo string, p Presubmit) {
	if p.Name != p.Context {
		t.Errorf("Context does not match job name: %s in %s", p.Name, repo)
	}
	for _, c := range p.RunAfterSuccess {
		checkContext(t, repo, c)
	}
}

func TestContextMatches(t *testing.T) {
	for repo, presubmits := range c.Presubmits {
		for _, p := range presubmits {
			checkContext(t, repo, p)
		}
	}
}

func checkRetest(t *testing.T, repo string, presubmits []Presubmit) {
	for _, p := range presubmits {
		expected := fmt.Sprintf("/test %s", p.Name)
		if p.RerunCommand != expected {
			t.Errorf("%s in %s rerun_command: %s != expected: %s", repo, p.Name, p.RerunCommand, expected)
		}
		checkRetest(t, repo, p.RunAfterSuccess)
	}
}

func TestRetestMatchJobsName(t *testing.T) {
	for repo, presubmits := range c.Presubmits {
		checkRetest(t, repo, presubmits)
	}
}

type SubmitQueueConfig struct {
	// this is the only field we need for the tests below
	RequiredRetestContexts string `json:"required-retest-contexts"`
}

func findRequired(t *testing.T, presubmits []Presubmit) []string {
	var required []string
	for _, p := range presubmits {
		if !p.AlwaysRun {
			continue
		}
		for _, r := range findRequired(t, p.RunAfterSuccess) {
			required = append(required, r)
		}
		if p.SkipReport {
			continue
		}
		required = append(required, p.Context)
	}
	return required
}

func TestRequiredRetestContextsMatch(t *testing.T) {
	b, err := ioutil.ReadFile("../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml")
	if err != nil {
		t.Fatalf("Could not load submit queue configmap: %v", err)
	}
	sqc := &SubmitQueueConfig{}
	if err = yaml.Unmarshal(b, sqc); err != nil {
		t.Fatalf("Could not parse submit queue configmap: %v", err)
	}
	required := strings.Split(sqc.RequiredRetestContexts, ",")

	running := findRequired(t, c.Presubmits["kubernetes/kubernetes"])

	for _, r := range required {
		found := false
		for _, s := range running {
			if s == r {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("Required context: %s does not always run: %s", r, running)
		}
	}
}

func TestConfigSecurityJobsMatch(t *testing.T) {
	conf, err := Load("../config.yaml")
	if err != nil {
		t.Fatalf("fail to load config for TestConfigSecurityJobsMatch : %v", err)
	}
	kp := conf.Presubmits["kubernetes/kubernetes"]
	sp := conf.Presubmits["kubernetes-security/kubernetes"]
	if len(kp) != len(sp) {
		t.Fatalf("length of kubernetes/kubernetes presubmits %d does not equal length of kubernetes-security/kubernetes presubmits %d", len(kp), len(sp))
	}
	for i, j := range kp {
		if err := replace(&j, &sp[i]); err != nil {
			t.Fatalf("[replace] : %v", err)
		}

		if !reflect.DeepEqual(j, sp[i]) {
			t.Fatalf("kubernetes/kubernetes prow config jobs do not match kubernetes-security/kubernetes jobs:\n%#v\nshould match: %#v", j, sp[i])
		}
	}
}

// checkDockerSocketVolumes returns an error if any volume uses a hostpath
// to the docker socket. we do not want to allow this
func checkDockerSocketVolumes(volumes []kube.Volume) error {
	for _, volume := range volumes {
		if volume.HostPath != nil && volume.HostPath.Path == "/var/run/docker.sock" {
			return errors.New("job uses HostPath with docker socket")
		}
	}
	return nil
}

// Make sure jobs are not using the docker socket as a host path
func TestJobDoesNotHaveDockerSocket(t *testing.T) {
	for _, pres := range c.Presubmits {
		for _, presubmit := range pres {
			if presubmit.Spec != nil {
				if err := checkDockerSocketVolumes(presubmit.Spec.Volumes); err != nil {
					t.Errorf("Error in presubmit: %v", err)
				}
			}
		}
	}

	for _, posts := range c.Postsubmits {
		for _, postsubmit := range posts {
			if postsubmit.Spec != nil {
				if err := checkDockerSocketVolumes(postsubmit.Spec.Volumes); err != nil {
					t.Errorf("Error in postsubmit: %v", err)
				}
			}
		}
	}

	for _, periodic := range c.Periodics {
		if periodic.Spec != nil {
			if err := checkDockerSocketVolumes(periodic.Spec.Volumes); err != nil {
				t.Errorf("Error in periodic: %v", err)
			}
		}
	}
}

func checkBazelPortContainer(c kube.Container, cache bool) error {
	if !cache {
		if len(c.Ports) != 0 {
			return errors.New("job does not use --cache-ssd and so should not set ports in spec")
		}
		return nil
	}

	if len(c.Ports) != 1 {
		return errors.New("job uses --cache-ssd and so needs to set ports in spec")
	} else if c.Ports[0].ContainerPort != 9999 {
		return errors.New("job uses --cache-ssd and so needs to have ContainerPort 9999")
	} else if c.Ports[0].HostPort != 9999 {
		return errors.New("job uses --cache-ssd and so needs to have HostPort 9999")
	}
	return nil
}

func checkBazelPortPresubmit(presubmits []Presubmit) error {
	for _, presubmit := range presubmits {
		if presubmit.Spec == nil {
			continue
		}
		hasCache := false
		for _, volume := range presubmit.Spec.Volumes {
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range presubmit.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", presubmit.Name, err)
			}
		}

		if err := checkBazelPortPresubmit(presubmit.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", presubmit.Name, err)
		}
	}

	return nil
}

func checkBazelPortPostsubmit(postsubmits []Postsubmit) error {
	for _, postsubmit := range postsubmits {
		hasCache := false
		for _, volume := range postsubmit.Spec.Volumes {
			// TODO(bentheelder): rewrite these tests and the entire caching layout...
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range postsubmit.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", postsubmit.Name, err)
			}
		}

		if err := checkBazelPortPostsubmit(postsubmit.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", postsubmit.Name, err)
		}
	}

	return nil
}

func checkBazelPortPeriodic(periodics []Periodic) error {
	for _, periodic := range periodics {
		hasCache := false
		for _, volume := range periodic.Spec.Volumes {
			if volume.Name == "cache-ssd" || volume.Name == "docker-graph" {
				hasCache = true
			}
		}

		for _, container := range periodic.Spec.Containers {
			if err := checkBazelPortContainer(container, hasCache); err != nil {
				return fmt.Errorf("%s: %v", periodic.Name, err)
			}
		}

		if err := checkBazelPortPeriodic(periodic.RunAfterSuccess); err != nil {
			return fmt.Errorf("%s: %v", periodic.Name, err)
		}
	}

	return nil
}

// Set the HostPort to 9999 for all bazel pods so that they are forced
// onto different nodes. Once pod affinity is GA, use that instead.
// Until https://github.com/kubernetes/community/blob/master/contributors/design-proposals/local-storage-overview.md
func TestBazelJobHasContainerPort(t *testing.T) {
	for _, pres := range c.Presubmits {
		if err := checkBazelPortPresubmit(pres); err != nil {
			t.Errorf("Error in presubmit: %v", err)
		}
	}

	for _, posts := range c.Postsubmits {
		if err := checkBazelPortPostsubmit(posts); err != nil {
			t.Errorf("Error in postsubmit: %v", err)
		}
	}

	if err := checkBazelPortPeriodic(c.Periodics); err != nil {
		t.Errorf("Error in periodic: %v", err)
	}
}

// Load the config and extract all jobs, including any child jobs inside
// RunAfterSuccess fields.
func allJobs() ([]Presubmit, []Postsubmit, []Periodic, error) {
	pres := []Presubmit{}
	posts := []Postsubmit{}
	peris := []Periodic{}

	{ // Find all presubmit jobs, including child jobs.
		q := []Presubmit{}

		for _, p := range c.Presubmits {
			for _, p2 := range p {
				q = append(q, p2)
			}
		}

		for len(q) > 0 {
			pres = append(pres, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	{ // Find all postsubmit jobs, including child jobs.
		q := []Postsubmit{}

		for _, p := range c.Postsubmits {
			for _, p2 := range p {
				q = append(q, p2)
			}
		}

		for len(q) > 0 {
			posts = append(posts, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	{ // Find all periodic jobs, including child jobs.
		q := []Periodic{}
		for _, p := range c.Periodics {
			q = append(q, p)
		}

		for len(q) > 0 {
			peris = append(peris, q[0])
			for _, p := range q[0].RunAfterSuccess {
				q = append(q, p)
			}
			q = q[1:]
		}
	}

	return pres, posts, peris, nil
}

// Validate any containers using a bazelbuild image, returning which bazelbuild tags are used.
// In particular ensure that:
//   * Presubmit, postsubmit jobs specify at least one --repo flag, the first of which uses PULL_REFS and REPO_NAME vars
//   * Prow injected vars like REPO_NAME, PULL_REFS, etc are only used on non-periodic jobs
//   * Deprecated --branch, --pull flags are not used
//   * Required --service-account, --upload, --job, --clean flags are present
func checkBazelbuildSpec(t *testing.T, name string, spec *kube.PodSpec, periodic bool) map[string]int {
	img := "gcr.io/k8s-testimages/bazelbuild"
	tags := map[string]int{}
	if spec == nil {
		return tags
	}
	// Tags look something like vDATE-SHA or vDATE-SHA-BAZELVERSION.
	// We want to match only on the date + sha
	tagRE := regexp.MustCompile(`^([^-]+-[^-]+)(-[^-]+)?$`)
	for _, c := range spec.Containers {
		parts := strings.SplitN(c.Image, ":", 2)
		var i, tag string // image:tag
		i = parts[0]
		if i != img {
			continue
		}
		if len(parts) == 1 {
			tag = "latest"
		} else {
			submatches := tagRE.FindStringSubmatch(parts[1])
			if submatches != nil {
				tag = submatches[1]
			} else {
				t.Errorf("bazelbuild tag '%s' doesn't match expected format", parts[1])
			}
		}
		tags[tag]++

		found := map[string][]string{}
		for _, a := range c.Args {
			parts := strings.SplitN(a, "=", 2)
			k := parts[0]
			v := "true"
			if len(parts) == 2 {
				v = parts[1]
			}
			found[k] = append(found[k], v)

			// Require --flag=FOO for easier processing
			if k == "--repo" && len(parts) == 1 {
				t.Errorf("%s: use --repo=FOO not --repo foo", name)
			}
		}

		if _, ok := found["--pull"]; ok {
			t.Errorf("%s: uses deprecated --pull arg, use --repo=org/repo=$(PULL_REFS) instead", name)
		}
		if _, ok := found["--branch"]; ok {
			t.Errorf("%s: uses deprecated --branch arg, use --repo=org/repo=$(PULL_REFS) instead", name)
		}

		for _, f := range []string{
			"--service-account",
			"--upload",
			"--job",
			"--clean",
		} {
			if _, ok := found[f]; !ok {
				t.Errorf("%s: missing %s flag", name, f)
			}
		}

		if v, ok := found["--repo"]; !ok {
			t.Errorf("%s: missing %s flag", name, "--repo")
		} else {
			firstRepo := true
			hasRefs := false
			hasName := false
			for _, r := range v {
				hasRefs = hasRefs || strings.Contains(r, "$(PULL_REFS)")
				hasName = hasName || strings.Contains(r, "$(REPO_NAME)")
				if !firstRepo {
					t.Errorf("%s: has too many --repo. REMOVE THIS CHECK BEFORE MERGE", name)
				}
				for _, d := range []string{
					"$(REPO_NAME)",
{'fraction_non_alphanumeric': 0.15130505709624797, 'fraction_numerical': 0.005487171881951654, 'mean_word_length': 3.3067220182021395, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6481191', 'n_tokens_mistral': 10565, 'n_tokens_neox': 9435, 'n_words': 2651}