Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions cmd/dotenv/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,7 @@ func (suite *DotenvModelTestSuite) FinalModel(tm *teatest.TestModel) Model {
}

fm, ok := finalModel.(Model)
if !ok {
suite.T().Error("Final model is not of type Model")
}
suite.Require().True(ok, "Final model is not of type Model")

return fm
}
Expand Down
57 changes: 57 additions & 0 deletions cmd/pipeline/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2026 DataRobot, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pipeline

import (
"github.com/datarobot/cli/cmd/pipeline/create"
"github.com/datarobot/cli/cmd/pipeline/del"
"github.com/datarobot/cli/cmd/pipeline/get"
"github.com/datarobot/cli/cmd/pipeline/graph"
"github.com/datarobot/cli/cmd/pipeline/list"
"github.com/datarobot/cli/cmd/pipeline/lock"
"github.com/datarobot/cli/cmd/pipeline/update"
"github.com/datarobot/cli/cmd/pipeline/version"
"github.com/datarobot/cli/internal/features"
"github.com/spf13/cobra"
)

func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "pipeline",
Aliases: []string{"pipelines"},
GroupID: "core",
Short: "Pipelines API management commands",
Long: `Manage AI/ML pipelines orchestrated by Covalent.

Create, list, inspect, and update pipelines registered with the
DataRobot pipelines service. Sub-commands are also available for managing
input payloads, runs, and recurring schedules.`,
}

features.SetGate(cmd, "pipeline")

cmd.AddCommand(
create.Cmd(),
get.Cmd(),
list.Cmd(),
update.Cmd(),
del.Cmd(),
lock.Cmd(),
version.Cmd(),
graph.Cmd(),
)

return cmd
}
107 changes: 107 additions & 0 deletions cmd/pipeline/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2026 DataRobot, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pipeline

import (
"testing"

"github.com/datarobot/cli/internal/features"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

func TestCmd_BasicMetadata(t *testing.T) {
cmd := Cmd()

assert.Equal(t, "pipeline", cmd.Use)
assert.Equal(t, "core", cmd.GroupID)
assert.NotEmpty(t, cmd.Short)
assert.NotEmpty(t, cmd.Long)
}

func TestCmd_HasAlias(t *testing.T) {
cmd := Cmd()

assert.Contains(t, cmd.Aliases, "pipelines")
}

func TestCmd_FeatureGate(t *testing.T) {
cmd := Cmd()

gate, ok := cmd.Annotations[features.AnnotationKey]
assert.True(t, ok, "expected feature-gate annotation to be set")
assert.Equal(t, "pipeline", gate)
}

func TestCmd_IsGroupOnly(t *testing.T) {
cmd := Cmd()

assert.Nil(t, cmd.RunE, "pipeline is a group command and should not have a RunE")
}

func TestCmd_HasExpectedSubcommands(t *testing.T) {
cmd := Cmd()

want := map[string]bool{
"create": false,
"get": false,
"list": false,
"update": false,
"delete": false,
"lock": false,
"version": false,
"graph": false,
}

for _, sub := range cmd.Commands() {
if _, ok := want[sub.Name()]; ok {
want[sub.Name()] = true
}
}

for name, found := range want {
assert.True(t, found, "expected subcommand %q to be registered", name)
}
}

func TestCmd_VersionHasSubcommands(t *testing.T) {
cmd := Cmd()

var versionCmd *cobra.Command
for _, sub := range cmd.Commands() {
if sub.Name() == "version" {
versionCmd = sub

break
}
}

assert.NotNil(t, versionCmd, "version subcommand must be registered")

want := map[string]bool{
"get": false,
"list": false,
}

for _, sub := range versionCmd.Commands() {
if _, ok := want[sub.Name()]; ok {
want[sub.Name()] = true
}
}

for name, found := range want {
assert.True(t, found, "expected version subcommand %q to be registered", name)
}
}
105 changes: 105 additions & 0 deletions cmd/pipeline/create/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2026 DataRobot, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package create

import (
"errors"
"fmt"

"github.com/datarobot/cli/internal/auth"
"github.com/datarobot/cli/internal/pipeline"
"github.com/datarobot/cli/internal/telemetry"
"github.com/spf13/cobra"
)

func Cmd() *cobra.Command {
var (
description string
mode string
outputFormat pipeline.OutputFormat
fromFile string
)

cmd := &cobra.Command{
Use: "create [<file>]",
Short: "Upload a Python file to create a pipeline.",
Long: `Upload a Python file containing a DataRobot pipeline (one or more tasks) to register a new pipeline.

The pipeline name is extracted from the file and used as the pipeline's resource name.
By default, output is human-readable. Use --output-format json for machine-parseable output.

The path to the Python file can be supplied either as a positional argument
or via the --from-file=<path> flag. Exactly one of the two must be provided.

Example:
dr pipeline create ./my_pipeline.py
dr pipeline create --from-file=./my_pipeline.py
dr pipeline create ./my_pipeline.py --description "First draft" --mode draft
dr pipeline create --from-file=./my_pipeline.py --output-format json`,
Args: cobra.MaximumNArgs(1),
PreRunE: auth.EnsureAuthenticatedE,
RunE: func(_ *cobra.Command, args []string) error {
if mode != "" && mode != pipeline.ModeDraft && mode != pipeline.ModeLocked {
return fmt.Errorf("invalid mode: %s (supported: draft, locked)", mode)
}

filePath, err := resolveFilePath(args, fromFile)
if err != nil {
return err
}

result, err := pipeline.CreatePipeline(filePath, description, mode)
if err != nil {
return err
}

return pipeline.RenderCreateResponse(outputFormat, *result)
},
}

cmd.Flags().StringVar(&description, "description", "", "Optional description for the pipeline")
cmd.Flags().StringVar(&mode, "mode", "", "Pipeline mode: draft (default) or locked")
cmd.Flags().StringVar(&fromFile, "from-file", "", "Path to the Python file to upload, e.g. --from-file=./my_pipeline.py (alternative to the positional argument)")
pipeline.AddOutputFlag(cmd, &outputFormat)

telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any {
return map[string]any{
"mode": mode,
"output_format": string(outputFormat),
}
})

return cmd
}

// resolveFilePath returns the file path supplied either positionally or via
// --from-file. Exactly one of the two must be provided.
func resolveFilePath(args []string, fromFile string) (string, error) {
positional := ""
if len(args) > 0 {
positional = args[0]
}

switch {
case positional != "" && fromFile != "":
return "", errors.New("specify the file either as a positional argument or via --from-file, not both")
case positional != "":
return positional, nil
case fromFile != "":
return fromFile, nil
default:
return "", errors.New("a file path is required (positional argument or --from-file)")
}
}
Loading