diff --git a/backend/src/apiserver/template/template.go b/backend/src/apiserver/template/template.go index f12f37a2d8b..e361bbc9317 100644 --- a/backend/src/apiserver/template/template.go +++ b/backend/src/apiserver/template/template.go @@ -45,6 +45,7 @@ const ( ) var ErrorInvalidPipelineSpec = fmt.Errorf("pipeline spec is invalid") +var ErrorInvalidPlatformSpec = fmt.Errorf("Platform spec is invalid") // inferTemplateFormat infers format from pipeline template. // There is no guarantee that the template is valid in inferred format, so validation diff --git a/backend/src/apiserver/template/template_test.go b/backend/src/apiserver/template/template_test.go index 8808b083026..457814adb48 100644 --- a/backend/src/apiserver/template/template_test.go +++ b/backend/src/apiserver/template/template_test.go @@ -15,17 +15,21 @@ package template import ( + "io/ioutil" + "strings" "testing" "time" "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" "github.com/ghodss/yaml" + "github.com/kubeflow/pipelines/api/v2alpha1/go/pipelinespec" "github.com/kubeflow/pipelines/backend/src/apiserver/model" "github.com/kubeflow/pipelines/backend/src/common/util" commonutil "github.com/kubeflow/pipelines/backend/src/common/util" scheduledworkflow "github.com/kubeflow/pipelines/backend/src/crd/pkg/apis/scheduledworkflow/v1beta1" "github.com/stretchr/testify/assert" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/encoding/protojson" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -94,7 +98,7 @@ kind: CronWorkflow`, template: `{"abc": "def", "b": {"key": 3}}`, templateType: Unknown, }, { - template: v2SpecHelloWorldYAML, + template: loadYaml(t, "testdata/hello_world.yaml"), templateType: V2, }} @@ -147,65 +151,6 @@ spec: container: image: docker/whalesay:latest` -var v2SpecHelloWorldYAML = ` -# this is a comment -components: - comp-hello-world: - executorLabel: exec-hello-world - inputDefinitions: - parameters: - text: - type: STRING -deploymentSpec: - executors: - exec-hello-world: - container: - args: - - "--text" - - "{{$.inputs.parameters['text']}}" - command: - - sh - - "-ec" - - | - program_path=$(mktemp) - printf "%s" "$0" > "$program_path" - python3 -u "$program_path" "$@" - - | - def hello_world(text): - print(text) - return text - - import argparse - _parser = argparse.ArgumentParser(prog='Hello world', description='') - _parser.add_argument("--text", dest="text", type=str, required=True, default=argparse.SUPPRESS) - _parsed_args = vars(_parser.parse_args()) - - _outputs = hello_world(**_parsed_args) - image: python:3.7 -pipelineInfo: - name: namespace/n1/pipeline/hello-world -root: - dag: - tasks: - hello-world: - cachingOptions: - enableCache: true - componentRef: - name: comp-hello-world - inputs: - parameters: - text: - componentInputParameter: text - taskInfo: - name: hello-world - inputDefinitions: - parameters: - text: - type: STRING -schemaVersion: 2.0.0 -sdkVersion: kfp-1.6.5 -` - var ( WorkflowSpecV1 = "{\"kind\":\"Workflow\",\"apiVersion\":\"argoproj.io/v1alpha1\",\"metadata\":{\"generateName\":\"hello-world-\",\"creationTimestamp\":null,\"annotations\":{\"pipelines.kubeflow.org/components-comp-hello-world\":\"{\\\"executorLabel\\\":\\\"exec-hello-world\\\",\\\"inputDefinitions\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"type\\\":\\\"STRING\\\"}}}}\",\"pipelines.kubeflow.org/components-root\":\"{\\\"dag\\\":{\\\"tasks\\\":{\\\"hello-world\\\":{\\\"cachingOptions\\\":{\\\"enableCache\\\":true},\\\"componentRef\\\":{\\\"name\\\":\\\"comp-hello-world\\\"},\\\"inputs\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"componentInputParameter\\\":\\\"text\\\"}}},\\\"taskInfo\\\":{\\\"name\\\":\\\"hello-world\\\"}}}},\\\"inputDefinitions\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"type\\\":\\\"STRING\\\"}}}}\",\"pipelines.kubeflow.org/implementations-comp-hello-world\":\"{\\\"args\\\":[\\\"--text\\\",\\\"{{$.inputs.parameters['text']}}\\\"],\\\"command\\\":[\\\"sh\\\",\\\"-ec\\\",\\\"program_path=$(mktemp)\\\\nprintf \\\\\\\"%s\\\\\\\" \\\\\\\"$0\\\\\\\" \\\\u003e \\\\\\\"$program_path\\\\\\\"\\\\npython3 -u \\\\\\\"$program_path\\\\\\\" \\\\\\\"$@\\\\\\\"\\\\n\\\",\\\"def hello_world(text):\\\\n print(text)\\\\n return text\\\\n\\\\nimport argparse\\\\n_parser = argparse.ArgumentParser(prog='Hello world', description='')\\\\n_parser.add_argument(\\\\\\\"--text\\\\\\\", dest=\\\\\\\"text\\\\\\\", type=str, required=True, default=argparse.SUPPRESS)\\\\n_parsed_args = vars(_parser.parse_args())\\\\n\\\\n_outputs = hello_world(**_parsed_args)\\\\n\\\"],\\\"image\\\":\\\"python:3.7\\\"}\"}},\"spec\":{\"templates\":[{\"name\":\"system-container-driver\",\"inputs\":{\"parameters\":[{\"name\":\"component\"},{\"name\":\"task\"},{\"name\":\"container\"},{\"name\":\"parent-dag-id\"},{\"name\":\"iteration-index\",\"default\":\"-1\"}]},\"outputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"valueFrom\":{\"path\":\"/tmp/outputs/pod-spec-patch\",\"default\":\"\"}},{\"name\":\"cached-decision\",\"default\":\"false\",\"valueFrom\":{\"path\":\"/tmp/outputs/cached-decision\",\"default\":\"false\"}},{\"name\":\"condition\",\"valueFrom\":{\"path\":\"/tmp/outputs/condition\",\"default\":\"true\"}}]},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-driver@sha256:d8f18e6f6cd84a43d45eb339d7e6d2dfed45358e713aaf0ddf7812b06234e9f5\",\"command\":[\"driver\"],\"args\":[\"--type\",\"CONTAINER\",\"--pipeline_name\",\"namespace/n1/pipeline/hello-world\",\"--run_id\",\"{{workflow.uid}}\",\"--dag_execution_id\",\"{{inputs.parameters.parent-dag-id}}\",\"--component\",\"{{inputs.parameters.component}}\",\"--task\",\"{{inputs.parameters.task}}\",\"--container\",\"{{inputs.parameters.container}}\",\"--iteration_index\",\"{{inputs.parameters.iteration-index}}\",\"--cached_decision_path\",\"{{outputs.parameters.cached-decision.path}}\",\"--pod_spec_patch_path\",\"{{outputs.parameters.pod-spec-patch.path}}\",\"--condition_path\",\"{{outputs.parameters.condition.path}}\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"512Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"64Mi\"}}}},{\"name\":\"system-container-executor\",\"inputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\"},{\"name\":\"cached-decision\",\"default\":\"false\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"executor\",\"template\":\"system-container-impl\",\"arguments\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"value\":\"{{inputs.parameters.pod-spec-patch}}\"}]},\"when\":\"{{inputs.parameters.cached-decision}} != true\"}]}},{\"name\":\"system-container-impl\",\"inputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline/should-be-overridden-during-runtime\",\"command\":[\"should-be-overridden-during-runtime\"],\"envFrom\":[{\"configMapRef\":{\"name\":\"metadata-grpc-configmap\",\"optional\":true}}],\"env\":[{\"name\":\"KFP_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"KFP_POD_UID\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.uid\"}}}],\"resources\":{},\"volumeMounts\":[{\"name\":\"kfp-launcher\",\"mountPath\":\"/kfp-launcher\"}]},\"volumes\":[{\"name\":\"kfp-launcher\",\"emptyDir\":{}}],\"initContainers\":[{\"name\":\"kfp-launcher\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-launcher-v2@sha256:98a25728bfdc5a91a54f93f388bd16fa386008164e665ea797a619096a131c1a\",\"command\":[\"launcher-v2\",\"--copy\",\"/kfp-launcher/launch\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"128Mi\"},\"requests\":{\"cpu\":\"100m\"}},\"volumeMounts\":[{\"name\":\"kfp-launcher\",\"mountPath\":\"/kfp-launcher\"}]}],\"podSpecPatch\":\"{{inputs.parameters.pod-spec-patch}}\"},{\"name\":\"root\",\"inputs\":{\"parameters\":[{\"name\":\"parent-dag-id\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"hello-world-driver\",\"template\":\"system-container-driver\",\"arguments\":{\"parameters\":[{\"name\":\"component\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/components-comp-hello-world}}\"},{\"name\":\"task\",\"value\":\"{\\\"cachingOptions\\\":{\\\"enableCache\\\":true},\\\"componentRef\\\":{\\\"name\\\":\\\"comp-hello-world\\\"},\\\"inputs\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"componentInputParameter\\\":\\\"text\\\"}}},\\\"taskInfo\\\":{\\\"name\\\":\\\"hello-world\\\"}}\"},{\"name\":\"container\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/implementations-comp-hello-world}}\"},{\"name\":\"parent-dag-id\",\"value\":\"{{inputs.parameters.parent-dag-id}}\"}]}},{\"name\":\"hello-world\",\"template\":\"system-container-executor\",\"arguments\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"value\":\"{{tasks.hello-world-driver.outputs.parameters.pod-spec-patch}}\"},{\"name\":\"cached-decision\",\"default\":\"false\",\"value\":\"{{tasks.hello-world-driver.outputs.parameters.cached-decision}}\"}]},\"depends\":\"hello-world-driver.Succeeded\"}]}},{\"name\":\"system-dag-driver\",\"inputs\":{\"parameters\":[{\"name\":\"component\"},{\"name\":\"runtime-config\",\"default\":\"\"},{\"name\":\"task\",\"default\":\"\"},{\"name\":\"parent-dag-id\",\"default\":\"0\"},{\"name\":\"iteration-index\",\"default\":\"-1\"},{\"name\":\"driver-type\",\"default\":\"DAG\"}]},\"outputs\":{\"parameters\":[{\"name\":\"execution-id\",\"valueFrom\":{\"path\":\"/tmp/outputs/execution-id\"}},{\"name\":\"iteration-count\",\"valueFrom\":{\"path\":\"/tmp/outputs/iteration-count\",\"default\":\"0\"}},{\"name\":\"condition\",\"valueFrom\":{\"path\":\"/tmp/outputs/condition\",\"default\":\"true\"}}]},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-driver@sha256:d8f18e6f6cd84a43d45eb339d7e6d2dfed45358e713aaf0ddf7812b06234e9f5\",\"command\":[\"driver\"],\"args\":[\"--type\",\"{{inputs.parameters.driver-type}}\",\"--pipeline_name\",\"namespace/n1/pipeline/hello-world\",\"--run_id\",\"{{workflow.uid}}\",\"--dag_execution_id\",\"{{inputs.parameters.parent-dag-id}}\",\"--component\",\"{{inputs.parameters.component}}\",\"--task\",\"{{inputs.parameters.task}}\",\"--runtime_config\",\"{{inputs.parameters.runtime-config}}\",\"--iteration_index\",\"{{inputs.parameters.iteration-index}}\",\"--execution_id_path\",\"{{outputs.parameters.execution-id.path}}\",\"--iteration_count_path\",\"{{outputs.parameters.iteration-count.path}}\",\"--condition_path\",\"{{outputs.parameters.condition.path}}\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"512Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"64Mi\"}}}},{\"name\":\"entrypoint\",\"inputs\":{},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"root-driver\",\"template\":\"system-dag-driver\",\"arguments\":{\"parameters\":[{\"name\":\"component\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/components-root}}\"},{\"name\":\"runtime-config\",\"value\":\"{}\"},{\"name\":\"driver-type\",\"value\":\"ROOT_DAG\"}]}},{\"name\":\"root\",\"template\":\"root\",\"arguments\":{\"parameters\":[{\"name\":\"parent-dag-id\",\"value\":\"{{tasks.root-driver.outputs.parameters.execution-id}}\"},{\"name\":\"condition\",\"value\":\"\"}]},\"depends\":\"root-driver.Succeeded\"}]}}],\"entrypoint\":\"entrypoint\",\"arguments\":{},\"serviceAccountName\":\"pipeline-runner\",\"podMetadata\":{\"annotations\":{\"pipelines.kubeflow.org/v2_component\":\"true\"},\"labels\":{\"pipelines.kubeflow.org/v2_component\":\"true\"}}},\"status\":{\"startedAt\":null,\"finishedAt\":null}}" ExpectedWorkflowSpecV2 = "{\"kind\":\"Workflow\",\"apiVersion\":\"argoproj.io/v1alpha1\",\"metadata\":{\"generateName\":\"hello-world-\",\"creationTimestamp\":null,\"annotations\":{\"pipelines.kubeflow.org/components-comp-hello-world\":\"{\\\"executorLabel\\\":\\\"exec-hello-world\\\",\\\"inputDefinitions\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"type\\\":\\\"STRING\\\"}}}}\",\"pipelines.kubeflow.org/components-root\":\"{\\\"dag\\\":{\\\"tasks\\\":{\\\"hello-world\\\":{\\\"cachingOptions\\\":{\\\"enableCache\\\":true},\\\"componentRef\\\":{\\\"name\\\":\\\"comp-hello-world\\\"},\\\"inputs\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"componentInputParameter\\\":\\\"text\\\"}}},\\\"taskInfo\\\":{\\\"name\\\":\\\"hello-world\\\"}}}},\\\"inputDefinitions\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"type\\\":\\\"STRING\\\"}}}}\",\"pipelines.kubeflow.org/implementations-comp-hello-world\":\"{\\\"args\\\":[\\\"--text\\\",\\\"{{$.inputs.parameters['text']}}\\\"],\\\"command\\\":[\\\"sh\\\",\\\"-ec\\\",\\\"program_path=$(mktemp)\\\\nprintf \\\\\\\"%s\\\\\\\" \\\\\\\"$0\\\\\\\" \\\\u003e \\\\\\\"$program_path\\\\\\\"\\\\npython3 -u \\\\\\\"$program_path\\\\\\\" \\\\\\\"$@\\\\\\\"\\\\n\\\",\\\"def hello_world(text):\\\\n print(text)\\\\n return text\\\\n\\\\nimport argparse\\\\n_parser = argparse.ArgumentParser(prog='Hello world', description='')\\\\n_parser.add_argument(\\\\\\\"--text\\\\\\\", dest=\\\\\\\"text\\\\\\\", type=str, required=True, default=argparse.SUPPRESS)\\\\n_parsed_args = vars(_parser.parse_args())\\\\n\\\\n_outputs = hello_world(**_parsed_args)\\\\n\\\"],\\\"image\\\":\\\"python:3.7\\\"}\"}},\"spec\":{\"templates\":[{\"name\":\"system-container-driver\",\"inputs\":{\"parameters\":[{\"name\":\"component\"},{\"name\":\"task\"},{\"name\":\"container\"},{\"name\":\"parent-dag-id\"},{\"name\":\"iteration-index\",\"default\":\"-1\"}]},\"outputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"valueFrom\":{\"path\":\"/tmp/outputs/pod-spec-patch\",\"default\":\"\"}},{\"name\":\"cached-decision\",\"default\":\"false\",\"valueFrom\":{\"path\":\"/tmp/outputs/cached-decision\",\"default\":\"false\"}},{\"name\":\"condition\",\"valueFrom\":{\"path\":\"/tmp/outputs/condition\",\"default\":\"true\"}}]},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-driver@sha256:d8f18e6f6cd84a43d45eb339d7e6d2dfed45358e713aaf0ddf7812b06234e9f5\",\"command\":[\"driver\"],\"args\":[\"--type\",\"CONTAINER\",\"--pipeline_name\",\"namespace/n1/pipeline/hello-world\",\"--run_id\",\"{{workflow.uid}}\",\"--dag_execution_id\",\"{{inputs.parameters.parent-dag-id}}\",\"--component\",\"{{inputs.parameters.component}}\",\"--task\",\"{{inputs.parameters.task}}\",\"--container\",\"{{inputs.parameters.container}}\",\"--iteration_index\",\"{{inputs.parameters.iteration-index}}\",\"--cached_decision_path\",\"{{outputs.parameters.cached-decision.path}}\",\"--pod_spec_patch_path\",\"{{outputs.parameters.pod-spec-patch.path}}\",\"--condition_path\",\"{{outputs.parameters.condition.path}}\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"512Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"64Mi\"}}}},{\"name\":\"system-container-executor\",\"inputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\"},{\"name\":\"cached-decision\",\"default\":\"false\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"executor\",\"template\":\"system-container-impl\",\"arguments\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"value\":\"{{inputs.parameters.pod-spec-patch}}\"}]},\"when\":\"{{inputs.parameters.cached-decision}} != true\"}]}},{\"name\":\"system-container-impl\",\"inputs\":{\"parameters\":[{\"name\":\"pod-spec-patch\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline/should-be-overridden-during-runtime\",\"command\":[\"should-be-overridden-during-runtime\"],\"envFrom\":[{\"configMapRef\":{\"name\":\"metadata-grpc-configmap\",\"optional\":true}}],\"env\":[{\"name\":\"KFP_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"KFP_POD_UID\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.uid\"}}}],\"resources\":{},\"volumeMounts\":[{\"name\":\"kfp-launcher\",\"mountPath\":\"/kfp-launcher\"}]},\"volumes\":[{\"name\":\"kfp-launcher\",\"emptyDir\":{}}],\"initContainers\":[{\"name\":\"kfp-launcher\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-launcher-v2@sha256:98a25728bfdc5a91a54f93f388bd16fa386008164e665ea797a619096a131c1a\",\"command\":[\"launcher-v2\",\"--copy\",\"/kfp-launcher/launch\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"128Mi\"},\"requests\":{\"cpu\":\"100m\"}},\"volumeMounts\":[{\"name\":\"kfp-launcher\",\"mountPath\":\"/kfp-launcher\"}]}],\"podSpecPatch\":\"{{inputs.parameters.pod-spec-patch}}\"},{\"name\":\"root\",\"inputs\":{\"parameters\":[{\"name\":\"parent-dag-id\"}]},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"hello-world-driver\",\"template\":\"system-container-driver\",\"arguments\":{\"parameters\":[{\"name\":\"component\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/components-comp-hello-world}}\"},{\"name\":\"task\",\"value\":\"{\\\"cachingOptions\\\":{\\\"enableCache\\\":true},\\\"componentRef\\\":{\\\"name\\\":\\\"comp-hello-world\\\"},\\\"inputs\\\":{\\\"parameters\\\":{\\\"text\\\":{\\\"componentInputParameter\\\":\\\"text\\\"}}},\\\"taskInfo\\\":{\\\"name\\\":\\\"hello-world\\\"}}\"},{\"name\":\"container\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/implementations-comp-hello-world}}\"},{\"name\":\"parent-dag-id\",\"value\":\"{{inputs.parameters.parent-dag-id}}\"}]}},{\"name\":\"hello-world\",\"template\":\"system-container-executor\",\"arguments\":{\"parameters\":[{\"name\":\"pod-spec-patch\",\"value\":\"{{tasks.hello-world-driver.outputs.parameters.pod-spec-patch}}\"},{\"name\":\"cached-decision\",\"default\":\"false\",\"value\":\"{{tasks.hello-world-driver.outputs.parameters.cached-decision}}\"}]},\"depends\":\"hello-world-driver.Succeeded\"}]}},{\"name\":\"system-dag-driver\",\"inputs\":{\"parameters\":[{\"name\":\"component\"},{\"name\":\"runtime-config\",\"default\":\"\"},{\"name\":\"task\",\"default\":\"\"},{\"name\":\"parent-dag-id\",\"default\":\"0\"},{\"name\":\"iteration-index\",\"default\":\"-1\"},{\"name\":\"driver-type\",\"default\":\"DAG\"}]},\"outputs\":{\"parameters\":[{\"name\":\"execution-id\",\"valueFrom\":{\"path\":\"/tmp/outputs/execution-id\"}},{\"name\":\"iteration-count\",\"valueFrom\":{\"path\":\"/tmp/outputs/iteration-count\",\"default\":\"0\"}},{\"name\":\"condition\",\"valueFrom\":{\"path\":\"/tmp/outputs/condition\",\"default\":\"true\"}}]},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"container\":{\"name\":\"\",\"image\":\"gcr.io/ml-pipeline-test/dev/kfp-driver@sha256:d8f18e6f6cd84a43d45eb339d7e6d2dfed45358e713aaf0ddf7812b06234e9f5\",\"command\":[\"driver\"],\"args\":[\"--type\",\"{{inputs.parameters.driver-type}}\",\"--pipeline_name\",\"namespace/n1/pipeline/hello-world\",\"--run_id\",\"{{workflow.uid}}\",\"--dag_execution_id\",\"{{inputs.parameters.parent-dag-id}}\",\"--component\",\"{{inputs.parameters.component}}\",\"--task\",\"{{inputs.parameters.task}}\",\"--runtime_config\",\"{{inputs.parameters.runtime-config}}\",\"--iteration_index\",\"{{inputs.parameters.iteration-index}}\",\"--execution_id_path\",\"{{outputs.parameters.execution-id.path}}\",\"--iteration_count_path\",\"{{outputs.parameters.iteration-count.path}}\",\"--condition_path\",\"{{outputs.parameters.condition.path}}\"],\"resources\":{\"limits\":{\"cpu\":\"500m\",\"memory\":\"512Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"64Mi\"}}}},{\"name\":\"entrypoint\",\"inputs\":{},\"outputs\":{},\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}},\"dag\":{\"tasks\":[{\"name\":\"root-driver\",\"template\":\"system-dag-driver\",\"arguments\":{\"parameters\":[{\"name\":\"component\",\"value\":\"{{workflow.annotations.pipelines.kubeflow.org/components-root}}\"},{\"name\":\"runtime-config\",\"value\":\"{\\\"parameterValues\\\":{\\\"param2\\\":\\\"world\\\"}}\"},{\"name\":\"driver-type\",\"value\":\"ROOT_DAG\"}]}},{\"name\":\"root\",\"template\":\"root\",\"arguments\":{\"parameters\":[{\"name\":\"parent-dag-id\",\"value\":\"{{tasks.root-driver.outputs.parameters.execution-id}}\"},{\"name\":\"condition\",\"value\":\"\"}]},\"depends\":\"root-driver.Succeeded\"}]}}],\"entrypoint\":\"entrypoint\",\"arguments\":{},\"serviceAccountName\":\"pipeline-runner\",\"podMetadata\":{\"annotations\":{\"pipelines.kubeflow.org/v2_component\":\"true\"},\"labels\":{\"pipelines.kubeflow.org/v2_component\":\"true\"}}},\"status\":{\"startedAt\":null,\"finishedAt\":null}}" @@ -230,6 +175,7 @@ func TestToSwfCRDResourceGeneratedName_EmptyName(t *testing.T) { } func TestScheduledWorkflow(t *testing.T) { + v2SpecHelloWorldYAML := loadYaml(t, "testdata/hello_world.yaml") v2Template, _ := New([]byte(v2SpecHelloWorldYAML)) modelJob := &model.Job{ @@ -300,3 +246,77 @@ func TestModelToCRDTrigger_Cron(t *testing.T) { assert.Nil(t, err) assert.Equal(t, expectedCRDTrigger, actualCRDTrigger) } + +func loadYaml(t *testing.T, path string) string { + res, err := ioutil.ReadFile(path) + if err != nil { + t.Error(err) + } + return string(res) +} + +func TestIsPlatformSpecWithKubernetesConfig(t *testing.T) { + template := loadYaml(t, "testdata/pipeline_with_volume.yaml") + splitTemplate := strings.Split(template, "\n---\n") + assert.True(t, isPlatformSpecWithKubernetesConfig([]byte(splitTemplate[1]))) + assert.False(t, isPlatformSpecWithKubernetesConfig([]byte(splitTemplate[0]))) + assert.False(t, isPlatformSpecWithKubernetesConfig([]byte(" "))) +} + +func TestNewTemplate_V2(t *testing.T) { + template := loadYaml(t, "testdata/hello_world.yaml") + expectedSpecJson, _ := yaml.YAMLToJSON([]byte(template)) + var expectedSpec pipelinespec.PipelineSpec + protojson.Unmarshal(expectedSpecJson, &expectedSpec) + expectedTemplate := &V2Spec{ + spec: &expectedSpec, + } + templateV2Spec, err := New([]byte(template)) + assert.Nil(t, err) + assert.Equal(t, expectedTemplate, templateV2Spec) +} + +func TestNewTemplate_WithPlatformSpec(t *testing.T) { + template := loadYaml(t, "testdata/pipeline_with_volume.yaml") + var expectedPipelineSpec pipelinespec.PipelineSpec + var expectedPlatformSpec pipelinespec.PlatformSpec + + splitTemplate := strings.Split(template, "\n---\n") + expectedSpecJson, _ := yaml.YAMLToJSON([]byte(splitTemplate[0])) + protojson.Unmarshal(expectedSpecJson, &expectedPipelineSpec) + + expectedPlatformSpecJson, _ := yaml.YAMLToJSON([]byte(splitTemplate[1])) + protojson.Unmarshal(expectedPlatformSpecJson, &expectedPlatformSpec) + + expectedTemplate := &V2Spec{ + spec: &expectedPipelineSpec, + platformSpec: &expectedPlatformSpec, + } + templateV2Spec, err := New([]byte(template)) + assert.Nil(t, err) + assert.Equal(t, expectedTemplate, templateV2Spec) +} + +// Verify that the V2Spec object created from Bytes() method is the same as the original object. +// The byte slice may be slightly different from the original input during the conversion, +// so we verify the parsed object. +func TestBytes_V2_WithExecutorConfig(t *testing.T) { + template := loadYaml(t, "testdata/pipeline_with_volume.yaml") + templateV2Spec, _ := New([]byte(template)) + templateBytes := templateV2Spec.Bytes() + newTemplateV2Spec, err := New(templateBytes) + assert.Nil(t, err) + assert.Equal(t, templateV2Spec, newTemplateV2Spec) +} + +// Verify that the V2Spec object created from Bytes() method is the same as the original object. +// The byte slice may be slightly different from the original input during the conversion, +// so we verify the parsed object. +func TestBytes_V2(t *testing.T) { + template := loadYaml(t, "testdata/hello_world.yaml") + templateV2Spec, _ := New([]byte(template)) + templateBytes := templateV2Spec.Bytes() + newTemplateV2Spec, err := New(templateBytes) + assert.Nil(t, err) + assert.Equal(t, templateV2Spec, newTemplateV2Spec) +} diff --git a/backend/src/apiserver/template/testdata/hello_world.yaml b/backend/src/apiserver/template/testdata/hello_world.yaml new file mode 100644 index 00000000000..0ec055fa12d --- /dev/null +++ b/backend/src/apiserver/template/testdata/hello_world.yaml @@ -0,0 +1,56 @@ +# this is a comment +components: + comp-hello-world: + executorLabel: exec-hello-world + inputDefinitions: + parameters: + text: + type: STRING +deploymentSpec: + executors: + exec-hello-world: + container: + args: + - "--text" + - "{{$.inputs.parameters['text']}}" + command: + - sh + - "-ec" + - | + program_path=$(mktemp) + printf "%s" "$0" > "$program_path" + python3 -u "$program_path" "$@" + - | + def hello_world(text): + print(text) + return text + + import argparse + _parser = argparse.ArgumentParser(prog='Hello world', description='') + _parser.add_argument("--text", dest="text", type=str, required=True, default=argparse.SUPPRESS) + _parsed_args = vars(_parser.parse_args()) + + _outputs = hello_world(**_parsed_args) + image: python:3.7 +pipelineInfo: + name: namespace/n1/pipeline/hello-world +root: + dag: + tasks: + hello-world: + cachingOptions: + enableCache: true + componentRef: + name: comp-hello-world + inputs: + parameters: + text: + componentInputParameter: text + taskInfo: + name: hello-world + inputDefinitions: + parameters: + text: + type: STRING +schemaVersion: 2.0.0 +sdkVersion: kfp-1.6.5 \ No newline at end of file diff --git a/backend/src/apiserver/template/testdata/pipeline_with_volume.yaml b/backend/src/apiserver/template/testdata/pipeline_with_volume.yaml new file mode 100644 index 00000000000..84b7b66d428 --- /dev/null +++ b/backend/src/apiserver/template/testdata/pipeline_with_volume.yaml @@ -0,0 +1,218 @@ +# PIPELINE DEFINITION +# Name: my-pipeline +components: + comp-comp: + executorLabel: exec-comp + comp-comp-2: + executorLabel: exec-comp-2 + comp-comp-3: + executorLabel: exec-comp-3 + comp-createpvc: + executorLabel: exec-createpvc + inputDefinitions: + parameters: + access_modes: + parameterType: LIST + annotations: + isOptional: true + parameterType: STRUCT + pvc_name: + isOptional: true + parameterType: STRING + pvc_name_suffix: + isOptional: true + parameterType: STRING + size: + parameterType: STRING + storage_class: + defaultValue: '' + isOptional: true + parameterType: STRING + volume_name: + isOptional: true + parameterType: STRING + outputDefinitions: + parameters: + name: + parameterType: STRING + comp-deletepvc: + executorLabel: exec-deletepvc + inputDefinitions: + parameters: + pvc_name: + parameterType: STRING +deploymentSpec: + executors: + exec-comp: + container: + args: + - --executor_input + - '{{$}}' + - --function_to_execute + - comp + command: + - sh + - -c + - "\nif ! [ -x \"$(command -v pip)\" ]; then\n python3 -m ensurepip ||\ + \ python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1\ + \ python3 -m pip install --quiet --no-warn-script-location 'kfp==2.0.0-beta.13'\ + \ && \"$0\" \"$@\"\n" + - sh + - -ec + - 'program_path=$(mktemp -d) + + printf "%s" "$0" > "$program_path/ephemeral_component.py" + + python3 -m kfp.components.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@" + + ' + - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import\ + \ *\n\ndef comp():\n pass\n\n" + image: python:3.7 + exec-comp-2: + container: + args: + - --executor_input + - '{{$}}' + - --function_to_execute + - comp + command: + - sh + - -c + - "\nif ! [ -x \"$(command -v pip)\" ]; then\n python3 -m ensurepip ||\ + \ python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1\ + \ python3 -m pip install --quiet --no-warn-script-location 'kfp==2.0.0-beta.13'\ + \ && \"$0\" \"$@\"\n" + - sh + - -ec + - 'program_path=$(mktemp -d) + + printf "%s" "$0" > "$program_path/ephemeral_component.py" + + python3 -m kfp.components.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@" + + ' + - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import\ + \ *\n\ndef comp():\n pass\n\n" + image: python:3.7 + exec-comp-3: + container: + args: + - --executor_input + - '{{$}}' + - --function_to_execute + - comp + command: + - sh + - -c + - "\nif ! [ -x \"$(command -v pip)\" ]; then\n python3 -m ensurepip ||\ + \ python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1\ + \ python3 -m pip install --quiet --no-warn-script-location 'kfp==2.0.0-beta.13'\ + \ && \"$0\" \"$@\"\n" + - sh + - -ec + - 'program_path=$(mktemp -d) + + printf "%s" "$0" > "$program_path/ephemeral_component.py" + + python3 -m kfp.components.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@" + + ' + - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import\ + \ *\n\ndef comp():\n pass\n\n" + image: python:3.7 + exec-createpvc: + container: + image: argostub/createpvc + exec-deletepvc: + container: + image: argostub/deletepvc +pipelineInfo: + name: my-pipeline +root: + dag: + tasks: + comp: + cachingOptions: + enableCache: true + componentRef: + name: comp-comp + taskInfo: + name: comp + comp-2: + cachingOptions: + enableCache: true + componentRef: + name: comp-comp-2 + dependentTasks: + - comp + taskInfo: + name: comp-2 + comp-3: + cachingOptions: + enableCache: true + componentRef: + name: comp-comp-3 + taskInfo: + name: comp-3 + createpvc: + cachingOptions: + enableCache: true + componentRef: + name: comp-createpvc + inputs: + parameters: + access_modes: + runtimeValue: + constant: + - ReadWriteMany + pvc_name_suffix: + runtimeValue: + constant: -my-pvc + size: + runtimeValue: + constant: 5Gi + storage_class: + runtimeValue: + constant: standard + taskInfo: + name: createpvc + deletepvc: + cachingOptions: + enableCache: true + componentRef: + name: comp-deletepvc + dependentTasks: + - comp-2 + - createpvc + inputs: + parameters: + pvc_name: + taskOutputParameter: + outputParameterKey: name + producerTask: createpvc + taskInfo: + name: deletepvc +schemaVersion: 2.1.0 +sdkVersion: kfp-2.0.0-beta.13 +--- +platforms: + kubernetes: + deploymentSpec: + executors: + exec-comp: + pvcMount: + - mountPath: /data + taskOutputParameter: + outputParameterKey: name + producerTask: createpvc + exec-comp-2: + pvcMount: + - mountPath: /reused_data + taskOutputParameter: + outputParameterKey: name + producerTask: createpvc + exec-comp-3: + pvcMount: + - constant: my-existing-pvc + mountPath: /data \ No newline at end of file diff --git a/backend/src/apiserver/template/v2_template.go b/backend/src/apiserver/template/v2_template.go index 38c874eba8b..2060b8b545a 100644 --- a/backend/src/apiserver/template/v2_template.go +++ b/backend/src/apiserver/template/v2_template.go @@ -15,6 +15,7 @@ package template import ( + "bytes" "fmt" "regexp" @@ -26,11 +27,13 @@ import ( scheduledworkflow "github.com/kubeflow/pipelines/backend/src/crd/pkg/apis/scheduledworkflow/v1beta1" "github.com/kubeflow/pipelines/backend/src/v2/compiler/argocompiler" "google.golang.org/protobuf/encoding/protojson" + goyaml "gopkg.in/yaml.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type V2Spec struct { - spec *pipelinespec.PipelineSpec + spec *pipelinespec.PipelineSpec + platformSpec *pipelinespec.PlatformSpec } // Converts modelJob to ScheduledWorkflow. @@ -107,43 +110,94 @@ func (t *V2Spec) GetTemplateType() TemplateType { func NewV2SpecTemplate(template []byte) (*V2Spec, error) { var spec pipelinespec.PipelineSpec - templateJson, err := yaml.YAMLToJSON(template) - if err != nil { - return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, fmt.Sprintf("cannot convert v2 pipeline spec to json format: %s", err.Error())) - } - err = protojson.Unmarshal(templateJson, &spec) - if err != nil { - return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, fmt.Sprintf("invalid v2 pipeline spec: %s", err.Error())) + var v2Spec V2Spec + decoder := goyaml.NewDecoder(bytes.NewReader(template)) + for { + var value map[string]interface{} + // Break at end of file + if decoder.Decode(&value) != nil { + break + } + valueBytes, err := goyaml.Marshal(value) + if err != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, fmt.Sprintf("unable to marshal this yaml document: %s", err.Error())) + } + if isPipelineSpec(valueBytes) { + // Pick out the yaml document with pipeline spec + if v2Spec.spec != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "multiple pipeline specs provided") + } + pipelineSpecJson, err := yaml.YAMLToJSON(valueBytes) + if err != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, fmt.Sprintf("cannot convert v2 pipeline spec to json format: %s", err.Error())) + } + err = protojson.Unmarshal(pipelineSpecJson, &spec) + if err != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, fmt.Sprintf("invalid v2 pipeline spec: %s", err.Error())) + } + if spec.GetPipelineInfo().GetName() == "" { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: name is empty") + } + match, _ := regexp.MatchString("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*", spec.GetPipelineInfo().GetName()) + if !match { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: name should consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character") + } + if spec.GetRoot() == nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: root component is empty") + } + v2Spec.spec = &spec + } else if isPlatformSpecWithKubernetesConfig(valueBytes) { + // Pick out the yaml document with platform spec + if v2Spec.platformSpec != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPlatformSpec, "multiple platform specs provided") + } + var platformSpec pipelinespec.PlatformSpec + platformSpecJson, err := yaml.YAMLToJSON(valueBytes) + if err != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPlatformSpec, fmt.Sprintf("cannot convert platform specific configs to json format: %s", err.Error())) + } + err = protojson.Unmarshal(platformSpecJson, &platformSpec) + if err != nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPlatformSpec, fmt.Sprintf("cannot unmarshal platform specific configs: %s", err.Error())) + } + v2Spec.platformSpec = &platformSpec + } } - if spec.GetPipelineInfo().GetName() == "" { - return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: name is empty") + if v2Spec.spec == nil { + return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "no pipeline spec is provided") } - match, _ := regexp.MatchString("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*", spec.GetPipelineInfo().GetName()) - if !match { - return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: name should consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character") - } - if spec.GetRoot() == nil { - return nil, util.NewInvalidInputErrorWithDetails(ErrorInvalidPipelineSpec, "invalid v2 pipeline spec: root component is empty") - } - - return &V2Spec{spec: &spec}, nil + return &v2Spec, nil } func (t *V2Spec) Bytes() []byte { if t == nil { return nil } - bytes, err := protojson.Marshal(t.spec) + bytesSpec, err := protojson.Marshal(t.spec) if err != nil { // this is unexpected, cannot convert proto message to JSON return nil } - bytesYAML, err := yaml.JSONToYAML(bytes) + bytes, err := yaml.JSONToYAML(bytesSpec) if err != nil { // this is unexpected, cannot convert JSON to YAML return nil } - return bytesYAML + if t.platformSpec != nil { + bytesExecutorConfig, err := protojson.Marshal(t.platformSpec) + if err != nil { + // this is unexpected, cannot convert proto message to JSON + return nil + } + bytesExecutorConfigYaml, err := yaml.JSONToYAML(bytesExecutorConfig) + if err != nil { + // this is unexpected, cannot convert JSON to YAML + return nil + } + bytes = append(bytes, []byte("\n---\n")...) + bytes = append(bytes, bytesExecutorConfigYaml...) + } + return bytes } func (t *V2Spec) IsV2() bool { @@ -217,3 +271,17 @@ func (t *V2Spec) RunWorkflow(modelRun *model.Run, options RunWorkflowOptions) (u executionSpec.SetPodMetadataLabels(util.LabelKeyWorkflowRunId, options.RunId) return executionSpec, nil } + +func isPlatformSpecWithKubernetesConfig(template []byte) bool { + var platformSpec pipelinespec.PlatformSpec + templateJson, err := yaml.YAMLToJSON(template) + if err != nil { + return false + } + err = protojson.Unmarshal(templateJson, &platformSpec) + if err != nil { + return false + } + _, ok := platformSpec.Platforms["kubernetes"] + return ok +} diff --git a/backend/third_party_licenses/apiserver.csv b/backend/third_party_licenses/apiserver.csv index 0d86e3d4417..1c20e7005e9 100644 --- a/backend/third_party_licenses/apiserver.csv +++ b/backend/third_party_licenses/apiserver.csv @@ -60,7 +60,7 @@ github.com/json-iterator/go,https://github.com/json-iterator/go/blob/v1.1.12/LIC github.com/klauspost/compress/flate,https://github.com/klauspost/compress/blob/v1.14.2/LICENSE,Apache-2.0 github.com/klauspost/cpuid,https://github.com/klauspost/cpuid/blob/v1.3.1/LICENSE,MIT github.com/klauspost/pgzip,https://github.com/klauspost/pgzip/blob/v1.2.5/LICENSE,MIT -github.com/kubeflow/pipelines/api/v2alpha1/go,https://github.com/kubeflow/pipelines/blob/d6cccc92a539/api/LICENSE,Apache-2.0 +github.com/kubeflow/pipelines/api/v2alpha1/go,https://github.com/kubeflow/pipelines/blob/10c4db9ebed9/api/LICENSE,Apache-2.0 github.com/kubeflow/pipelines/backend,https://github.com/kubeflow/pipelines/blob/HEAD/LICENSE,Apache-2.0 github.com/kubeflow/pipelines/third_party/ml-metadata/go/ml_metadata,https://github.com/kubeflow/pipelines/blob/e78ed557ddcb/third_party/ml-metadata/LICENSE,Apache-2.0 github.com/lann/builder,https://github.com/lann/builder/blob/47ae307949d0/LICENSE,MIT diff --git a/go.mod b/go.mod index f1e2b837d94..8087f5a4470 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/jinzhu/gorm v1.9.1 github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.4 // indirect - github.com/kubeflow/pipelines/api v0.0.0-20221221212450-d6cccc92a539 + github.com/kubeflow/pipelines/api v0.0.0-20230313210723-10c4db9ebed9 github.com/kubeflow/pipelines/third_party/ml-metadata v0.0.0-20220118175555-e78ed557ddcb github.com/lestrrat-go/strftime v1.0.4 github.com/mattn/go-sqlite3 v1.14.16 diff --git a/go.sum b/go.sum index 83b580695fb..cc63149bfe1 100644 --- a/go.sum +++ b/go.sum @@ -923,8 +923,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.9.32/go.mod h1:FWxy2UK7GlK5b0NSJGc5hPqnssVlkNnsChvyuOf/Xno= -github.com/kubeflow/pipelines/api v0.0.0-20221221212450-d6cccc92a539 h1:dGXREDHbFyZn7fYMzdnn+8cnEcCufegJZJCzNPpHEYE= -github.com/kubeflow/pipelines/api v0.0.0-20221221212450-d6cccc92a539/go.mod h1:T7TOQB36gGe97yUdfVAnYK5uuT0+uQbLNHDUHxYkmE4= +github.com/kubeflow/pipelines/api v0.0.0-20230313210723-10c4db9ebed9 h1:gKmDi7U/ytR/KnfBe3Ku6HSYZM2bR0D3iIdGvCPR0pE= +github.com/kubeflow/pipelines/api v0.0.0-20230313210723-10c4db9ebed9/go.mod h1:T7TOQB36gGe97yUdfVAnYK5uuT0+uQbLNHDUHxYkmE4= github.com/kubeflow/pipelines/third_party/ml-metadata v0.0.0-20220118175555-e78ed557ddcb h1:i0RzcKBlfGHueIwrUlKB+AvVZPuMUJIYe1g8nvhwgbo= github.com/kubeflow/pipelines/third_party/ml-metadata v0.0.0-20220118175555-e78ed557ddcb/go.mod h1:chIDffBaVQ/asNl1pTTdbAymYcuBKf8BR3YtSP+3FEU= github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=