func findContainerIndex(job *v1.Job, containerName string) (int, error) {
	for index, container := range job.Spec.Template.Spec.Containers {
		if container.Name == containerName {
			return index, nil
		}
	}
	return 0, fmt.Errorf("Specified container %s does not exist in the template", containerName)
}
findContainerIndex finds target container from job definition.
package job
import (
	"context"
	"crypto/md5"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
	shellwords "github.com/mattn/go-shellwords"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/ghodss/yaml"
	v1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)
type Job struct {
	client kubernetes.Interface
	CurrentJob *v1.Job
	Args []string
	Container string
	Timeout time.Duration
}
func NewJob(configFile, currentFile, command, container string, timeout time.Duration) (*Job, error) {
	if len(configFile) == 0 {
		return nil, errors.New("Config file is required")
	}
	if len(currentFile) == 0 {
		return nil, errors.New("Template file is required")
	}
	if len(container) == 0 {
		return nil, errors.New("Container is required")
	}
	client, err := newClient(os.ExpandEnv(configFile))
	if err != nil {
		return nil, err
	}
	downloaded, err := downloadFile(currentFile)
	if err != nil {
		return nil, err
	}
	bytes, err := ioutil.ReadFile(downloaded)
	if err != nil {
		return nil, err
	}
	var currentJob v1.Job
	err = yaml.Unmarshal(bytes, &currentJob)
	if err != nil {
		return nil, err
	}
	currentJob.SetName(generateRandomName(currentJob.Name))
	p := shellwords.NewParser()
	args, err := p.Parse(command)
	log.Info("Received args:")
	for _, arg := range args {
		log.Info(arg)
	}
	if err != nil {
		return nil, err
	}
	return &Job{
		client,
		&currentJob,
		args,
		container,
		timeout,
	}, nil
}
func downloadFile(rawurl string) (string, error) {
	if !strings.HasPrefix(rawurl, "https://") {
		return rawurl, nil
	}
	req, err := http.NewRequest("GET", rawurl, nil)
	if err != nil {
		return rawurl, err
	}
	token := os.Getenv("GITHUB_TOKEN")
	if len(token) > 0 {
		req.Header.Set("Authorization", "token "+token)
		req.Header.Set("Accept", "application/vnd.github.v3.raw")
	}
	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		return rawurl, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return rawurl, fmt.Errorf("Could not read template file from %s", rawurl)
	}
	hasher := md5.New()
	hasher.Write([]byte(rawurl))
	downloaded := "/tmp/" + hex.EncodeToString(hasher.Sum(nil)) + ".yml"
	out, err := os.Create(downloaded)
	if err != nil {
		return rawurl, err
	}
	defer out.Close()
	_, err = io.Copy(out, resp.Body)
	return downloaded, err
}
func generateRandomName(name string) string {
	return fmt.Sprintf("%s-%s", name, secureRandomStr(16))
}
func secureRandomStr(b int) string {
	k := make([]byte, b)
	if _, err := rand.Read(k); err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", k)
}
func (j *Job) Validate() error {
	_, err := findContainerIndex(j.CurrentJob, j.Container)
	return err
}
func (j *Job) RunJob() (*v1.Job, error) {
	ctx := context.Background()
	currentJob := j.CurrentJob.DeepCopy()
	index, err := findContainerIndex(currentJob, j.Container)
	if err != nil {
		return nil, err
	}
	if len(j.Args) > 0 {
		currentJob.Spec.Template.Spec.Containers[index].Args = j.Args
	}
	resultJob, err := j.client.BatchV1().Jobs(j.CurrentJob.Namespace).Create(ctx, currentJob, metav1.CreateOptions{})
	if err != nil {
		return nil, err
	}
	return resultJob, nil
}