Skip to content

Commit

Permalink
Add support for local pods to mkpod and include a script for running …
Browse files Browse the repository at this point in the history
…local pods with kind.
  • Loading branch information
cjwagner committed Jul 24, 2019
1 parent dbfce42 commit b63f12a
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 3 deletions.
2 changes: 2 additions & 0 deletions prow/cmd/mkpod/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pj.yaml
pod.yaml
7 changes: 7 additions & 0 deletions prow/cmd/mkpod/local-kind-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Example kind config for getting output on the machine running kind instead of the contain 'node'.
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- extraMounts:
- containerPath: /output
hostPath: /tmp/prowjob-out
52 changes: 52 additions & 0 deletions prow/cmd/mkpod/local-kind.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Copyright 2019 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.

set -o errexit
set -o nounset
set -o pipefail

job=${1:-pull-test-infra-yamllint}
config=${CONFIG_PATH:-$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../config.yaml)}
job_config=${JOB_CONFIG_PATH-$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../../config/jobs)}

echo job=${job}
echo config=${config}
echo job_config=${job_config}

if [[ -n ${job_config} ]]
then
job_config="--job-config-path=${job_config}"
fi

found="false"
for clust in $(kind get clusters)
do
if [[ ${clust} == "mkpod" ]]
then
found="true"
fi
done

if [[ ${found} == "false" ]]
then
kind create cluster --name=mkpod --config=local-kind-config.yaml --wait=5m
fi
export KUBECONFIG="$(kind get kubeconfig-path --name="mkpod")"

bazel run //prow/cmd/mkpj -- --config-path=${config} ${job_config} --job=${job} > ${PWD}/pj.yaml
bazel run //prow/cmd/mkpod -- --build-id=snowflake --prow-job=${PWD}/pj.yaml --local --out-dir=/output/${job} > ${PWD}/pod.yaml

pod=$(kubectl apply -f ${PWD}/pod.yaml | cut -d ' ' -f 1)
kubectl get ${pod} -w
82 changes: 79 additions & 3 deletions prow/cmd/mkpod/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"io/ioutil"
"k8s.io/test-infra/prow/pjutil"
"os"
"path"
"strings"

"github.com/sirupsen/logrus"
Expand All @@ -36,20 +37,29 @@ import (
type options struct {
prowJobPath string
buildID string

localMode bool
outputDir string
}

func (o *options) Validate() error {
if o.prowJobPath == "" {
return errors.New("required flag --prow-job was unset")
}

if !o.localMode && o.outputDir != "" {
return errors.New("out-dir may only be specified in --local mode")
}

return nil
}

func gatherOptions() options {
o := options{}
flag.StringVar(&o.prowJobPath, "prow-job", "", "ProwJob to decorate, - for stdin.")
flag.StringVar(&o.buildID, "build-id", "", "Build ID for the job run or 'snowflake' to generate one. Use 'snowflake' if tot is not used.")
flag.BoolVar(&o.localMode, "local", false, "Configures pod utils for local mode which avoids uploading to GCS and the need for credentials. Instead, files are copied to a directory on the host. Hint: This works great with kind!")
flag.StringVar(&o.outputDir, "out-dir", "", "Only allowed in --local mode. This is the directory to 'upload' to instead of GCS. If unspecified a temp dir is created.")
flag.Parse()
return o
}
Expand Down Expand Up @@ -94,9 +104,31 @@ func main() {
logrus.Warning("No BuildID found in ProwJob status or given with --build-id, GCS interaction will be poor.")
}

pod, err := decorate.ProwJobToPod(job, o.buildID)
if err != nil {
logrus.WithError(err).Fatal("Could not decorate PodSpec.")
var pod *v1.Pod
var err error
if o.localMode {
outDir := o.outputDir
if outDir == "" {
prefix := strings.Join([]string{"prowjob-out", job.Spec.Job, o.buildID}, "-")
logrus.Infof("Creating temp directory for job output in %q with prefix %q.", os.TempDir(), prefix)
outDir, err = ioutil.TempDir("", prefix)
if err != nil {
logrus.WithError(err).Fatal("Could not create temp directory for job output.")
}
} else {
outDir = path.Join(outDir, o.buildID)
}
logrus.WithField("out-dir", outDir).Info("Pod-utils configured for local mode. Instead of uploading to GCS, files will be copied to an output dir on the host.")

pod, err = makeLocalPod(job, o.buildID, outDir)
if err != nil {
logrus.WithError(err).Fatal("Could not decorate PodSpec for local mode.")
}
} else {
pod, err = decorate.ProwJobToPod(job, o.buildID)
if err != nil {
logrus.WithError(err).Fatal("Could not decorate PodSpec.")
}
}

pod.GetObjectKind().SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Pod"))
Expand All @@ -106,3 +138,47 @@ func main() {
}
fmt.Println(string(podYAML))
}

func makeLocalPod(pj prowapi.ProwJob, buildID, outDir string) (*v1.Pod, error) {
pod, err := decorate.ProwJobToPodLocal(pj, buildID, outDir)
if err != nil {
return nil, err
}

// Prompt for emptyDir or hostPath replacements for all volume sources besides those two.
volsToFix := nonLocalVolumes(pod.Spec.Volumes)
if len(volsToFix) > 0 {
prompt := `For each of the following volumes specify one of:
- 'empty' to use an emptyDir;
- a path on the host to use hostPath;
- '' (nothing) to use the existing volume source and assume it is available in the cluster`
fmt.Fprintln(os.Stderr, prompt)
for _, vol := range volsToFix {
fmt.Fprintf(os.Stderr, "Volume %q: ", vol.Name)

var choice string
fmt.Scanln(&choice)
choice = strings.TrimSpace(choice)
switch {
case choice == "":
// Leave the VolumeSource as is.
case choice == "empty" || strings.ToLower(choice) == "emptydir":
vol.VolumeSource = v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}
default:
vol.VolumeSource = v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: choice}}
}
}
}

return pod, nil
}

func nonLocalVolumes(vols []v1.Volume) []*v1.Volume {
var res []*v1.Volume
for i, vol := range vols {
if vol.HostPath == nil && vol.EmptyDir == nil {
res = append(res, &vols[i])
}
}
return res
}

0 comments on commit b63f12a

Please sign in to comment.