-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: deprecate and remove
create
command
- now points users at `job run`. - closes #4042
- Loading branch information
Showing
2 changed files
with
5 additions
and
378 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,258 +1,18 @@ | ||
package create | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"reflect" | ||
"strings" | ||
"time" | ||
|
||
"github.com/ipld/go-ipld-prime/codec/json" | ||
"github.com/spf13/cobra" | ||
"k8s.io/kubectl/pkg/util/i18n" | ||
"sigs.k8s.io/yaml" | ||
|
||
"github.com/bacalhau-project/bacalhau/cmd/util" | ||
"github.com/bacalhau-project/bacalhau/cmd/util/flags/cliflags" | ||
"github.com/bacalhau-project/bacalhau/cmd/util/hook" | ||
"github.com/bacalhau-project/bacalhau/cmd/util/printer" | ||
"github.com/bacalhau-project/bacalhau/pkg/config/types" | ||
legacy_job "github.com/bacalhau-project/bacalhau/pkg/legacyjob" | ||
"github.com/bacalhau-project/bacalhau/pkg/lib/marshaller" | ||
"github.com/bacalhau-project/bacalhau/pkg/model" | ||
clientv1 "github.com/bacalhau-project/bacalhau/pkg/publicapi/client" | ||
clientv2 "github.com/bacalhau-project/bacalhau/pkg/publicapi/client/v2" | ||
"github.com/bacalhau-project/bacalhau/pkg/userstrings" | ||
"github.com/bacalhau-project/bacalhau/pkg/util/templates" | ||
) | ||
|
||
var ( | ||
createLong = templates.LongDesc(i18n.T(` | ||
Create a job from a file or from stdin. | ||
JSON and YAML formats are accepted. | ||
`)) | ||
//nolint:lll // Documentation | ||
createExample = templates.Examples(i18n.T(` | ||
# Create a job using the data in job.yaml | ||
bacalhau create ./job.yaml | ||
# Create a new job from an already executed job | ||
bacalhau describe 6e51df50 | bacalhau create `)) | ||
) | ||
|
||
type CreateOptions struct { | ||
Filename string // Filename for job (can be .json or .yaml) | ||
Concurrency int // Number of concurrent jobs to run | ||
RunTimeSettings *cliflags.RunTimeSettingsWithDownload // Run time settings for execution (e.g. wait, get, etc after submission) | ||
DownloadFlags *cliflags.DownloaderSettings // Settings for running Download | ||
} | ||
|
||
func NewCreateOptions() *CreateOptions { | ||
return &CreateOptions{ | ||
Filename: "", | ||
Concurrency: 1, | ||
DownloadFlags: cliflags.NewDefaultDownloaderSettings(), | ||
RunTimeSettings: cliflags.DefaultRunTimeSettingsWithDownload(), | ||
} | ||
} | ||
|
||
func NewCmd() *cobra.Command { | ||
OC := NewCreateOptions() | ||
|
||
createCmd := &cobra.Command{ | ||
Use: "create", | ||
Short: "Create a job using a json or yaml file.", | ||
Long: createLong, | ||
Example: createExample, | ||
Args: cobra.MinimumNArgs(0), | ||
PreRunE: hook.RemoteCmdPreRunHooks, | ||
PostRunE: hook.RemoteCmdPostRunHooks, | ||
Use: "create", | ||
Short: "DEPRECATED: Create a job using a json or yaml file.", | ||
Deprecated: "This command has moved! Please use `job run` to create jobs.", | ||
Args: cobra.MinimumNArgs(0), | ||
RunE: func(cmd *cobra.Command, cmdArgs []string) error { | ||
// initialize a new or open an existing repo merging any config file(s) it contains into cfg. | ||
cfg, err := util.SetupRepoConfig(cmd) | ||
if err != nil { | ||
return fmt.Errorf("failed to setup repo: %w", err) | ||
} | ||
// create a v1 api client | ||
apiV1, err := util.GetAPIClient(cfg) | ||
if err != nil { | ||
return fmt.Errorf("failed to create v1 api client: %w", err) | ||
} | ||
// create a v2 api client | ||
apiV2, err := util.GetAPIClientV2(cmd, cfg) | ||
if err != nil { | ||
return fmt.Errorf("failed to create v2 api client: %w", err) | ||
} | ||
return create(cmd, cmdArgs, apiV1, apiV2, cfg, OC) | ||
return nil | ||
}, | ||
} | ||
|
||
createCmd.Flags().AddFlagSet(cliflags.NewDownloadFlags(OC.DownloadFlags)) | ||
createCmd.Flags().AddFlagSet(cliflags.NewRunTimeSettingsFlagsWithDownload(OC.RunTimeSettings)) | ||
|
||
return createCmd | ||
} | ||
|
||
//nolint:funlen,gocyclo | ||
func create( | ||
cmd *cobra.Command, | ||
cmdArgs []string, | ||
apiV1 *clientv1.APIClient, | ||
apiV2 clientv2.API, | ||
cfg types.BacalhauConfig, | ||
OC *CreateOptions, | ||
) error { //nolint:funlen,gocyclo | ||
ctx := cmd.Context() | ||
|
||
// Custom unmarshaller | ||
// https://stackoverflow.com/questions/70635636/unmarshaling-yaml-into-different-struct-based-off-yaml-field?rq=1 | ||
var jwi model.JobWithInfo | ||
var j *model.Job | ||
var byteResult []byte | ||
var rawMap map[string]interface{} | ||
|
||
j, err := model.NewJobWithSaneProductionDefaults() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(cmdArgs) == 0 { | ||
byteResult, err = util.ReadFromStdinIfAvailable(cmd) | ||
if err != nil { | ||
return fmt.Errorf("unknown error reading from file or stdin: %w", err) | ||
} | ||
} else { | ||
OC.Filename = cmdArgs[0] | ||
|
||
var fileContent *os.File | ||
fileContent, err = os.Open(OC.Filename) | ||
|
||
if err != nil { | ||
return fmt.Errorf("error opening file: %w", err) | ||
} | ||
|
||
byteResult, err = io.ReadAll(fileContent) | ||
if err != nil { | ||
return fmt.Errorf("error reading file: %w", err) | ||
} | ||
} | ||
|
||
// Do a first pass for parsing to see if it's a Job or JobWithInfo | ||
err = marshaller.YAMLUnmarshalWithMax(byteResult, &rawMap) | ||
if err != nil { | ||
return fmt.Errorf("error parsing file: %w", err) | ||
} | ||
|
||
// If it's a JobWithInfo, we need to convert it to a Job | ||
if _, isJobWithInfo := rawMap["Job"]; isJobWithInfo { | ||
err = marshaller.YAMLUnmarshalWithMax(byteResult, &jwi) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
byteResult, err = marshaller.YAMLMarshalWithMax(jwi.Job) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
} else if _, isTask := rawMap["with"]; isTask { | ||
// Else it might be a IPVM Task in JSON format | ||
var task *model.Task | ||
task, err = model.UnmarshalIPLD[model.Task](byteResult, json.Decode, model.UCANTaskSchema) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
|
||
job, err := model.NewJobWithSaneProductionDefaults() | ||
if err != nil { | ||
// TODO this is a bit extreme, maybe just ensure the above call doesn't return an error? the mergo package is a bit pointless there. | ||
panic(err) | ||
} | ||
|
||
spec, err := task.ToSpec() | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
|
||
job.Spec = *spec | ||
byteResult, err = marshaller.YAMLMarshalWithMax(job) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
} | ||
|
||
if len(byteResult) == 0 { | ||
// TODO better error | ||
return fmt.Errorf("%s: job is empty", userstrings.JobSpecBad) | ||
} | ||
|
||
// Turns out the yaml parser supports both yaml & json (because json is a subset of yaml) | ||
// so we can just use that | ||
err = marshaller.YAMLUnmarshalWithMax(byteResult, &j) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err) | ||
} | ||
|
||
// See if the job spec is empty | ||
if j == nil || reflect.DeepEqual(j.Spec, &model.Job{}) { | ||
// TODO better error | ||
return fmt.Errorf("%s: job is empty", userstrings.JobSpecBad) | ||
} | ||
|
||
// Warn on fields with data that will be ignored | ||
var unusedFieldList []string | ||
if j.Metadata.ClientID != "" { | ||
unusedFieldList = append(unusedFieldList, "ClientID") | ||
j.Metadata.ClientID = "" | ||
} | ||
if !reflect.DeepEqual(j.Metadata.CreatedAt, time.Time{}) { | ||
unusedFieldList = append(unusedFieldList, "CreatedAt") | ||
j.Metadata.CreatedAt = time.Time{} | ||
} | ||
if j.Metadata.ID != "" { | ||
unusedFieldList = append(unusedFieldList, "ID") | ||
j.Metadata.ID = "" | ||
} | ||
if j.Metadata.Requester.RequesterNodeID != "" { | ||
unusedFieldList = append(unusedFieldList, "RequesterNodeID") | ||
j.Metadata.Requester.RequesterNodeID = "" | ||
} | ||
if len(j.Metadata.Requester.RequesterPublicKey) != 0 { | ||
unusedFieldList = append(unusedFieldList, "RequesterPublicKey") | ||
j.Metadata.Requester.RequesterPublicKey = nil | ||
} | ||
|
||
if !model.IsValidPublisher(j.Spec.PublisherSpec.Type) { | ||
j.Spec.PublisherSpec = model.PublisherSpec{ | ||
//nolint:staticcheck // TODO: remove this when we have a proper publisher | ||
Type: j.Spec.Publisher, | ||
} | ||
} | ||
|
||
// Warn on fields with data that will be ignored | ||
if len(unusedFieldList) > 0 { | ||
cmd.Printf("WARNING: The following fields have data in them and will be ignored on creation: %s\n", strings.Join(unusedFieldList, ", ")) | ||
} | ||
|
||
err = legacy_job.VerifyJob(ctx, j) | ||
if err != nil { | ||
return fmt.Errorf("error verifying job: %w", err) | ||
} | ||
if OC.RunTimeSettings.DryRun { | ||
// Converting job to yaml | ||
var yamlBytes []byte | ||
yamlBytes, err = yaml.Marshal(j) | ||
if err != nil { | ||
return fmt.Errorf("error converting job to yaml: %w", err) | ||
} | ||
cmd.Print(string(yamlBytes)) | ||
return nil | ||
} | ||
|
||
executingJob, err := apiV1.Submit(ctx, j) | ||
if err != nil { | ||
return fmt.Errorf("submitting job for execution: %w", err) | ||
} | ||
|
||
return printer.PrintJobExecutionLegacy(ctx, executingJob, cmd, OC.DownloadFlags, OC.RunTimeSettings, apiV1, apiV2, cfg) | ||
} |
This file was deleted.
Oops, something went wrong.