github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/mkpod/main.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "path" 26 "strings" 27 28 "github.com/sirupsen/logrus" 29 v1 "k8s.io/api/core/v1" 30 "sigs.k8s.io/yaml" 31 32 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 33 "sigs.k8s.io/prow/pkg/kube" 34 "sigs.k8s.io/prow/pkg/pjutil" 35 "sigs.k8s.io/prow/pkg/pod-utils/decorate" 36 ) 37 38 type options struct { 39 prowJobPath string 40 buildID string 41 42 localMode bool 43 outputDir string 44 } 45 46 func (o *options) Validate() error { 47 if o.prowJobPath == "" { 48 return errors.New("required flag --prow-job was unset") 49 } 50 51 if !o.localMode && o.outputDir != "" { 52 return errors.New("out-dir may only be specified in --local mode") 53 } 54 55 return nil 56 } 57 58 func gatherOptions() options { 59 o := options{} 60 flag.StringVar(&o.prowJobPath, "prow-job", "", "ProwJob to decorate, - for stdin.") 61 flag.StringVar(&o.buildID, "build-id", "", "Build ID for the job run or 'snowflake' to generate one. Use 'snowflake' if tot is not used.") 62 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!") 63 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.") 64 flag.Parse() 65 return o 66 } 67 68 func main() { 69 o := gatherOptions() 70 if err := o.Validate(); err != nil { 71 logrus.Fatalf("Invalid options: %v", err) 72 } 73 74 var rawJob []byte 75 if o.prowJobPath == "-" { 76 raw, err := io.ReadAll(os.Stdin) 77 if err != nil { 78 logrus.WithError(err).Fatal("Could not read ProwJob YAML from stdin.") 79 } 80 rawJob = raw 81 } else { 82 raw, err := os.ReadFile(o.prowJobPath) 83 if err != nil { 84 logrus.WithError(err).Fatal("Could not open ProwJob YAML.") 85 } 86 rawJob = raw 87 } 88 89 var job prowapi.ProwJob 90 if err := yaml.Unmarshal(rawJob, &job); err != nil { 91 logrus.WithError(err).Fatal("Could not unmarshal ProwJob YAML.") 92 } 93 94 if o.buildID == "" && job.Status.BuildID != "" { 95 o.buildID = job.Status.BuildID 96 } 97 98 if strings.ToLower(o.buildID) == "snowflake" { 99 // No error possible since this won't use tot. 100 o.buildID, _ = pjutil.GetBuildID(job.Spec.Job, "") 101 logrus.WithField("build-id", o.buildID).Info("Generated build-id for job.") 102 } 103 104 if o.buildID == "" { 105 logrus.Warning("No BuildID found in ProwJob status or given with --build-id, GCS interaction will be poor.") 106 } 107 108 var pod *v1.Pod 109 var err error 110 if o.localMode { 111 outDir := o.outputDir 112 if outDir == "" { 113 prefix := strings.Join([]string{"prowjob-out", job.Spec.Job, o.buildID}, "-") 114 logrus.Infof("Creating temp directory for job output in %q with prefix %q.", os.TempDir(), prefix) 115 outDir, err = os.MkdirTemp("", prefix) 116 if err != nil { 117 logrus.WithError(err).Fatal("Could not create temp directory for job output.") 118 } 119 } else { 120 outDir = path.Join(outDir, o.buildID) 121 } 122 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 node.") 123 124 job.Status.BuildID = o.buildID 125 pod, err = makeLocalPod(job, outDir) 126 if err != nil { 127 logrus.WithError(err).Fatal("Could not decorate PodSpec for local mode.") 128 } 129 } else { 130 job.Status.BuildID = o.buildID 131 pod, err = decorate.ProwJobToPod(job) 132 if err != nil { 133 logrus.WithError(err).Fatal("Could not decorate PodSpec.") 134 } 135 } 136 137 // We need to remove the created-by-prow label, otherwise sinker will promptly clean this 138 // up as there is no associated prowjob 139 newLabels := map[string]string{} 140 for k, v := range pod.Labels { 141 if k == kube.CreatedByProw { 142 continue 143 } 144 newLabels[k] = v 145 } 146 pod.Labels = newLabels 147 148 pod.GetObjectKind().SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Pod")) 149 podYAML, err := yaml.Marshal(pod) 150 if err != nil { 151 logrus.WithError(err).Fatal("Could not marshal Pod YAML.") 152 } 153 fmt.Println(string(podYAML)) 154 } 155 156 func makeLocalPod(pj prowapi.ProwJob, outDir string) (*v1.Pod, error) { 157 pod, err := decorate.ProwJobToPodLocal(pj, outDir) 158 if err != nil { 159 return nil, err 160 } 161 162 // Prompt for emptyDir or hostPath replacements for all volume sources besides those two. 163 volsToFix := nonLocalVolumes(pod.Spec.Volumes) 164 if len(volsToFix) > 0 { 165 prompt := `For each of the following volumes specify one of: 166 - 'empty' to use an emptyDir; 167 - a path on the host to use hostPath; 168 - '' (nothing) to use the existing volume source and assume it is available in the cluster` 169 fmt.Fprintln(os.Stderr, prompt) 170 for _, vol := range volsToFix { 171 fmt.Fprintf(os.Stderr, "Volume %q: ", vol.Name) 172 173 var choice string 174 fmt.Scanln(&choice) 175 choice = strings.TrimSpace(choice) 176 switch { 177 case choice == "": 178 // Leave the VolumeSource as is. 179 case choice == "empty" || strings.ToLower(choice) == "emptydir": 180 vol.VolumeSource = v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}} 181 default: 182 vol.VolumeSource = v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: choice}} 183 } 184 } 185 } 186 187 return pod, nil 188 } 189 190 func nonLocalVolumes(vols []v1.Volume) []*v1.Volume { 191 var res []*v1.Volume 192 for i, vol := range vols { 193 if vol.HostPath == nil && vol.EmptyDir == nil { 194 res = append(res, &vols[i]) 195 } 196 } 197 return res 198 }