diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b853e0630..707d40fe8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Check out code uses: actions/checkout@v2 @@ -23,6 +23,7 @@ jobs: key: ${{ runner.os }}-funnel-bin-${{ hashFiles('**/go.sum') }}-${{ github.ref }} restore-keys: | ${{ runner.os }}-funnel-bin-${{ github.ref }} + ${{ runner.os }}-funnel-bin- - name: Build Funnel (if cache doesn't exist) run: | diff --git a/.github/workflows/k8s.yaml b/.github/workflows/k8s.yaml index 2ff197f23..2f325a9d6 100644 --- a/.github/workflows/k8s.yaml +++ b/.github/workflows/k8s.yaml @@ -35,8 +35,9 @@ jobs: - name: Deploy Funnel run: | helm repo add ohsu https://ohsu-comp-bio.github.io/helm-charts - # 'local-path' is a k3d specific storage class - # Ref: https://k3d.io/v5.7.4/usage/k3s/#local-path-provisioner + + # 'local-path' is a k3d specific storage class used to automatically create a PersistentVolume + # - Ref: https://k3d.io/v5.7.4/usage/k3s/#local-path-provisioner helm upgrade --install funnel ohsu/funnel --set storage.className=local-path --set storage.provisioner=local-path # Wait for the Deployment to be available @@ -45,6 +46,9 @@ jobs: # Port-forward the service kubectl port-forward svc/funnel 8000:8000 & + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + - name: Submit Task run: | export PATH="$PATH:$(pwd)" diff --git a/Dockerfile b/Dockerfile index 71520ed07..32b259d93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,6 @@ RUN --mount=type=cache,target=/root/.cache/go-build make build # final stage FROM alpine WORKDIR /opt/funnel -VOLUME /opt/funnel/funnel-work-dir EXPOSE 8000 9090 ENV PATH="/app:${PATH}" COPY --from=build-env /go/src/github.com/ohsu-comp-bio/funnel/funnel /app/ diff --git a/README.md b/README.md index b00de8d85..2902a9668 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,23 @@ -[![Build Status](https://img.shields.io/github/actions/workflow/status/ohsu-comp-bio/funnel/tests.yaml)](https://github.com/ohsu-comp-bio/funnel/actions/workflows/tests.yaml) -[![Compliance Tests Status](https://img.shields.io/github/actions/workflow/status/ohsu-comp-bio/funnel/compliance-test.yaml?label=Compliance%20Tests)](https://github.com/ohsu-comp-bio/funnel/actions/workflows/compliance-test.yaml) -[![Gitter](https://badges.gitter.im/ohsu-comp-bio/funnel.svg)](https://gitter.im/ohsu-comp-bio/funnel) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Godoc](https://img.shields.io/badge/godoc-ref-blue.svg)](http://godoc.org/github.com/ohsu-comp-bio/funnel) +[![Build Status][build-badge]][build] +[![Compliance Tests Status][compliance-tests-badge]][compliance-tests] +[![Gitter][gitter-badge]][gitter] +[![License: MIT][license-badge]][license] +[![Godoc][godoc-badge]][godoc] + +[build-badge]: https://img.shields.io/github/actions/workflow/status/ohsu-comp-bio/funnel/tests.yaml +[build]: https://github.com/ohsu-comp-bio/funnel/actions/workflows/tests.yaml + +[compliance-tests]: https://github.com/ohsu-comp-bio/funnel/actions/workflows/compliance-test.yaml +[compliance-tests-badge]: https://img.shields.io/github/actions/workflow/status/ohsu-comp-bio/funnel/compliance-test.yaml?label=Compliance%20Tests + +[gitter-badge]: https://badges.gitter.im/ohsu-comp-bio/funnel.svg +[gitter]: https://gitter.im/ohsu-comp-bio/funnel + +[license-badge]: https://img.shields.io/badge/License-MIT-yellow.svg +[license]: https://opensource.org/licenses/MIT + +[godoc-badge]: https://img.shields.io/badge/godoc-ref-blue.svg +[godoc]: http://godoc.org/github.com/ohsu-comp-bio/funnel diff --git a/compute/kubernetes/backend.go b/compute/kubernetes/backend.go index ba7ea4590..334f2abbb 100644 --- a/compute/kubernetes/backend.go +++ b/compute/kubernetes/backend.go @@ -14,7 +14,6 @@ import ( v1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" k8errors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -68,13 +67,17 @@ func NewBackend(ctx context.Context, conf config.Kubernetes, reader tes.ReadOnly } b := &Backend{ - client: clientset.BatchV1().Jobs(conf.Namespace), - namespace: conf.Namespace, - template: conf.Template, - event: writer, - database: reader, - log: log, - config: kubeconfig, + bucket: conf.Bucket, + region: conf.Region, + client: clientset.BatchV1().Jobs(conf.Namespace), + namespace: conf.Namespace, + template: conf.Template, + pvTemplate: conf.PVTemplate, + pvcTemplate: conf.PVCTemplate, + event: writer, + database: reader, + log: log, + config: kubeconfig, } if !conf.DisableReconciler { @@ -87,9 +90,13 @@ func NewBackend(ctx context.Context, conf config.Kubernetes, reader tes.ReadOnly // Backend represents the local backend. type Backend struct { + bucket string + region string client batchv1.JobInterface namespace string template string + pvTemplate string + pvcTemplate string event events.Writer database tes.ReadOnlyServer log *logger.Logger @@ -133,7 +140,7 @@ func (b *Backend) Close() { //TODO: close database? } -// Create the Funnel Worker job +// Create the Funnel Worker job from kubernetes-template.yaml // Executor job is created in worker/kubernetes.go#Run func (b *Backend) createJob(task *tes.Task) (*v1.Job, error) { submitTpl, err := template.New(task.Id).Parse(b.template) @@ -155,7 +162,7 @@ func (b *Backend) createJob(task *tes.Task) (*v1.Job, error) { "DiskGb": res.GetDiskGb(), }) if err != nil { - return nil, fmt.Errorf("executing template: %v", err) + return nil, fmt.Errorf("executing Worker template: %v", err) } decode := scheme.Codecs.UniversalDeserializer().Decode @@ -171,43 +178,74 @@ func (b *Backend) createJob(task *tes.Task) (*v1.Job, error) { return job, nil } -func (b *Backend) createPVC(ctx context.Context, taskID string, resources *tes.Resources) error { - clientset, err := kubernetes.NewForConfig(b.config) // You'll need to store the config during NewBackend +// Create the Worker/Executor PVC from config/kubernetes-pvc.yaml +// TODO: Move this config file to Helm Charts so users can see/customize it +func (b *Backend) createPVC(task *tes.Task) (*corev1.PersistentVolumeClaim, error) { + // Load templates + pvcTpl, err := template.New(task.Id).Parse(b.pvcTemplate) if err != nil { - return fmt.Errorf("getting kubernetes client: %v", err) + return nil, fmt.Errorf("parsing template: %v", err) + } + + // Template parameters + var buf bytes.Buffer + err = pvcTpl.Execute(&buf, map[string]interface{}{ + "TaskId": task.Id, + "Namespace": b.namespace, + "Bucket": b.bucket, + "Region": b.region, + }) + if err != nil { + return nil, fmt.Errorf("executing PVC template: %v", err) } - storageSize := resource.NewQuantity(1024*1024*1024, resource.BinarySI) // 1Gi default - if resources != nil && resources.DiskGb > 0 { - storageSize = resource.NewQuantity(int64(resources.DiskGb*1024*1024*1024), resource.BinarySI) - } - - pvc := &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("funnel-pvc-%s", taskID), - Labels: map[string]string{ - "app": "funnel", - "taskId": taskID, - }, - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{ - corev1.ReadWriteOnce, - }, - Resources: corev1.VolumeResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: *storageSize, - }, - }, - }, - } - - _, err = clientset.CoreV1().PersistentVolumeClaims(b.namespace).Create(ctx, pvc, metav1.CreateOptions{}) + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err := decode(buf.Bytes(), nil, nil) if err != nil { - return fmt.Errorf("creating shared PVC: %v", err) + return nil, fmt.Errorf("decoding PVC spec: %v", err) } - return nil + fmt.Println("PVC spec: ", string(buf.Bytes())) + pvc, ok := obj.(*corev1.PersistentVolumeClaim) + if !ok { + return nil, fmt.Errorf("failed to decode PVC spec") + } + return pvc, nil +} + +// Create the Worker/Executor PV from config/kubernetes-pv.yaml +// TODO: Move this config file to Helm Charts so users can see/customize it +func (b *Backend) createPV(task *tes.Task) (*corev1.PersistentVolume, error) { + // Load templates + pvTpl, err := template.New(task.Id).Parse(b.pvTemplate) + if err != nil { + return nil, fmt.Errorf("parsing template: %v", err) + } + + // Template parameters + var buf bytes.Buffer + err = pvTpl.Execute(&buf, map[string]interface{}{ + "TaskId": task.Id, + "Namespace": b.namespace, + "Bucket": b.bucket, + "Region": b.region, + }) + if err != nil { + return nil, fmt.Errorf("executing PV template: %v", err) + } + + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err := decode(buf.Bytes(), nil, nil) + if err != nil { + return nil, fmt.Errorf("decoding PV spec: %v", err) + } + + fmt.Println("PV spec: ", string(buf.Bytes())) + pv, ok := obj.(*corev1.PersistentVolume) + if !ok { + return nil, fmt.Errorf("failed to decode PV spec") + } + return pv, nil } // Add this helper function for PVC cleanup @@ -235,13 +273,35 @@ func (b *Backend) Submit(ctx context.Context, task *tes.Task) error { // Create a new background context instead of inheriting from the potentially canceled one submitCtx := context.Background() - // TODO: Update this so that a PVC is only created if the task has inputs or outputs + // TODO: Update this so that a PVC/PV is only created if the task has inputs or outputs // If the task has either inputs or outputs, then create a PVC // shared between the Funnel Worker and the Executor // e.g. `if len(task.Inputs) > 0 || len(task.Outputs) > 0 {}` - err := b.createPVC(submitCtx, task.Id, task.GetResources()) + pvc, err := b.createPVC(task) + if err != nil { + return fmt.Errorf("creating shared storage PVC: %v", err) + } + + pv, err := b.createPV(task) + if err != nil { + return fmt.Errorf("creating shared storage PV: %v", err) + } + + clientset, err := kubernetes.NewForConfig(b.config) + if err != nil { + return fmt.Errorf("getting kubernetes client: %v", err) + } + + // Create PVC + pvc, err = clientset.CoreV1().PersistentVolumeClaims(b.namespace).Create(context.Background(), pvc, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("creating PVC: %v", err) + } + + // Create PV + pv, err = clientset.CoreV1().PersistentVolumes().Create(context.Background(), pv, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("creating shared storage: %v", err) + return fmt.Errorf("creating PV: %v", err) } // Create the worker job diff --git a/config/config.go b/config/config.go index 978aba8da..32ed83707 100644 --- a/config/config.go +++ b/config/config.go @@ -409,6 +409,10 @@ func (h FTPStorage) Valid() bool { // Kubernetes describes the configuration for the Kubernetes compute backend. type Kubernetes struct { + // The bucket to use for the task's Working Directory + Bucket string + // The region to use for the task's Bucket + Region string // The executor used to execute tasks. Available executors: docker, kubernetes Executor string // Turn off task state reconciler. When enabled, Funnel communicates with Kuberenetes @@ -428,6 +432,10 @@ type Kubernetes struct { ExecutorTemplate string // ExecutorTemplateFile is the path to the executor template. ExecutorTemplateFile string + // Worker/Executor PV job template. + PVTemplate string + // Worker/Executor PVC job template. + PVCTemplate string // Path to the Kubernetes configuration file, otherwise assumes the Funnel server is running in a pod and // attempts to use https://godoc.org/k8s.io/client-go/rest#InClusterConfig to infer configuration. ConfigFile string diff --git a/config/default-config.yaml b/config/default-config.yaml index 21b7b409a..15cf647f3 100644 --- a/config/default-config.yaml +++ b/config/default-config.yaml @@ -299,7 +299,7 @@ AWSBatch: # Kubernetes describes the configuration for the Kubernetes compute backend. Kubernetes: # The executor used to execute tasks. Available executors: docker, kubernetes - Executor: "kubernetes" + Executor: "docker" # Turn off task state reconciler. When enabled, Funnel communicates with Kubernetes # to find tasks that are stuck in a queued state or errored and # updates the task state accordingly. diff --git a/config/default.go b/config/default.go index 2dfde8f1c..f873a859c 100644 --- a/config/default.go +++ b/config/default.go @@ -164,11 +164,17 @@ func DefaultConfig() Config { kubernetesTemplate := intern.MustAsset("config/kubernetes-template.yaml") executorTemplate := intern.MustAsset("config/kubernetes-executor-template.yaml") + pvTemplate := intern.MustAsset("config/kubernetes-pv.yaml") + pvcTemplate := intern.MustAsset("config/kubernetes-pvc.yaml") c.Kubernetes.Executor = "docker" c.Kubernetes.Namespace = "default" c.Kubernetes.ServiceAccount = "funnel-sa" c.Kubernetes.Template = string(kubernetesTemplate) c.Kubernetes.ExecutorTemplate = string(executorTemplate) + c.Kubernetes.Bucket = "" + c.Kubernetes.Region = "" + c.Kubernetes.PVTemplate = string(pvTemplate) + c.Kubernetes.PVCTemplate = string(pvcTemplate) c.Kubernetes.ReconcileRate = reconcile return c diff --git a/config/internal/bundle.go b/config/internal/bundle.go index c4f9a5310..a707627bd 100644 --- a/config/internal/bundle.go +++ b/config/internal/bundle.go @@ -1,12 +1,14 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: +// config/kubernetes-pvc.yaml (309B) // config/gridengine-template.txt (346B) // config/pbs-template.txt (361B) // config/slurm-template.txt (415B) -// config/kubernetes-executor-template.yaml (1.764kB) +// config/kubernetes-pv.yaml (560B) +// config/kubernetes-executor-template.yaml (1.232kB) // config/default-config.yaml (11.655kB) // config/htcondor-template.txt (505B) -// config/kubernetes-template.yaml (1.34kB) +// config/kubernetes-template.yaml (1.483kB) package config @@ -74,6 +76,26 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } +var _configKubernetesPvcYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x8f\xc1\x6a\xf3\x30\x10\x84\xef\x7a\x8a\x81\xff\xec\xfc\xe4\xaa\x6b\x28\xa5\x87\x84\x50\x8a\x73\xde\xca\xd3\x22\x6c\x4b\xaa\x56\x32\x2d\x26\xef\x5e\x6c\x6c\x4a\x8e\xb3\x3b\xf3\xc1\xf7\x0f\xb7\x98\x7b\xe6\xff\x4f\xdf\x74\xb5\xc4\x8c\x6b\x7b\x32\x92\x7c\xcb\xac\x3e\x06\x8b\xe9\x68\x7a\x1f\x3a\x8b\xeb\x72\xd1\xc2\x50\xda\x38\xd4\x91\xa7\x41\xfc\x68\x46\x16\xe9\xa4\x88\x35\x40\x90\x91\x16\x1f\x35\x04\x0e\x4d\x9a\x5c\x33\xcf\x87\x37\xd1\xfe\xa5\xbb\xdf\xb7\xb7\x26\x71\xb4\x98\x67\x1c\x2e\x7b\xc4\xfa\x1d\xe4\x9d\x83\x2e\x18\x40\x52\xda\x39\x6b\x2e\x2b\x64\x99\xfd\xf1\x34\xd1\x2d\x6d\x71\x8e\xaa\xe7\xd8\x71\x1b\x37\x78\xa5\x74\xb7\xec\x0b\xcf\x12\x7e\x0c\x90\xa9\xb1\x66\xb7\x17\x32\xbf\x2a\xb5\x6c\x09\xd0\x12\xb3\x7c\xd2\xe2\xf8\xec\x0d\x30\xad\x76\x97\x47\x97\x07\x95\xdf\x00\x00\x00\xff\xff\x01\x27\x69\xed\x35\x01\x00\x00") + +func configKubernetesPvcYamlBytes() ([]byte, error) { + return bindataRead( + _configKubernetesPvcYaml, + "config/kubernetes-pvc.yaml", + ) +} + +func configKubernetesPvcYaml() (*asset, error) { + bytes, err := configKubernetesPvcYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "config/kubernetes-pvc.yaml", size: 309, mode: os.FileMode(0644), modTime: time.Unix(1732590327, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe9, 0xc, 0x1f, 0x92, 0xa1, 0xb3, 0x7, 0x8, 0xb0, 0x29, 0x2f, 0x8e, 0x77, 0x9d, 0x10, 0x18, 0xc6, 0xcd, 0x90, 0xbf, 0xdf, 0x92, 0xf6, 0xf5, 0x81, 0x70, 0x47, 0xb6, 0x35, 0x85, 0x1a, 0x9d}} + return a, nil +} + var _configGridengineTemplateTxt = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x90\xcd\x4a\xc4\x30\x14\x85\xf7\x79\x8a\x6b\xc7\x59\x26\xed\x0b\xb8\xb2\x30\xb8\x71\x21\x82\x4b\x69\xc9\x0d\x13\x32\xf9\xe1\x26\x51\x30\xe4\xdd\xa5\x69\x11\x0a\x75\x76\x97\xc3\x77\x3e\xb8\xe7\xf4\xd0\xcf\xda\xf5\xf3\x14\xaf\xec\xf4\x08\xfc\x15\x4a\x11\xef\x53\x34\x2f\xb2\xd6\x96\xf8\x25\xf9\xf0\x64\x46\x4d\xb5\xf6\x2a\x3b\x87\x37\x1e\x93\xf4\x39\x35\x00\xff\x03\x90\x88\x95\xa2\x15\x38\x04\xf1\x1c\x72\x84\x01\x78\xad\xac\x94\x40\xda\x25\x05\xdd\x52\x0f\x08\x36\x68\x38\xcb\x6e\x85\x1a\xc0\x01\x9d\x6c\xd7\x56\x7f\x9b\xec\x65\x86\x41\x1c\x19\x6e\x70\xfd\xfc\xb2\x68\x9f\xce\x62\x50\x97\x6e\x83\x8f\x3d\xa3\x8e\xe6\xae\x48\x45\xfd\x83\x7f\xa6\x15\xdf\xa9\xd8\xfa\x20\x7c\x7b\x32\x48\x40\xd9\x01\xe7\x69\x59\x6c\xdc\x6d\xf7\x1b\x00\x00\xff\xff\xcf\x92\x30\x7f\x5a\x01\x00\x00") func configGridengineTemplateTxtBytes() ([]byte, error) { @@ -134,7 +156,27 @@ func configSlurmTemplateTxt() (*asset, error) { return a, nil } -var _configKubernetesExecutorTemplateYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x95\xc1\x8e\xdb\x36\x10\x86\xef\x7e\x8a\x81\xbc\x45\x2f\x91\x57\x7b\xe9\x41\x40\x0f\x0b\x6f\xb0\xd9\xa0\x71\x16\xc9\x36\x3d\x14\x3d\x50\xd4\xc8\x66\x2d\x92\xea\xcc\xd0\x59\x43\xf5\xbb\x17\x94\x64\x45\xda\x38\x29\xa2\x13\xc5\x19\x7e\xf3\xcf\xaf\xa1\xbd\x84\x27\xc5\x7b\x78\xfd\x8c\x3a\x88\xa7\x85\x6a\xcc\x27\x24\x36\xde\xe5\x50\x28\xd1\xbb\xeb\xc3\xcd\x62\x6f\x5c\x99\xc3\x5b\x5f\x2c\x2c\x8a\x2a\x95\xa8\x7c\x01\xe0\x94\xc5\x1c\xda\x76\x15\x09\x0f\xe5\xe9\x94\xb6\xed\xea\xad\x2f\xe2\x72\x08\x73\xa3\x74\x9f\xb3\x39\xbf\x75\xb1\x5a\x15\x58\x73\x84\x00\xfc\xed\x8b\xf4\xbb\x28\x6e\x50\xc7\xd4\x42\xe9\xbd\xaf\xaa\xdf\x8c\x35\x92\x43\xb6\x00\xd0\xde\x36\x35\x8a\xf1\x8e\x73\xb8\x59\x00\x08\xda\xa6\x56\x82\x3d\xf9\x7c\x30\x3e\x84\x2c\x8a\xe4\xd1\xd7\x46\x1f\x73\xd8\xe0\x01\x69\x08\x31\xd2\xc1\x68\xbc\xd5\xda\x07\x27\x9b\x4e\x4a\x15\x9c\xc3\x3a\x65\x35\xe4\x2c\xe1\x23\x4a\x68\x80\x8f\xb6\x36\x6e\xcf\x50\x91\xb7\xf0\xd9\xd3\x1e\x4a\x43\x20\x1e\x44\xd1\x16\x05\x1a\x25\x3b\x1e\x0e\x19\x67\x64\xed\x9d\x28\xe3\x90\xf8\xac\x24\x1d\x8c\xe3\x08\x4c\x4b\x43\xe7\x74\x00\x63\xd5\x16\x73\x28\x02\x1f\x0b\xff\x3c\x6e\x6b\x6f\xad\x8a\x5f\xe0\xcf\xe4\xba\x30\xee\x9a\x77\xc9\x2b\x48\x52\x9d\xfc\x35\xa6\x28\xda\x8e\x05\xfa\x22\xff\x4e\xde\xa2\xfe\x35\xa1\x12\x04\x15\xf5\xa2\x16\x4f\xc7\xa8\xba\x30\xae\x04\x1b\xfb\x06\xd9\x61\xd7\x10\xd2\xcf\x0c\x95\xa9\x91\x2f\x13\x1a\x45\xe8\x64\xc4\x18\x64\xa8\x3c\x81\x72\xc7\xae\xf9\xce\x75\x53\x19\x2c\xc1\xb8\x0e\x2a\x8a\xf7\x97\x51\x31\x3a\x38\xfa\xc5\x50\xa4\x0b\x96\xce\xce\xa3\xde\x79\x48\x66\xee\xe6\x3d\xd2\xb8\xed\x4c\x98\x72\xe5\xf8\xc9\x92\x91\xc1\xa8\x03\x19\x39\xc6\xd3\xf8\x2c\x53\xdf\x28\xb8\x5b\xfe\x9d\x23\x30\x8b\x42\x3f\x04\x07\x8a\x81\xbc\x97\xa8\x08\x1d\x07\xc2\x89\x85\xba\xab\xea\x5d\xa7\xfc\x6c\x98\xfe\xe6\x37\x1f\xc6\xaa\x6f\x33\x9d\xcc\xfb\xcb\x11\x68\xdb\xd5\x43\x5c\xbd\x8c\x3c\x86\xba\x3e\xcf\xf0\x6d\xfd\x59\x1d\xf9\x87\xa7\x24\xb2\xd7\x7d\xee\x84\x1e\x25\x19\xb7\xbd\x33\xd4\x25\xfc\xe1\x69\x5f\x1a\x9a\x24\x10\xb2\x0f\xa4\x71\x36\x66\x84\xff\x04\x64\x99\xed\x01\xe8\x26\x44\x88\xa9\xc0\x21\xac\xd6\x4d\x60\xc8\x20\x3d\x9d\x62\xe1\x26\x70\x5c\x00\xd6\x8c\x10\x57\xc9\x4d\x96\xd9\x24\xae\x70\x26\x28\x3e\x16\xad\xa7\xe3\x84\xf5\x41\xd9\xfb\x02\xb2\xd5\x80\x6b\xc8\x38\xa9\x20\xf9\x69\x95\x55\xf7\xc9\x10\xee\x50\x35\x63\x0f\xff\xe5\xdd\x37\xd8\xd8\xec\xd0\x22\xa9\x3a\x65\xf1\x34\x98\x3e\x94\xb9\x33\xbc\xff\x5e\x9d\x3e\x3e\x2f\x94\x65\x97\x2a\x8d\x8b\x83\xaf\x83\xc5\x77\xf1\xa6\x4d\xdc\x5a\x2e\x97\x70\xf7\x1e\x36\xef\x9f\x60\xfd\xe6\x76\x73\xff\x1a\x9e\xde\x3c\x7c\x1c\xc3\x6d\x4b\xca\x6d\x11\xae\x4c\xf9\xfc\x0a\xae\x8c\xa0\x85\xfc\x57\x58\x7d\xea\x60\x3c\xa9\xf3\x62\xc0\x86\x96\xd2\xb6\xbd\xfa\x7a\xc4\xa0\xbf\xf0\x8f\x4a\x76\xb1\xe7\x0e\xbb\x1a\x6f\x52\xdc\x9e\x25\x73\x28\xc6\xd4\x11\xf6\x7f\xc7\xce\x3e\x2c\xa6\xdd\x73\x0e\x97\xef\xc3\x17\xb9\x5f\xab\x6d\xe2\xbf\x11\x0b\x3a\xe9\xbb\x5e\xd7\xca\xd8\xe9\xbc\xe9\xb8\x31\xfb\xd5\x6e\x0e\x7a\x86\xfa\x2f\x00\x00\xff\xff\x63\xd1\xdc\x25\xe4\x06\x00\x00") +var _configKubernetesPvYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x91\x41\xab\xdb\x30\x0c\xc7\xef\xfe\x14\x82\x9d\x93\xed\x31\x1e\x03\xc3\x3b\x6c\x30\xb6\x1d\xda\x95\x30\xda\xb3\x6a\xab\x45\xc4\xb1\x8d\xad\xa4\x2b\xa1\xdf\x7d\xd8\x69\xc6\x6b\x6f\x96\xf4\xd3\x5f\xfa\x5b\x1f\xe0\x10\x52\x4f\xe9\xe3\xf7\xbf\x64\x46\x09\x09\x76\x7b\x85\x91\xf7\x94\x32\x07\xaf\x61\x7a\x51\x3d\x7b\xab\x61\x57\x32\x59\xc8\xcb\x3e\xb8\x71\x20\x35\x90\xa0\x45\x41\xad\x00\x3c\x0e\xa4\xe1\x34\x7a\x4f\xae\x89\x53\x33\xcf\xed\x1f\xcc\xfd\x2f\x7b\xbb\x29\x00\x87\x47\x72\xb9\x70\x00\x18\xe3\x0a\xd6\x58\x2a\xa6\xe1\x7d\x47\x8e\x64\x0a\x6d\x30\xa2\x61\xb9\x2e\x9d\x59\x42\xc2\x33\x69\x78\xf9\xc1\x0a\x00\x8d\xa1\x9c\x37\xc1\xd2\x5d\xb9\x81\x8e\xd0\x1e\x12\x0b\x6d\xd0\x5f\x15\x40\x7c\x5a\xb9\x23\xe3\x90\x87\x5d\x70\x6c\xae\x1a\x3a\x12\x64\xaf\x00\x86\x30\x7a\xf9\x1d\x85\x83\xff\xaf\x85\xce\x85\x4b\x63\xc9\x91\xd0\x43\x2a\x4c\x94\x2e\x65\xc8\x3d\x9b\xe8\xcc\xc1\xbf\xcd\x73\xdb\xd5\x57\x75\x5c\x0a\x27\x76\xd4\x0c\xc1\xd2\xdb\xa7\x2f\xaf\xaf\xc5\x4e\xe6\x45\xdd\x26\x9e\x28\x69\xc8\x9f\x5b\x93\xb9\xc5\x4b\x6e\x4d\x18\x6a\x69\xaa\x8b\xfe\x44\x6f\x1d\x15\xa0\x31\x99\x9f\x7e\x73\x85\xbe\x8a\x24\x3e\x8e\xb2\xfa\x07\x38\x8e\xa6\x27\xd9\xd6\x5b\xcc\x73\xfb\xad\x86\xb5\xa7\xfa\xee\xe8\xb4\x90\xe5\x5a\x39\xa2\x59\xb0\xed\x1a\xdd\xd5\x9f\x6e\x69\x1e\xc6\xff\x0b\x00\x00\xff\xff\x33\x84\x08\xef\x30\x02\x00\x00") + +func configKubernetesPvYamlBytes() ([]byte, error) { + return bindataRead( + _configKubernetesPvYaml, + "config/kubernetes-pv.yaml", + ) +} + +func configKubernetesPvYaml() (*asset, error) { + bytes, err := configKubernetesPvYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "config/kubernetes-pv.yaml", size: 560, mode: os.FileMode(0644), modTime: time.Unix(1732649398, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa9, 0x15, 0x2e, 0x42, 0x94, 0x68, 0xc1, 0xf2, 0x27, 0xfe, 0xb7, 0xa7, 0xe8, 0x8d, 0x53, 0x1a, 0x1b, 0x36, 0xb0, 0x5c, 0xc, 0x59, 0xb4, 0x7d, 0x16, 0x9c, 0x2b, 0x72, 0xfc, 0x17, 0x7c, 0x20}} + return a, nil +} + +var _configKubernetesExecutorTemplateYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x54\xcd\x4f\xdb\x4e\x10\xbd\xfb\xaf\x18\x39\xfc\x6e\xd8\x98\xcb\xef\x60\xa9\x07\x14\x10\x1f\x2a\x01\xb5\x88\x1e\xaa\x1e\xc6\x9b\x49\xb2\xcd\x7e\x75\x67\x1d\x40\x56\xfe\xf7\x6a\xfd\x55\x07\x28\xf5\x69\xb2\xef\xf9\xbd\x37\x93\x59\xcf\xe0\x01\x79\x0b\x17\xcf\x24\xea\x60\x7d\x82\x4e\x3e\x92\x67\x69\x4d\x09\x15\x06\xb1\x39\xd9\x9d\x26\x5b\x69\x96\x25\xdc\xd8\x2a\xd1\x14\x70\x89\x01\xcb\x04\xc0\xa0\xa6\x12\x9a\x26\x8f\x0a\xd7\xcb\xfd\x3e\x6b\x9a\xfc\xc6\x56\xb1\xec\x61\x76\x28\x3a\xce\x62\xf8\xd5\x62\x0a\x2b\x52\x1c\x45\x00\xd0\xb9\x12\x56\xb5\x31\xa4\x32\x1a\x62\x44\xe0\xa7\xad\xb2\x0f\x3d\xd8\x91\x88\x1a\x15\x8a\xad\x5d\xad\x3e\x4b\x2d\x43\x09\x45\x02\x20\xac\x76\x8a\x82\xb4\x86\x4b\x38\x4d\x00\x02\x69\xa7\x30\x50\x67\x39\xbc\x18\x1f\x4f\x1c\xd0\x87\x7b\xab\xa4\x78\x29\x61\x41\x3b\xf2\x3d\xc4\xe4\x77\x52\xd0\x99\x10\xb6\x36\x61\xd1\x46\xe9\x83\x32\xf6\x1c\x61\x4d\x40\x69\xc8\xf3\x20\x98\xf5\x83\xe9\x99\x4f\xd6\x6f\xc9\x67\x93\x16\x7a\x1e\x80\xd4\xb8\xee\xba\xbb\x8e\xd5\x6b\xe4\xbe\x56\x6a\x88\x75\xa6\x9e\xf0\x85\x47\x5c\x58\xad\x31\xfe\x27\xdf\xd3\x93\x4a\x9a\x13\xde\xa4\xc7\x90\x66\x22\xfd\x31\x52\xd0\xaf\xb9\xd5\x9e\x77\xdc\x89\x7a\x8c\x24\xcd\xfa\x5c\xfa\x96\xf0\xcd\xfa\xed\x52\xfa\x09\xc1\x13\xdb\xda\x0b\x1a\x7b\xea\x0e\x7f\xd5\xc4\xe1\xe0\x0c\x40\xb8\x3a\x8a\xc8\x15\x18\x82\x7c\xee\x6a\x86\x02\xb2\xfd\x3e\x1a\xbb\x9a\x63\x01\xa4\x98\x20\x56\xe9\x69\x51\xe8\x34\x56\x74\x10\x28\x3e\x9a\xb4\xf5\x2f\x13\xad\x2f\xa8\x2f\x2b\x28\xf2\x5e\xce\x79\x69\xc2\x0a\xd2\xff\xf2\x62\x75\x99\xf6\x70\x2b\xa5\x98\x3a\xf1\xff\x6f\xff\xa2\x4d\x6e\x43\x9a\x3c\xaa\x8c\x83\xf5\xfd\xd0\x7b\x9b\x73\xc9\xdb\x8f\x7c\x3a\xfc\xd0\xa8\x28\xde\x73\x1a\x8b\x9d\x55\xb5\xa6\xdb\xb8\x34\x93\x69\xcd\x66\x33\x38\xbf\x83\xc5\xdd\x03\xcc\xaf\xce\x16\x97\x17\xf0\x70\x75\xfd\x75\x84\x9b\xc6\xa3\x59\x13\x1c\xc9\xe5\xf3\x31\x1c\xc9\x40\x1a\xca\x4f\x90\x3f\xb6\x62\x3c\xf1\x79\xb5\x60\x7d\x4b\x59\xd3\x1c\xbd\x5d\x31\x00\x1d\x63\xdc\x63\xd8\xc4\x9e\x5b\xd9\x7c\x3e\xec\x6c\x3c\x3e\x20\x73\x5d\x8d\xd4\x51\xec\x5f\xaf\x0d\x73\x48\xa6\xdd\x73\x09\xef\xdf\x87\x3f\x71\xdf\xa6\x75\xf1\xcb\xc3\x81\x4c\xe8\xba\x9e\x2b\x94\x7a\xba\x6f\x22\x1e\x1c\x5c\x44\xb7\x13\x07\x52\xbf\x03\x00\x00\xff\xff\x00\x13\xe6\xe4\xd0\x04\x00\x00") func configKubernetesExecutorTemplateYamlBytes() ([]byte, error) { return bindataRead( @@ -149,8 +191,8 @@ func configKubernetesExecutorTemplateYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/kubernetes-executor-template.yaml", size: 1764, mode: os.FileMode(0644), modTime: time.Unix(1732069321, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x25, 0x2, 0xdd, 0x4b, 0xb1, 0xe0, 0x8, 0x1f, 0xee, 0xb8, 0xec, 0x74, 0x27, 0xdb, 0xee, 0x63, 0x56, 0x82, 0x78, 0xa4, 0x2a, 0x2b, 0x8c, 0x98, 0x5c, 0xdd, 0x1c, 0xf6, 0x19, 0xd6, 0x9a, 0xb2}} + info := bindataFileInfo{name: "config/kubernetes-executor-template.yaml", size: 1232, mode: os.FileMode(0644), modTime: time.Unix(1733362467, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x28, 0xc4, 0x2b, 0x17, 0x62, 0x5d, 0x51, 0xe4, 0x35, 0x84, 0xde, 0x2a, 0x3, 0x35, 0x7d, 0x71, 0x79, 0x5a, 0xe0, 0xbd, 0x90, 0xcd, 0x65, 0x9d, 0xe8, 0x11, 0x72, 0x90, 0x35, 0xe7, 0x36, 0x7a}} return a, nil } @@ -169,7 +211,7 @@ func configDefaultConfigYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/default-config.yaml", size: 11655, mode: os.FileMode(0644), modTime: time.Unix(1732064227, 0)} + info := bindataFileInfo{name: "config/default-config.yaml", size: 11655, mode: os.FileMode(0644), modTime: time.Unix(1732326201, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x61, 0x5a, 0x7d, 0xb0, 0x9b, 0x9d, 0x13, 0x7, 0x49, 0xd0, 0x4c, 0xab, 0xc2, 0xe5, 0x37, 0xfa, 0x4a, 0x5a, 0x9a, 0x68, 0x89, 0x2b, 0x4b, 0x3e, 0xf3, 0x73, 0x35, 0x82, 0xe8, 0xf1, 0xbf, 0x3c}} return a, nil } @@ -194,7 +236,7 @@ func configHtcondorTemplateTxt() (*asset, error) { return a, nil } -var _configKubernetesTemplateYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\xc9\x6e\xdb\x30\x10\xbd\xeb\x2b\x06\x2a\x7a\xd4\x92\x4b\x0f\xbc\x05\x09\x1a\xb4\x68\x82\xa0\x28\xd2\xf3\x88\x1e\xd9\x84\xb9\x85\x8b\x02\xc3\xd0\xbf\x17\x14\xe5\x56\x72\xec\x00\xe5\x89\x9c\xe5\x2d\xe4\x48\x9f\xe0\x6b\xd4\x9a\x24\xfc\x36\x6e\x4f\xae\x40\x2b\x5e\xc8\x79\x61\x34\x83\x0e\x03\xdf\x35\xc3\x4d\xb1\x17\x7a\xc3\xe0\xbb\xe9\x0a\x45\x01\x37\x18\x90\x15\x00\x1a\x15\x31\x38\x1e\xeb\x5f\xe8\xf7\xdf\x36\xe3\x38\xc7\xbc\x45\x9e\x13\x4f\xa7\xd3\x38\x16\xde\x12\x67\x50\x00\x74\xc8\xf7\xa6\xef\x7f\x08\x25\x02\x83\xb6\x00\xe0\x46\x59\x49\x41\x18\xed\x19\xdc\x14\x00\x81\x94\x95\x18\x28\xb1\x00\x4c\x9d\xd3\x0e\xc0\x93\x1b\x04\xa7\x5b\xce\x4d\xd4\xe1\x69\x52\xd0\x4f\x06\x2a\x8f\x73\x8d\x23\x1f\xd0\x85\x67\x23\x05\x3f\x30\x78\xa2\x81\xdc\x9c\xe2\x46\x07\x14\x9a\x9c\x9f\xa4\xe4\x55\xcd\x4e\x66\x9c\xb7\xe9\x22\xaa\xb5\xaf\xd3\x12\x0a\xb7\xc4\xe0\x35\xe2\xa1\x16\xa6\x31\x3b\x1f\xab\x24\xbf\xea\x84\x69\x32\x00\xdb\xd0\x40\xd2\x58\x45\x3a\x9c\x77\x3e\x47\x29\x4f\xba\x6e\xe5\x1b\x1e\xfc\xa2\x02\xdd\xd6\xb3\xc5\x39\x29\x2b\xb3\x9a\xf2\x3c\xec\xa2\x7e\x17\xab\x2a\x6e\x74\x2f\xb6\xef\x12\x0d\x05\xde\xe4\x5c\xb3\x32\x59\x1f\x50\xc9\x0b\x38\x21\x19\xbf\x3f\x4f\x5c\xbe\x11\x47\xde\x44\xc7\xe9\x4c\xba\xa3\xd7\x48\x3e\x9c\x45\x01\xb8\x8d\x69\x34\x44\x0f\x9a\xa0\xbe\xb3\xd1\x43\x0b\xd5\x38\x1e\x8f\xd3\x21\x6d\x80\xa4\x27\x48\xbb\xf2\xa6\x6d\x55\x99\x76\xa4\xd7\xac\x69\x29\x52\xc6\x1d\x16\x68\x3f\x51\x3d\x74\xd0\xd6\x33\xa0\x75\x42\x87\x1e\xca\xcf\x75\xdb\x3f\x94\x73\x7a\x02\x93\x9e\x32\xfc\x97\xc7\xab\xe8\x64\x77\xa4\xc8\xa1\xac\x7c\x30\x6e\x7a\xf5\xbf\x44\xf7\xc2\xef\x3f\x62\xca\xf9\x35\x55\xdb\x5e\xe6\x1a\x8c\x8c\x8a\x1e\xd3\x38\xaf\x2e\xeb\x34\x95\xf9\xdd\xaa\x5c\xb6\xd2\xa8\x52\xcf\x33\x86\x1d\x83\xc5\x0b\x2f\x4a\x2e\xa0\x9d\xbe\x95\x6c\xe9\xca\x90\xaf\xa1\x8d\x0d\xf3\xd4\x2c\x87\xa7\xda\x08\xd7\x5c\x6b\xf7\xb1\xcb\xcd\xcb\x82\x62\xe9\xf7\xdf\xf7\xf7\xb1\xcf\x1c\x7d\x44\xbb\xbc\x9a\x95\x95\xd9\x75\xf1\xdf\x3e\x6d\xfa\xcd\xf9\x40\x3a\xbc\x4c\x9c\x77\x12\x85\x5a\xd2\xf0\x14\x58\xfd\x62\xec\xc0\x97\x50\x7f\x02\x00\x00\xff\xff\x58\xbe\x1f\x47\x3c\x05\x00\x00") +var _configKubernetesTemplateYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\xc9\x6e\xdb\x30\x10\xbd\xeb\x2b\x06\x2e\x7a\xa4\xa5\x5c\x7a\xe0\x2d\x48\xd0\x20\x45\x13\x04\x45\x91\x9e\x47\xf4\x28\x26\xcc\x2d\x5c\x1c\x18\x86\xff\xbd\xa0\x28\xa7\x94\x63\x07\x28\x4f\xd4\x2c\xef\xbd\x59\xc4\x2f\xf0\x3d\x19\x43\x0a\xfe\x58\xbf\x21\xdf\xa0\x93\xcf\xe4\x83\xb4\x86\x43\x8f\x51\xac\xdb\xed\x55\xb3\x91\x66\xc5\xe1\x87\xed\x1b\x4d\x11\x57\x18\x91\x37\x00\x06\x35\x71\xd8\xef\x97\xbf\x31\x6c\xee\x57\x87\xc3\x64\x0b\x0e\x45\x71\x3c\x1e\xbf\x46\x9f\xc2\x9e\x54\xc8\x99\x00\xe8\x1c\x87\x61\x64\x66\x6f\x85\x39\x9b\x23\x86\x0d\x93\xab\x39\x6a\x70\x24\x38\x34\x00\x3d\x8a\x8d\x1d\x86\x9f\x52\xcb\xc8\xa1\x6b\x00\x84\xd5\x4e\x51\x94\xd6\x04\x0e\x57\x0d\x40\x24\xed\x14\x46\x2a\x2c\xb5\xda\x7c\x6a\x05\x9f\xa8\xb8\xa8\x24\x3b\x46\x35\x53\x58\x20\xbf\x95\x82\xae\x85\xb0\xc9\xc4\xc7\xb1\x1f\x13\x5c\xc0\x29\xc6\x53\x88\xe8\xe3\x93\x55\x52\xec\x38\x3c\xd2\xf6\x9d\x45\x58\x13\x51\x1a\xf2\x61\x2c\xaf\x1c\x36\xf5\x75\x26\x8b\x9d\xaa\x28\x47\x6a\x7c\x21\x0e\xaf\x09\x77\x4b\x69\x5b\xbb\x0e\x89\xe5\x96\xb0\x5e\xda\xb6\x00\xf0\x15\x6d\x49\x59\xa7\xc9\xc4\xd3\xcc\xa7\xa4\xd4\x51\xd7\xb5\x7a\xc3\x5d\xa8\x22\xd0\xbf\x54\x9d\x2a\xca\x16\x45\xcd\xe2\xd4\xec\x93\xf9\x60\x63\x4c\x58\x33\xc8\x97\x0f\x8e\x96\xa2\x68\x8b\xaf\x9d\x15\xb9\xdc\xa1\x56\x67\x70\xf2\x28\xee\x6f\x4f\x1d\xe7\x3b\xe2\x29\xd8\xe4\x05\x9d\x48\xf7\xf4\x9a\x28\xc4\x13\x2b\x80\x70\x29\x4f\x58\x0e\x60\x08\x96\x37\x2e\x05\xe8\x80\x1d\x0e\xfb\xfd\xf8\x91\x2f\x40\x2a\x10\xe4\xdb\xe2\xaa\xeb\xf4\x22\xdf\xc8\xcc\x59\x61\xdc\x35\x6d\xfd\xae\x42\xfb\x85\xfa\xae\x87\x6e\x39\x01\x3a\x2f\x4d\x1c\x60\xf1\x75\xd9\x0d\x77\x8b\xc9\x3d\x82\xa9\x40\x05\xfe\xdb\xc3\x45\x74\x72\x6b\xd2\xe4\x51\xb1\x10\xad\x1f\xa7\xfe\x4e\x74\x2b\xc3\xe6\x33\xa6\xe2\x9f\x53\x75\xdd\x79\xae\xad\x55\x49\xd3\x43\x5e\xe7\x59\xb3\x8e\x5b\x59\xe6\xc6\x4a\xd8\x4c\xa3\xce\x39\x4f\x18\xd7\x1c\xaa\x09\x57\x21\x67\xd0\x8e\xff\x4a\x29\xe9\xc2\x92\xcf\xa1\xad\x8b\xd3\xd6\xd4\xcb\xc3\x56\xd2\xb7\x97\xd2\x43\xea\x4b\x72\x1d\xd0\xd4\xf5\xfe\xfb\xff\x3e\xaf\xb3\x58\x1f\xd0\xd5\xad\x99\x95\x32\x55\xdd\xfc\x77\x9d\x2e\x3f\xba\x21\x92\x89\xcf\x23\xe7\x8d\x42\xa9\x6b\x1a\x91\x0d\xb3\x27\xc6\x6d\xc5\x0c\xea\x6f\x00\x00\x00\xff\xff\x08\x8f\xc3\x13\xcb\x05\x00\x00") func configKubernetesTemplateYamlBytes() ([]byte, error) { return bindataRead( @@ -209,8 +251,8 @@ func configKubernetesTemplateYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config/kubernetes-template.yaml", size: 1340, mode: os.FileMode(0644), modTime: time.Unix(1732069183, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd, 0x9f, 0xc2, 0x40, 0x16, 0xb2, 0xe4, 0xc1, 0xca, 0x91, 0x63, 0x8c, 0xd5, 0x75, 0x4a, 0x48, 0x1f, 0x5b, 0x6a, 0xca, 0xcd, 0x8d, 0x20, 0x7d, 0x76, 0xa4, 0x7, 0x7f, 0xf3, 0xe5, 0x87, 0xe5}} + info := bindataFileInfo{name: "config/kubernetes-template.yaml", size: 1483, mode: os.FileMode(0644), modTime: time.Unix(1732239949, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2c, 0x8c, 0x8e, 0x18, 0x35, 0xe0, 0x7c, 0x96, 0x55, 0x8e, 0x9a, 0x5a, 0x33, 0xde, 0x4f, 0x66, 0x80, 0xae, 0x95, 0xb, 0x6a, 0x60, 0xf3, 0xb4, 0x8d, 0xd9, 0xa9, 0xf4, 0x6b, 0x90, 0xbc, 0x91}} return a, nil } @@ -305,9 +347,11 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ + "config/kubernetes-pvc.yaml": configKubernetesPvcYaml, "config/gridengine-template.txt": configGridengineTemplateTxt, "config/pbs-template.txt": configPbsTemplateTxt, "config/slurm-template.txt": configSlurmTemplateTxt, + "config/kubernetes-pv.yaml": configKubernetesPvYaml, "config/kubernetes-executor-template.yaml": configKubernetesExecutorTemplateYaml, "config/default-config.yaml": configDefaultConfigYaml, "config/htcondor-template.txt": configHtcondorTemplateTxt, @@ -365,6 +409,8 @@ var _bintree = &bintree{nil, map[string]*bintree{ "gridengine-template.txt": {configGridengineTemplateTxt, map[string]*bintree{}}, "htcondor-template.txt": {configHtcondorTemplateTxt, map[string]*bintree{}}, "kubernetes-executor-template.yaml": {configKubernetesExecutorTemplateYaml, map[string]*bintree{}}, + "kubernetes-pv.yaml": {configKubernetesPvYaml, map[string]*bintree{}}, + "kubernetes-pvc.yaml": {configKubernetesPvcYaml, map[string]*bintree{}}, "kubernetes-template.yaml": {configKubernetesTemplateYaml, map[string]*bintree{}}, "pbs-template.txt": {configPbsTemplateTxt, map[string]*bintree{}}, "slurm-template.txt": {configSlurmTemplateTxt, map[string]*bintree{}}, diff --git a/config/kubernetes-executor-template.yaml b/config/kubernetes-executor-template.yaml index 8e10d2546..b87f8ef7f 100644 --- a/config/kubernetes-executor-template.yaml +++ b/config/kubernetes-executor-template.yaml @@ -5,6 +5,7 @@ metadata: name: {{.TaskId}}-{{.JobId}} namespace: {{.Namespace}} labels: + app: funnel-executor job-name: {{.TaskId}}-{{.JobId}} spec: backoffLimit: 0 @@ -13,19 +14,6 @@ spec: spec: restartPolicy: Never serviceAccountName: funnel-sa - # Setup symlinks from work dir to target paths - initContainers: - - name: setup-dirs - image: busybox - command: ["/bin/sh", "-c"] - args: - - | - # Create a directory to bind mount the worker's files - # Create parent directories for any path specified in the task - # Create the symlink from worker dir to target path - echo "initContainer: Creating directories and symlinks" - securityContext: - runAsUser: 0 # Run as root to ensure directory creation works containers: - name: funnel-worker-{{.TaskId}} image: {{.Image}} diff --git a/config/kubernetes-pv.yaml b/config/kubernetes-pv.yaml new file mode 100644 index 000000000..7bc53f64f --- /dev/null +++ b/config/kubernetes-pv.yaml @@ -0,0 +1,27 @@ +# Worker/Executor PV +apiVersion: v1 +kind: PersistentVolume +metadata: + name: funnel-pv-{{.TaskId}} + labels: + app: funnel + taskId: {{.TaskId}} +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + mountOptions: + - allow-delete + - allow-overwrite + - region={{.Region}} + - file-mode=0755 + csi: + driver: s3.csi.aws.com + volumeHandle: s3-csi-{{.TaskId}} + volumeAttributes: + bucketName: {{.Bucket}} + claimRef: + namespace: {{.Namespace}} + name: funnel-pvc-{{.TaskId}} diff --git a/config/kubernetes-pvc.yaml b/config/kubernetes-pvc.yaml new file mode 100644 index 000000000..183a87ba9 --- /dev/null +++ b/config/kubernetes-pvc.yaml @@ -0,0 +1,16 @@ +# Worker/Executor PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: funnel-pvc-{{.TaskId}} + namespace: {{ .Namespace }} + labels: + app: funnel + taskId: {{.TaskId}} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi + volumeName: funnel-pv-{{.TaskId}} diff --git a/config/kubernetes-template.yaml b/config/kubernetes-template.yaml index 81239b68a..97f8af013 100644 --- a/config/kubernetes-template.yaml +++ b/config/kubernetes-template.yaml @@ -4,10 +4,17 @@ kind: Job metadata: name: {{.TaskId}} namespace: {{.Namespace}} + labels: + app: funnel-worker + task-id: {{.TaskId}} spec: backoffLimit: 0 completions: 1 template: + metadata: + labels: + app: funnel-worker + task-id: {{.TaskId}} spec: serviceAccountName: funnel-sa restartPolicy: Never @@ -42,4 +49,4 @@ spec: - name: funnel-storage-{{.TaskId}} persistentVolumeClaim: - claimName: funnel-pvc-{{.TaskId}} \ No newline at end of file + claimName: funnel-pvc-{{.TaskId}} diff --git a/deployments/kubernetes/helm/Chart.yaml b/deployments/kubernetes/helm/Chart.yaml index 42db24fba..d55e41595 100644 --- a/deployments/kubernetes/helm/Chart.yaml +++ b/deployments/kubernetes/helm/Chart.yaml @@ -17,7 +17,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.11 +version: 0.1.15 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/deployments/kubernetes/helm/config/funnel-server.yaml b/deployments/kubernetes/helm/config/funnel-server.yaml index 29812b538..5deeb38fe 100644 --- a/deployments/kubernetes/helm/config/funnel-server.yaml +++ b/deployments/kubernetes/helm/config/funnel-server.yaml @@ -1,8 +1,15 @@ Compute: {{ .Values.Compute }} Kubernetes: - Executor: "kubernetes" + Executor: {{ .Values.Kubernetes.Executor }} Namespace: {{ .Release.Namespace }} + DisableReconciler: {{ .Values.Kubernetes.DisableReconciler }} + ReconcileRate: {{ .Values.Kubernetes.ReconcileRate }} + ServiceAccount: {{ .Values.Kubernetes.ServiceAccount }} + Template: {{ .Values.Kubernetes.Template }} + TemplateFile: {{ .Values.Kubernetes.TemplateFile }} + Bucket: {{ .Values.Kubernetes.Bucket }} + Region: {{ .Values.Kubernetes.Region }} Database: {{ .Values.Database }} diff --git a/deployments/kubernetes/helm/config/funnel-worker.yaml b/deployments/kubernetes/helm/config/funnel-worker.yaml index 5f2a3cb61..d8abf37d3 100644 --- a/deployments/kubernetes/helm/config/funnel-worker.yaml +++ b/deployments/kubernetes/helm/config/funnel-worker.yaml @@ -3,8 +3,15 @@ Database: {{ .Values.Database }} Compute: {{ .Values.Compute }} Kubernetes: - Executor: "kubernetes" + Executor: {{ .Values.Kubernetes.Executor }} Namespace: {{ .Release.Namespace }} + DisableReconciler: {{ .Values.Kubernetes.DisableReconciler }} + ReconcileRate: {{ .Values.Kubernetes.ReconcileRate }} + ServiceAccount: {{ .Values.Kubernetes.ServiceAccount }} + Template: {{ .Values.Kubernetes.Template }} + TemplateFile: {{ .Values.Kubernetes.TemplateFile }} + Bucket: {{ .Values.Kubernetes.Bucket }} + Region: {{ .Values.Kubernetes.Region }} Logger: Level: {{ .Values.Logger.level }} diff --git a/deployments/kubernetes/helm/templates/NOTES.txt b/deployments/kubernetes/helm/templates/NOTES.txt index 19666b112..34e155afb 100644 --- a/deployments/kubernetes/helm/templates/NOTES.txt +++ b/deployments/kubernetes/helm/templates/NOTES.txt @@ -1,5 +1,3 @@ -1. To access the Funnel application, use the following instructions: - -To access the service locally, use: +To access the Funnel service locally, use: kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "funnel.fullname" . }} 8000:8000 echo "Visit http://127.0.0.1:8000" diff --git a/deployments/kubernetes/helm/templates/clusterrole.yaml b/deployments/kubernetes/helm/templates/clusterrole.yaml new file mode 100644 index 000000000..31e4ff694 --- /dev/null +++ b/deployments/kubernetes/helm/templates/clusterrole.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Namespace }}-{{ .Release.Name }}-clusterrole +rules: +- apiGroups: [""] + resources: + - persistentvolumes + verbs: + - get + - list + - watch + - create + - update + - delete diff --git a/deployments/kubernetes/helm/templates/clusterrolebinding.yaml b/deployments/kubernetes/helm/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..6c3584fa6 --- /dev/null +++ b/deployments/kubernetes/helm/templates/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Namespace }}-{{ .Release.Name }}-clusterrolebinding +subjects: +- kind: ServiceAccount + name: funnel-sa + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ .Release.Namespace }}-{{ .Release.Name }}-clusterrole + apiGroup: rbac.authorization.k8s.io diff --git a/deployments/kubernetes/helm/templates/role.yaml b/deployments/kubernetes/helm/templates/role.yaml index 64628d22e..32fa3b93c 100644 --- a/deployments/kubernetes/helm/templates/role.yaml +++ b/deployments/kubernetes/helm/templates/role.yaml @@ -6,7 +6,7 @@ metadata: namespace: {{ .Release.Namespace }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "persistentvolumeclaims"] # Added PVCs + resources: ["pods", "pods/log", "persistentvolumeclaims"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - apiGroups: ["batch", "extensions"] resources: ["jobs"] diff --git a/deployments/kubernetes/helm/values.yaml b/deployments/kubernetes/helm/values.yaml index 55ae99bf8..1860b2c60 100644 --- a/deployments/kubernetes/helm/values.yaml +++ b/deployments/kubernetes/helm/values.yaml @@ -349,7 +349,7 @@ AWSBatch: # Kubernetes describes the configuration for the Kubernetes compute backend. Kubernetes: # The executor used to execute tasks. Available executors: docker, kubernetes - Executor: "kubernetes" + Executor: "kubernetes" # Turn off task state reconciler. When enabled, Funnel communicates with Kubernetes # to find tasks that are stuck in a queued state or errored and # updates the task state accordingly. @@ -365,6 +365,10 @@ Kubernetes: Template: "" # TemplateFile is the path to the master job template. TemplateFile: "" + # The bucket to use for the task's Working Directory + Bucket: "" + # The region to use for the task's Bucket + Region: "" # Configuration of the Kubernetes executor. diff --git a/examples/internal/bundle.go b/examples/internal/bundle.go index b8c2533b5..4699b377d 100644 --- a/examples/internal/bundle.go +++ b/examples/internal/bundle.go @@ -10,7 +10,7 @@ // examples/s3-sleep.json (518B) // examples/md5sum.json (737B) // examples/nextflow.json (551B) -// examples/s3.json (524B) +// examples/s3.json (532B) // examples/malicious-tag.json (283B) // examples/full-hello.json (577B) @@ -280,7 +280,7 @@ func examplesNextflowJson() (*asset, error) { return a, nil } -var _examplesS3Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x90\xcf\x4e\xf3\x30\x10\xc4\xef\x79\x8a\x95\x4f\xdf\x27\x35\xb1\x44\xc5\x25\xd7\xb4\xe2\x52\x71\xa0\x70\x01\xf5\xb0\x71\xb6\xa9\xc1\x7f\x22\xaf\x4d\x83\xaa\xbe\x3b\x8a\x0b\x2d\x85\x4b\x14\x69\xc6\x33\xbf\x9d\x43\x01\x20\x1c\x5a\x12\x35\x88\xf5\x1c\xd6\xd1\x07\xec\x09\x68\x44\x3b\x18\x12\xb3\x49\xef\x88\x55\xd0\x43\xd4\xde\x4d\xb6\x47\xe4\x37\xd0\x6e\x48\x91\x01\x5d\x07\x3e\xc5\xfc\xaf\xd0\x41\x4b\xd0\x18\x9f\xba\x73\xd0\xd3\xc3\x8a\xab\x53\x0c\x8d\xa4\x52\xf4\x81\x45\x0d\x2f\x05\x00\xc0\x21\x7f\x01\x84\xb6\xd8\x67\x84\xd4\x26\x17\x53\xf6\x67\x41\x79\x6b\xd1\x75\xd3\x0b\x61\xbb\x5b\x4e\x56\xcc\x40\xc8\x40\x86\x90\xa9\x7a\x65\xef\xc4\x26\x9b\x8f\x05\xc0\x26\xf7\x9c\xd0\xfe\x96\x7c\x9f\x99\xf5\x4b\xc5\xaf\xeb\x16\x7e\xef\x8c\xc7\x0e\x10\x86\xd4\x1a\xad\x60\xab\x0d\xc1\x36\x78\x0b\x3f\x06\xfa\xd7\x3c\x43\xb3\x5c\xad\xc6\xbb\xe5\xfd\x12\x16\x9a\x95\x7f\xa7\x00\x0d\x39\x4e\x0c\x0b\x8c\x58\xc3\x2e\xc6\x81\x6b\x29\x03\xf5\x9a\x63\xf8\xa8\xfc\x40\xae\xc3\x88\x15\xee\x59\x46\xec\x65\xab\xbd\x76\x5b\x1f\x2c\x46\xad\x58\xfe\xbf\x50\xa5\x60\x26\x1a\x9e\xd7\x52\x2a\x32\x66\xec\xc9\x51\xa9\x72\x7c\x79\x02\x2b\x13\x97\x7b\xe2\x58\xde\x64\xc7\x97\x78\xbd\xcd\x39\x6f\xc0\xb8\x9b\x02\xaf\xe5\xcb\x72\xc5\xb1\xf8\x0c\x00\x00\xff\xff\xc0\x06\x69\xef\x0c\x02\x00\x00") +var _examplesS3Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x90\x4f\x8f\xd3\x30\x10\xc5\xef\xf9\x14\x23\x9f\x40\xda\xc4\x12\x2b\x2e\xb9\xa6\x15\x97\x8a\x03\x0b\x17\x50\x0f\x13\x67\x9a\x1a\xfc\x4f\x9e\x31\x0d\xaa\xfa\xdd\x51\x5c\x68\x61\x7b\xb1\x2c\xbd\x99\xf7\x7e\xf3\xce\x0d\x80\x0a\xe8\x49\xf5\xa0\x5e\x9e\xe1\x45\x62\xc6\x99\x80\x16\xf4\xc9\x91\x7a\x5a\xf5\x89\xd8\x64\x9b\xc4\xc6\xb0\x8e\x7d\x46\xfe\x01\x36\xa4\x22\x0c\x18\x26\x88\x45\xea\xdf\x60\x80\x91\x60\x70\xb1\x4c\x37\xa3\x2f\x9f\x76\xdc\x5d\x6d\x68\x21\x53\x24\x66\x56\x3d\x7c\x6b\x00\x00\xce\xf5\x05\x50\xd6\xe3\x5c\x11\xca\x58\x82\x94\x3a\x5f\x05\x13\xbd\xc7\x30\xad\x1b\xca\x4f\xef\xb9\x78\xf5\x04\x4a\x8b\x4f\x3a\x93\x23\x64\xea\xbe\x73\x0c\x6a\x5f\x17\x2e\x0d\xc0\xbe\x66\x5d\xf1\x1e\x83\xfe\x9e\x5a\xf5\x7b\xcc\xab\x0b\x37\xf1\x14\x5c\xc4\x09\x10\x52\x19\x9d\x35\x70\xb0\x8e\xe0\x90\xa3\x87\x7f\x4a\x7a\x33\x7c\x85\x61\xbb\xdb\x2d\x1f\xb6\x1f\xb7\xb0\xb1\x6c\xe2\x4f\xca\x30\x50\xe0\xc2\xb0\x41\xc1\x1e\x8e\x22\x89\x7b\xad\x33\xcd\x96\x25\xff\xea\x62\xa2\x30\xa1\x60\x87\x27\xd6\x82\xb3\x1e\x6d\xb4\xe1\x10\xb3\x47\xb1\x86\xf5\xdb\x3b\x55\xc9\x6e\xa5\xe1\xe7\x5e\x6b\x43\xce\x2d\x33\x05\x6a\x4d\xb5\x6f\xaf\x60\x6d\xe1\xf6\x44\x2c\xed\xbb\x3a\xf1\x47\xfc\xbf\x9b\x9b\x5f\x42\x39\xae\x86\x8f\xf5\xdd\xdb\x6b\x2e\xcd\xef\x00\x00\x00\xff\xff\x65\xd0\xc2\x9b\x14\x02\x00\x00") func examplesS3JsonBytes() ([]byte, error) { return bindataRead( @@ -295,8 +295,8 @@ func examplesS3Json() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "examples/s3.json", size: 524, mode: os.FileMode(0644), modTime: time.Unix(1732064231, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa5, 0x12, 0xab, 0xa6, 0x34, 0xd0, 0xb, 0xd6, 0x56, 0x79, 0x95, 0xa, 0x80, 0x42, 0x6e, 0x70, 0x53, 0xb8, 0xcb, 0x92, 0x4, 0xb6, 0x62, 0xe0, 0xb, 0x1c, 0xfb, 0xb1, 0xd5, 0x78, 0xa4, 0x37}} + info := bindataFileInfo{name: "examples/s3.json", size: 532, mode: os.FileMode(0644), modTime: time.Unix(1732133183, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xce, 0xcf, 0xc, 0x0, 0xf8, 0xd5, 0x2b, 0x3c, 0x3f, 0x6e, 0x32, 0xcb, 0x99, 0x40, 0x9, 0x3b, 0x20, 0xd4, 0xde, 0x90, 0xd6, 0x4b, 0x53, 0x91, 0xd3, 0x71, 0x9b, 0x4f, 0x31, 0x3b, 0x70, 0x94}} return a, nil } diff --git a/funnel.config.yml b/funnel.config.yml deleted file mode 100644 index caf984b06..000000000 --- a/funnel.config.yml +++ /dev/null @@ -1,8 +0,0 @@ -LocalStorage: - # Whitelist of local directory paths which Funnel is allowed to access. - AllowedDirs: - - ./ - - /tmp - -Worker: - LeaveWorkDir: true diff --git a/go.mod b/go.mod index 3b8faf2e5..e95132c1e 100644 --- a/go.mod +++ b/go.mod @@ -135,6 +135,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/muonsoft/openapi-mock v0.3.9 // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect diff --git a/go.sum b/go.sum index 6d8271209..b99eb742e 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/muonsoft/openapi-mock v0.3.9 h1:MNeqzwk6QzGTtnhvyRyDIywvt1YdCIvBEbtENiOnXmo= +github.com/muonsoft/openapi-mock v0.3.9/go.mod h1:M5zGO4+q2BSS+KKsZtHX+lBFKMJ5QTQ0PdK2SdhHHc4= github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= diff --git a/website/content/docs/compute/kubernetes.md b/website/content/docs/compute/kubernetes.md index 004a285fb..b484fda49 100644 --- a/website/content/docs/compute/kubernetes.md +++ b/website/content/docs/compute/kubernetes.md @@ -104,7 +104,9 @@ funnel task create hello-world.json # Storage Architecture -![K8s Storage](/img/k8s-pvc.png) + + + # Additional Resources 📚 diff --git a/website/static/img/k8s-pvc.png b/website/static/img/k8s-pvc.png index 9325fb5bc..7a815d5b5 100644 Binary files a/website/static/img/k8s-pvc.png and b/website/static/img/k8s-pvc.png differ diff --git a/worker/kubernetes.go b/worker/kubernetes.go index 2ad7ca6b4..49987127c 100644 --- a/worker/kubernetes.go +++ b/worker/kubernetes.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "strings" "text/template" "github.com/ohsu-comp-bio/funnel/tes" @@ -26,10 +27,11 @@ type KubernetesCommand struct { Namespace string Resources *tes.Resources ServiceAccount string + Clientset kubernetes.Interface Command } -// Create the Executor K8s job +// Create the Executor K8s job from kubernetes-executor-template.yaml // Funnel Worker job is created in compute/kubernetes/backend.go#createJob func (kcmd KubernetesCommand) Run(ctx context.Context) error { var taskId = kcmd.TaskId @@ -43,6 +45,11 @@ func (kcmd KubernetesCommand) Run(ctx context.Context) error { if kcmd.StdinFile != "" { command = append(command, "<", kcmd.StdinFile) } + for i, v := range command { + if strings.Contains(v, " ") { + command[i] = fmt.Sprintf("'%s'", v) + } + } var buf bytes.Buffer err = tpl.Execute(&buf, map[string]interface{}{ @@ -74,9 +81,13 @@ func (kcmd KubernetesCommand) Run(ctx context.Context) error { return err } - clientset, err := getKubernetesClientset() - if err != nil { - return err + clientset := kcmd.Clientset + if clientset == nil { + var err error + clientset, err = getKubernetesClientset() + if err != nil { + return err + } } var client = clientset.BatchV1().Jobs(kcmd.Namespace) diff --git a/worker/worker.go b/worker/worker.go index ab4210202..93683ddf0 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -34,6 +34,10 @@ type Executor struct { Backend string // Kubernetes executor template Template string + // Kubernetes persistent volume template + PVTemplate string + // Kubernetes persistent volume claim template + PVCTemplate string // Kubernetes namespace Namespace string // Kubernetes service account name