From 7242507a005775ab0af835864e67394f11c4ce40 Mon Sep 17 00:00:00 2001 From: "Christian G. Warden" Date: Fri, 15 Jul 2022 11:25:34 -0500 Subject: [PATCH] Refactor Commands to Use Cobra For Flag and Argument Passing Use cobra for flag and argument passing to make commands more consistent. Add global --account flag to use an account named on the command line rather than the current active account. Update bulk commands to only support sub-command mode. Generate documentation from cobra. Add flags to logins command filter logins by user id and org id. Allow :: to separate class and method in `force test`. --- Makefile | 3 + README.md | 88 +++-- command/active.go | 117 +++--- command/apex.go | 112 +++--- command/apiversion.go | 56 ++- command/attrs.go | 23 -- command/aura.go | 212 ----------- command/bigobject.go | 131 +++---- command/bulk.go | 567 ++++++++++------------------- command/command.go | 87 ----- command/command_test.go | 16 - command/create.go | 138 ++++--- command/datapipe.go | 288 +++++++-------- command/describe.go | 154 ++++---- command/eventlogfile.go | 52 +-- command/export.go | 97 ++--- command/export_test.go | 3 +- command/fetch.go | 78 ++-- command/field.go | 114 +++--- command/help.go | 63 ---- command/import.go | 116 +++--- command/limits.go | 19 +- command/log.go | 87 +++-- command/login.go | 190 +++++----- command/logins.go | 102 ++++-- command/logout.go | 52 +-- command/notify.go | 35 +- command/oauth.go | 67 ++-- command/open.go | 44 +-- command/package.go | 67 ++-- command/password.go | 142 ++++---- command/push.go | 134 ++++--- command/pushAura.go | 47 +-- command/query.go | 132 +++---- command/quickdeploy.go | 53 +-- command/record.go | 210 ++++++----- command/rest.go | 162 +++++---- command/root.go | 66 ++++ command/security.go | 52 ++- command/sobject.go | 119 +++--- command/test.go | 51 +-- command/trace.go | 78 ++-- command/usedxauth.go | 33 +- command/version.go | 29 +- command/whoami.go | 36 +- docs/force.md | 53 +++ docs/force_active.md | 40 ++ docs/force_apex.md | 36 ++ docs/force_apiversion.md | 33 ++ docs/force_aura.md | 65 ++++ docs/force_bigobject.md | 53 +++ docs/force_bigobject_create.md | 37 ++ docs/force_bigobject_list.md | 24 ++ docs/force_bulk.md | 47 +++ docs/force_bulk_batch.md | 24 ++ docs/force_bulk_batches.md | 24 ++ docs/force_bulk_delete.md | 27 ++ docs/force_bulk_hardDelete.md | 27 ++ docs/force_bulk_insert.md | 27 ++ docs/force_bulk_job.md | 24 ++ docs/force_bulk_query.md | 29 ++ docs/force_bulk_retrieve.md | 24 ++ docs/force_bulk_update.md | 27 ++ docs/force_bulk_upsert.md | 28 ++ docs/force_create.md | 24 ++ docs/force_create_apexclass.md | 25 ++ docs/force_create_apexcomponent.md | 25 ++ docs/force_create_apexpage.md | 25 ++ docs/force_create_apextrigger.md | 26 ++ docs/force_datapipe.md | 37 ++ docs/force_datapipe_create.md | 29 ++ docs/force_datapipe_createjob.md | 25 ++ docs/force_datapipe_delete.md | 25 ++ docs/force_datapipe_list.md | 25 ++ docs/force_datapipe_listjobs.md | 24 ++ docs/force_datapipe_queryjob.md | 25 ++ docs/force_datapipe_update.md | 29 ++ docs/force_describe.md | 31 ++ docs/force_describe_metadata.md | 26 ++ docs/force_describe_sobject.md | 26 ++ docs/force_eventlogfile.md | 33 ++ docs/force_export.md | 36 ++ docs/force_fetch.md | 47 +++ docs/force_field.md | 51 +++ docs/force_field_create.md | 33 ++ docs/force_field_delete.md | 24 ++ docs/force_field_list.md | 24 ++ docs/force_field_type.md | 24 ++ docs/force_import.md | 45 +++ docs/force_limits.md | 33 ++ docs/force_log.md | 35 ++ docs/force_log_delete.md | 32 ++ docs/force_login.md | 50 +++ docs/force_logins.md | 34 ++ docs/force_logout.md | 24 ++ docs/force_notify.md | 30 ++ docs/force_oauth.md | 39 ++ docs/force_oauth_create.md | 42 +++ docs/force_open.md | 39 ++ docs/force_package.md | 21 ++ docs/force_package_install.md | 26 ++ docs/force_password.md | 39 ++ docs/force_password_change.md | 33 ++ docs/force_password_reset.md | 32 ++ docs/force_password_status.md | 33 ++ docs/force_push.md | 57 +++ docs/force_pushAura.md | 35 ++ docs/force_query.md | 39 ++ docs/force_quickdeploy.md | 34 ++ docs/force_record.md | 58 +++ docs/force_record_create.md | 24 ++ docs/force_record_delete.md | 24 ++ docs/force_record_get.md | 36 ++ docs/force_record_merge.md | 24 ++ docs/force_record_undelete.md | 24 ++ docs/force_record_update.md | 24 ++ docs/force_rest.md | 37 ++ docs/force_rest_get.md | 35 ++ docs/force_rest_patch.md | 25 ++ docs/force_rest_post.md | 25 ++ docs/force_rest_put.md | 25 ++ docs/force_security.md | 32 ++ docs/force_sobject.md | 47 +++ docs/force_sobject_Delete.md | 24 ++ docs/force_sobject_create.md | 24 ++ docs/force_sobject_import.md | 28 ++ docs/force_sobject_list.md | 24 ++ docs/force_test.md | 42 +++ docs/force_trace.md | 33 ++ docs/force_trace_delete.md | 24 ++ docs/force_trace_list.md | 24 ++ docs/force_trace_start.md | 24 ++ docs/force_usedxauth.md | 40 ++ docs/force_version.md | 32 ++ docs/force_whoami.md | 32 ++ docs/mkdocs.go | 15 + go.mod | 11 +- go.sum | 21 +- lib/deploy.go | 10 +- lib/force.go | 8 +- lib/metadata.go | 2 +- main.go | 33 +- 142 files changed, 4910 insertions(+), 2603 deletions(-) delete mode 100644 command/attrs.go delete mode 100644 command/aura.go delete mode 100644 command/command.go delete mode 100644 command/command_test.go delete mode 100644 command/help.go create mode 100644 command/root.go create mode 100644 docs/force.md create mode 100644 docs/force_active.md create mode 100644 docs/force_apex.md create mode 100644 docs/force_apiversion.md create mode 100644 docs/force_aura.md create mode 100644 docs/force_bigobject.md create mode 100644 docs/force_bigobject_create.md create mode 100644 docs/force_bigobject_list.md create mode 100644 docs/force_bulk.md create mode 100644 docs/force_bulk_batch.md create mode 100644 docs/force_bulk_batches.md create mode 100644 docs/force_bulk_delete.md create mode 100644 docs/force_bulk_hardDelete.md create mode 100644 docs/force_bulk_insert.md create mode 100644 docs/force_bulk_job.md create mode 100644 docs/force_bulk_query.md create mode 100644 docs/force_bulk_retrieve.md create mode 100644 docs/force_bulk_update.md create mode 100644 docs/force_bulk_upsert.md create mode 100644 docs/force_create.md create mode 100644 docs/force_create_apexclass.md create mode 100644 docs/force_create_apexcomponent.md create mode 100644 docs/force_create_apexpage.md create mode 100644 docs/force_create_apextrigger.md create mode 100644 docs/force_datapipe.md create mode 100644 docs/force_datapipe_create.md create mode 100644 docs/force_datapipe_createjob.md create mode 100644 docs/force_datapipe_delete.md create mode 100644 docs/force_datapipe_list.md create mode 100644 docs/force_datapipe_listjobs.md create mode 100644 docs/force_datapipe_queryjob.md create mode 100644 docs/force_datapipe_update.md create mode 100644 docs/force_describe.md create mode 100644 docs/force_describe_metadata.md create mode 100644 docs/force_describe_sobject.md create mode 100644 docs/force_eventlogfile.md create mode 100644 docs/force_export.md create mode 100644 docs/force_fetch.md create mode 100644 docs/force_field.md create mode 100644 docs/force_field_create.md create mode 100644 docs/force_field_delete.md create mode 100644 docs/force_field_list.md create mode 100644 docs/force_field_type.md create mode 100644 docs/force_import.md create mode 100644 docs/force_limits.md create mode 100644 docs/force_log.md create mode 100644 docs/force_log_delete.md create mode 100644 docs/force_login.md create mode 100644 docs/force_logins.md create mode 100644 docs/force_logout.md create mode 100644 docs/force_notify.md create mode 100644 docs/force_oauth.md create mode 100644 docs/force_oauth_create.md create mode 100644 docs/force_open.md create mode 100644 docs/force_package.md create mode 100644 docs/force_package_install.md create mode 100644 docs/force_password.md create mode 100644 docs/force_password_change.md create mode 100644 docs/force_password_reset.md create mode 100644 docs/force_password_status.md create mode 100644 docs/force_push.md create mode 100644 docs/force_pushAura.md create mode 100644 docs/force_query.md create mode 100644 docs/force_quickdeploy.md create mode 100644 docs/force_record.md create mode 100644 docs/force_record_create.md create mode 100644 docs/force_record_delete.md create mode 100644 docs/force_record_get.md create mode 100644 docs/force_record_merge.md create mode 100644 docs/force_record_undelete.md create mode 100644 docs/force_record_update.md create mode 100644 docs/force_rest.md create mode 100644 docs/force_rest_get.md create mode 100644 docs/force_rest_patch.md create mode 100644 docs/force_rest_post.md create mode 100644 docs/force_rest_put.md create mode 100644 docs/force_security.md create mode 100644 docs/force_sobject.md create mode 100644 docs/force_sobject_Delete.md create mode 100644 docs/force_sobject_create.md create mode 100644 docs/force_sobject_import.md create mode 100644 docs/force_sobject_list.md create mode 100644 docs/force_test.md create mode 100644 docs/force_trace.md create mode 100644 docs/force_trace_delete.md create mode 100644 docs/force_trace_list.md create mode 100644 docs/force_trace_start.md create mode 100644 docs/force_usedxauth.md create mode 100644 docs/force_version.md create mode 100644 docs/force_whoami.md create mode 100644 docs/mkdocs.go diff --git a/Makefile b/Makefile index b552caef..5ad6da10 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ $(basename $(WINDOWS)).zip: $(WINDOWS) zip $@ $< 7za rn $@ $< $(EXECUTABLE) +docs: + go run docs/mkdocs.go + dist: test $(addsuffix .zip,$(basename $(ALL))) fmt: diff --git a/README.md b/README.md index adfeabeb..200b1f1b 100644 --- a/README.md +++ b/README.md @@ -15,38 +15,62 @@ Can be downloaded from the [Current Release Page](https://github.com/ForceCLI/fo ### Usage - Usage: force [] - - Available commands: - login Log in to force.com - logout Log out from force.com - logins List force.com logins used - active Show or set the active force.com account - whoami Show information about the active account - describe Describe the object or list of available objects - sobject Manage standard & custom objects - field Manage sobject fields - record Create, modify, or view records - bulk Load csv file use Bulk API - fetch Export specified artifact(s) to a local directory - import Import metadata from a local directory - export Export metadata to a local directory - query Execute a SOQL statement - apex Execute anonymous Apex code - log Fetch debug logs - oauth Manage ConnectedApp credentials - test Run apex tests - security Displays the OLS and FLS for a given SObject - version Display current version - apiversion Set the Salesforce API version metadata is pulled with - push Deploy single artifact from a local directory - aura Retrieve or deploy Aura components - password See password status or reset password - notify Should notifications be used - limits Display current limits - help Show this help - - Run 'force help [command]' for details. +See [docs/force.md](docs/force.md) for all supported commands. + +Tab completion simplifies use of the force CLI. +Enable bash completion or see `force completion --help` for other options. + +``` +$ source <(force completion bash) +``` + + Usage: + force [command] + + Available Commands: + active Show or set the active force.com account + apex Execute anonymous Apex code + apiversion Display/Set current API version + bigobject Manage big objects + bulk Load csv file or query data using Bulk API + completion Generate the autocompletion script for the specified shell + create Creates a new, empty Apex Class, Trigger, Visualforce page, or Component. + datapipe Manage DataPipes + describe Describe the object or list of available objects + eventlogfile List and fetch event log file + export Export metadata to a local directory + fetch Export specified artifact(s) to a local directory + field Manage SObject fields + help Help about any command + import Import metadata from a local directory + limits Display current limits + log Fetch debug logs + login force login [-i=] [<-u=username> <-p=password>] [-scratch] [-s] + logins List force.com logins used + logout Log out from Force.com + notify Should notifications be used + oauth Manage ConnectedApp credentials + open Open a browser window, logged into an authenticated Salesforce org + package Manage installed packages + password See password status or reset password + push Deploy metadata from a local directory + query Execute a SOQL statement + quickdeploy Quick deploy validation id + record Create, modify, or view records + rest Execute a REST request + security Displays the OLS and FLS for a given SObject + sobject Manage standard & custom objects + test Run apex tests + trace Manage trace flags + usedxauth Authenticate with SFDX Scratch Org User + version Display current version + whoami Show information about the active account + + Flags: + -a, --account username account username to use + -h, --help help for force + + Use "force [command] --help" for more information about a command. ### login When you login using the CLI a record of the login is saved. Eventually your token will expire requiring re-authentication. The default login is for all production instances of salesforce.com. Two predefined non-production instances are available using the test and pre aliases. You can set an arbitrary instance to log in to by specifying the instance url in the form of subdomain.domain. For example login-blitz.soma.salesforce.com. diff --git a/command/active.go b/command/active.go index 494afd8e..06c69ff6 100644 --- a/command/active.go +++ b/command/active.go @@ -8,81 +8,72 @@ import ( . "github.com/ForceCLI/force/config" . "github.com/ForceCLI/force/error" - . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdActive = &Command{ - Usage: "active [account]", - Short: "Show or set the active force.com account", - Long: ` -Set the active force.com account - -Examples: +func init() { + activeCmd.Flags().BoolP("json", "j", false, "output in JSON format") + activeCmd.Flags().BoolP("local", "l", false, "set active account locally, for current directory") + activeCmd.Flags().BoolP("session", "s", false, "output session id") + RootCmd.AddCommand(activeCmd) +} +var activeCmd = &cobra.Command{ + Use: "active [account]", + Short: "Show or set the active force.com account", + Long: "Get or set the active force.com account", + Example: ` force active force active user@example.org - -Options: - -json, -j Output in JSON format - -local, -l Set active account locally, for current directory - -session, -s Output session id -`, - MaxExpectedArgs: 1, + `, + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + printCurrentAccount(cmd) + return + } + local, _ := cmd.Flags().GetBool("local") + setAccount(args[0], local) + }, } -var ( - tojson bool - account string - local bool - sessionId bool -) -func init() { - cmdActive.Flag.BoolVar(&tojson, "j", false, "output to json") - cmdActive.Flag.BoolVar(&tojson, "json", false, "output to json") - cmdActive.Flag.StringVar(&account, "a", "", "deprecated: set active account") - cmdActive.Flag.StringVar(&account, "account", "", "deprecated: set active account") - cmdActive.Flag.BoolVar(&local, "l", false, "set active account for current directory") - cmdActive.Flag.BoolVar(&local, "local", false, "set active account for current directory") - cmdActive.Flag.BoolVar(&sessionId, "session", false, "display session id") - cmdActive.Flag.BoolVar(&sessionId, "s", false, "display session id") - cmdActive.Run = runActive +func printCurrentAccount(cmd *cobra.Command) { + if force == nil { + ErrorAndExit("No active session") + } + creds := force.Credentials + if creds == nil || creds.UserInfo == nil { + ErrorAndExit("No active session") + } + sessionId, _ := cmd.Flags().GetBool("session") + tojson, _ := cmd.Flags().GetBool("json") + if sessionId { + fmt.Println(creds.AccessToken) + } else if tojson { + fmt.Printf(fmt.Sprintf("{ \"login\": \"%s\", \"instanceUrl\": \"%s\", \"namespace\":\"%s\" }", creds.SessionName(), creds.InstanceUrl, creds.UserInfo.OrgNamespace)) + } else { + fmt.Println(fmt.Sprintf("%s - %s - ns:%s", creds.SessionName(), creds.InstanceUrl, creds.UserInfo.OrgNamespace)) + } } -func runActive(cmd *Command, args []string) { - if account == "" && len(args) == 0 { - creds, err := ActiveCredentials(true) - if err != nil { - ErrorAndExit(err.Error()) - } - if sessionId { - fmt.Println(creds.AccessToken) - } else if tojson { - fmt.Printf(fmt.Sprintf("{ \"login\": \"%s\", \"instanceUrl\": \"%s\", \"namespace\":\"%s\" }", creds.SessionName(), creds.InstanceUrl, creds.UserInfo.OrgNamespace)) +func setAccount(account string, local bool) { + accounts, _ := Config.List("accounts") + i := sort.SearchStrings(accounts, account) + if i < len(accounts) && accounts[i] == account { + if runtime.GOOS == "windows" { + cmd := exec.Command("title", account) + cmd.Run() } else { - fmt.Println(fmt.Sprintf("%s - %s - ns:%s", creds.SessionName(), creds.InstanceUrl, creds.UserInfo.OrgNamespace)) - } - } else { - if account == "" { - account = args[0] + title := fmt.Sprintf("\033];%s\007", account) + fmt.Printf(title) } - accounts, _ := Config.List("accounts") - i := sort.SearchStrings(accounts, account) - if i < len(accounts) && accounts[i] == account { - if runtime.GOOS == "windows" { - cmd := exec.Command("title", account) - cmd.Run() - } else { - title := fmt.Sprintf("\033];%s\007", account) - fmt.Printf(title) - } - fmt.Printf("%s now active\n", account) - if local { - Config.SaveLocal("current", "account", account) - } else { - Config.SaveGlobal("current", "account", account) - } + fmt.Printf("%s now active\n", account) + if local { + Config.SaveLocal("current", "account", account) } else { - ErrorAndExit(fmt.Sprintf("no such account %s\n", account)) + Config.SaveGlobal("current", "account", account) } + } else { + ErrorAndExit(fmt.Sprintf("no such account %s\n", account)) } } diff --git a/command/apex.go b/command/apex.go index 2ab31b3a..4b3b0eea 100644 --- a/command/apex.go +++ b/command/apex.go @@ -6,72 +6,82 @@ import ( "os" . "github.com/ForceCLI/force/error" - . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdApex = &Command{ - Run: runApex, - Usage: "apex [file]", - Short: "Execute anonymous Apex code", - Long: ` -Execute anonymous Apex code - -Apex Options - -test Run in test context - -Examples: +func init() { + apexCmd.Flags().BoolP("test", "t", false, "run in test context") + RootCmd.AddCommand(apexCmd) +} +var apexCmd = &cobra.Command{ + Use: "apex [file]", + Short: "Execute anonymous Apex code", + Example: ` force apex ~/test.apex force apex >> Start typing Apex code; press CTRL-D(for Mac/Linux) / Ctrl-Z (for Windows) when finished - -`, - MaxExpectedArgs: 2, -} - -func init() { - cmdApex.Flag.BoolVar(&testContext, "test", false, "run apex from in a test context") + `, + Run: func(cmd *cobra.Command, args []string) { + testContext, _ := cmd.Flags().GetBool("test") + switch len(args) { + case 1: + runApexInFile(args[0], testContext) + case 0: + runApexFromStdin(testContext) + default: + fmt.Println("Got test indication. DEPRECATED.") + getTestCoverage(args[1]) + } + }, } -var ( - testContext bool -) - -func runApex(cmd *Command, args []string) { - var code []byte - var err error - if len(args) == 1 { - code, err = ioutil.ReadFile(args[0]) - } else if len(args) > 1 { - fmt.Println("Got test indication.") +func runApexFromStdin(testContext bool) { + fmt.Println(">> Start typing Apex code; press CTRL-D(for Mac/Linux) / Ctrl-Z (for Windows) when finished") + code, err := ioutil.ReadAll(os.Stdin) + fmt.Println("\n\n>> Executing code...") + var output string + if testContext { + output, err = executeAsTest(code) } else { - fmt.Println(">> Start typing Apex code; press CTRL-D(for Mac/Linux) / Ctrl-Z (for Windows) when finished") - code, err = ioutil.ReadAll(os.Stdin) - fmt.Println("\n\n>> Executing code...") + output, err = execute(code) + } + if err != nil { + ErrorAndExit(err.Error()) } + fmt.Println(output) +} + +func runApexInFile(filename string, testContext bool) { + code, err := ioutil.ReadFile(filename) if err != nil { ErrorAndExit(err.Error()) } - force, _ := ActiveForce() + var output string if testContext { - output, err := force.Partner.ExecuteAnonymousTest(string(code)) - if err != nil { - ErrorAndExit(err.Error()) - } - fmt.Println(output) - } else if len(args) <= 1 { - output, err := force.Partner.ExecuteAnonymous(string(code)) - if err != nil { - ErrorAndExit(err.Error()) - } - fmt.Println(output) + output, err = executeAsTest(code) } else { - apexclass := args[1] - fmt.Println(apexclass) - err := force.GetCodeCoverage("", apexclass) - if err != nil { - ErrorAndExit(err.Error()) - } + output, err = execute(code) + } + if err != nil { + ErrorAndExit(err.Error()) + } + fmt.Println(output) +} + +func executeAsTest(code []byte) (string, error) { + return force.Partner.ExecuteAnonymousTest(string(code)) +} + +func execute(code []byte) (string, error) { + return force.Partner.ExecuteAnonymous(string(code)) +} + +func getTestCoverage(apexclass string) { + fmt.Println(apexclass) + err := force.GetCodeCoverage("", apexclass) + if err != nil { + ErrorAndExit(err.Error()) } } diff --git a/command/apiversion.go b/command/apiversion.go index 2a4d3075..3c182548 100644 --- a/command/apiversion.go +++ b/command/apiversion.go @@ -6,47 +6,41 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdApiVersion = &Command{ - Run: runApiVersion, - Usage: "apiversion", +var apiVersionCmd = &cobra.Command{ + Use: "apiversion", Short: "Display/Set current API version", - Long: ` -Display/Set current API version - -Examples: - + Example: ` force apiversion force apiversion 40.0 `, - MaxExpectedArgs: 1, + Args: cobra.MaximumNArgs(1), + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 1 { + setApiVersion(args[0]) + } else { + fmt.Println(ApiVersion()) + } + }, } func init() { + RootCmd.AddCommand(apiVersionCmd) } -func runApiVersion(cmd *Command, args []string) { - if len(args) == 1 { - apiVersionNumber := args[0] - matched, err := regexp.MatchString("^\\d{2}\\.0$", apiVersionNumber) - if err != nil { - ErrorAndExit("%v", err) - } - if !matched { - ErrorAndExit("apiversion must be in the form of nn.0.") - } - force, err := ActiveForce() - if err != nil { - ErrorAndExit(err.Error()) - } - err = force.UpdateApiVersion(apiVersionNumber) - if err != nil { - ErrorAndExit("%v", err) - } - } else if len(args) == 0 { - fmt.Println(ApiVersion()) - } else { - ErrorAndExit("The apiversion command only accepts a single argument in the form of nn.0") +func setApiVersion(apiVersionNumber string) { + matched, err := regexp.MatchString("^\\d{2}\\.0$", apiVersionNumber) + if err != nil { + ErrorAndExit("%v", err) + } + if !matched { + ErrorAndExit("apiversion must be in the form of nn.0.") + } + err = force.UpdateApiVersion(apiVersionNumber) + if err != nil { + ErrorAndExit("%v", err) } } diff --git a/command/attrs.go b/command/attrs.go deleted file mode 100644 index d2a0f775..00000000 --- a/command/attrs.go +++ /dev/null @@ -1,23 +0,0 @@ -package command - -import ( - "net/url" - "strings" -) - -func ParseArgumentAttrs(pairs []string) (parsed map[string]string) { - parsed = make(map[string]string) - for _, pair := range pairs { - split := strings.SplitN(pair, ":", 2) - parsed[split[0]] = split[1] - } - return -} - -func PairsToUrlValues(pairs map[string]string) (values url.Values) { - values = url.Values{} - for key, value := range pairs { - values.Set(key, value) - } - return -} diff --git a/command/aura.go b/command/aura.go deleted file mode 100644 index db9ba8a7..00000000 --- a/command/aura.go +++ /dev/null @@ -1,212 +0,0 @@ -package command - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - . "github.com/ForceCLI/force/error" - . "github.com/ForceCLI/force/lib" -) - -// Brief comment to fire commit - -var cmdAura = &Command{ - Usage: "aura", - Short: "force aura push -f ", - Long: ` - The aura command needs context to work. If you execute "aura get" - it will create a folder structure that provides the context for - aura components on disk. - - The aura components will be created in "metadata/aurabundles/" - relative to the current working directory and a .manifest file will be - created that associates components and their artifacts with their ids in - the database. - - To create a new component (application, evt or component), create a new - folder under "aura". Then create a new file in your new folder. You - must follow a naming convention for your files to enable proper definition - of the component type. - - Naming convention . - Examples: metadata - aura - MyApp - MyAppApplication.app - MyAppStyle.css - MyList - MyComponent.cmp - MyComponentHelper.js - MyComponentStyle.css - - force aura push -f - - force aura create -t= - - force aura delete -f= - - force aura list - - `, - MaxExpectedArgs: -1, -} - -func init() { - cmdAura.Run = runAura - cmdAura.Flag.Var(&resourcepaths, "p", "fully qualified file name for entity") - cmdAura.Flag.Var(&resourcepaths, "f", "fully qualified file name for entity") - cmdAura.Flag.StringVar(&metadataType, "entitytype", "", "fully qualified file name for entity") - cmdAura.Flag.StringVar(&auraentityname, "entityname", "", "fully qualified file name for entity") - cmdAura.Flag.StringVar(&metadataType, "t", "", "fully qualified file name for entity") - cmdAura.Flag.StringVar(&auraentityname, "n", "", "fully qualified file name for entity") -} - -var ( - auraentityname string - metadataType string -) - -func runAura(cmd *Command, args []string) { - if err := cmd.Flag.Parse(args[0:]); err != nil { - os.Exit(2) - } - - force, _ := ActiveForce() - - subcommand := args[0] - // Sublime hack - the way sublime passes parameters seems to - // break the flag parsing by sending a single element array - // for the args. ARGH!!! - if strings.HasPrefix(subcommand, "delete ") || strings.HasPrefix(subcommand, "push ") { - what := strings.Split(subcommand, " ") - if err := cmd.Flag.Parse(what[1:]); err != nil { - ErrorAndExit(err.Error()) - } - subcommand = what[0] - } else { - if err := cmd.Flag.Parse(args[1:]); err != nil { - ErrorAndExit(err.Error()) - } - } - - switch strings.ToLower(subcommand) { - case "create": - /*if *auraentitytype == "" || *auraentityname == "" { - fmt.Println("Must specify entity type and name") - os.Exit(2) - }*/ - ErrorAndExit("force aura create not yet implemented") - - case "delete": - runDeleteAura() - case "list": - bundles, err := force.GetAuraBundlesList() - if err != nil { - ErrorAndExit(err.Error()) - } - for _, bundle := range bundles.Records { - fmt.Println(bundle["DeveloperName"]) - } - case "push": - // absPath, _ := filepath.Abs(resourcepaths[0]) - runPushAura(cmd, resourcepaths) - } -} - -func runDeleteAura() { - absPath, _ := filepath.Abs(resourcepaths[0]) - //resourcepaths = absPath - - if InAuraBundlesFolder(absPath) { - info, err := os.Stat(absPath) - if err != nil { - ErrorAndExit(err.Error()) - } - manifest, err := GetManifest(absPath) - isBundle := false - if info.IsDir() { - force, _ := ActiveForce() - manifest, err = GetManifest(filepath.Join(absPath, ".manifest")) - bid := "" - if err != nil { // Could not find a manifest, use bundle name - // Try to look up the bundle by name - b, err := force.GetAuraBundleByName(filepath.Base(absPath)) - if err != nil { - ErrorAndExit(err.Error()) - } else { - if len(b.Records) == 0 { - ErrorAndExit(fmt.Sprintf("No bundle definition named %q", filepath.Base(absPath))) - } else { - bid = b.Records[0]["Id"].(string) - } - } - } else { - bid = manifest.Id - } - - err = force.DeleteToolingRecord("AuraDefinitionBundle", bid) - if err != nil { - ErrorAndExit(err.Error()) - } - // Now walk the bundle and remove all the atrifacts - filepath.Walk(absPath, func(path string, inf os.FileInfo, err error) error { - os.Remove(path) - return nil - }) - os.Remove(absPath) - fmt.Println("Bundle ", filepath.Base(absPath), " deleted.") - return - } - - for key := range manifest.Files { - mfile := manifest.Files[key].FileName - cfile := absPath - if !filepath.IsAbs(mfile) { - cfile = filepath.Base(cfile) - } - if isBundle { - if !filepath.IsAbs(mfile) { - cfile = filepath.Join(absPath, mfile) - } else { - cfile = mfile - deleteAuraDefinition(manifest, key) - } - } else { - if mfile == cfile { - deleteAuraDefinition(manifest, key) - return - } - } - } - if isBundle { - // Need to remove the bundle using the id in the manifest - deleteAuraDefinitionBundle(manifest) - } - } -} -func deleteAuraDefinitionBundle(manifest BundleManifest) { - force, err := ActiveForce() - err = force.DeleteToolingRecord("AuraDefinitionBundle", manifest.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - os.Remove(filepath.Join(resourcepaths[0], ".manifest")) - os.Remove(resourcepaths[0]) -} - -func deleteAuraDefinition(manifest BundleManifest, key int) { - force, err := ActiveForce() - err = force.DeleteToolingRecord("AuraDefinition", manifest.Files[key].ComponentId) - if err != nil { - ErrorAndExit(err.Error()) - } - fname := manifest.Files[key].FileName - os.Remove(fname) - manifest.Files = append(manifest.Files[:key], manifest.Files[key+1:]...) - bmBody, _ := json.Marshal(manifest) - ioutil.WriteFile(filepath.Join(filepath.Dir(fname), ".manifest"), bmBody, 0644) -} diff --git a/command/bigobject.go b/command/bigobject.go index 2676ecc5..5e79b009 100644 --- a/command/bigobject.go +++ b/command/bigobject.go @@ -2,18 +2,60 @@ package command import ( "fmt" - "os" "strconv" "strings" "github.com/ForceCLI/inflect" + "github.com/spf13/cobra" . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" ) -var cmdBigObject = &Command{ - Usage: "bigobject", +func init() { + bigObjectCreateCmd.Flags().StringSliceP("field", "f", []string{}, "field definition") + bigObjectCreateCmd.Flags().StringP("label", "l", "", "big object label") + bigObjectCreateCmd.Flags().StringP("plural", "p", "", "big object plural label") + bigObjectCreateCmd.MarkFlagRequired("label") + + bigObjectCmd.AddCommand(bigObjectListCmd) + bigObjectCmd.AddCommand(bigObjectCreateCmd) + RootCmd.AddCommand(bigObjectCmd) +} + +var bigObjectListCmd = &cobra.Command{ + Use: "list [object]", + Short: "List big objects", + Args: cobra.MaximumNArgs(1), + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + object := "" + if len(args) > 0 { + object = args[0] + } + getBigObjectList(object) + }, +} + +var bigObjectCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create big object", + Args: cobra.MaximumNArgs(0), + Example: ` + force bigobject create -n=MyObject -l="My Object" -p="My Objects" \ + -f=name:Field1+label:"Field 1"+type:Text+length:120 \ + -f=name:MyDate+type=dateTime +`, + Run: func(cmd *cobra.Command, args []string) { + fields, _ := cmd.Flags().GetStringSlice("field") + label, _ := cmd.Flags().GetString("label") + plural, _ := cmd.Flags().GetString("plural") + runBigObjectCreate(fields, label, plural) + }, +} + +var bigObjectCmd = &cobra.Command{ + Use: "bigobject", Short: "Manage big objects", Long: ` Manage big objects @@ -29,10 +71,9 @@ Usage: Type = text: name, label, length Type = datetime: name, label Type = lookup: name, label, referenceTo, relationshipName +`, - -Examples: - + Example: ` force bigobject list force bigobject create -n=MyObject -l="My Object" -p="My Objects" \ @@ -40,63 +81,7 @@ Examples: -f=name:MyDate+type=dateTime `, - MaxExpectedArgs: -1, -} - -type boField []string - -func (i *boField) String() string { - return fmt.Sprint(*i) -} - -func (i *boField) Set(value string) error { - // That would permit usages such as - // -deltaT 10s -deltaT 15s - for _, name := range strings.Split(value, ",") { - *i = append(*i, name) - } - return nil -} - -var ( - fields boField - deploymentStatus string - objectLabel string - pluralLabel string -) - -func init() { - cmdBigObject.Flag.Var(&fields, "field", "names of metadata") - cmdBigObject.Flag.Var(&fields, "f", "names of metadata") - cmdBigObject.Flag.StringVar(&deploymentStatus, "deployment", "Deployed", "deployment status") - cmdBigObject.Flag.StringVar(&deploymentStatus, "d", "Deployed", "deployment status") - cmdBigObject.Flag.StringVar(&objectLabel, "label", "", "big object label") - cmdBigObject.Flag.StringVar(&objectLabel, "l", "", "big object label") - cmdBigObject.Flag.StringVar(&pluralLabel, "plural", "", "big object plural label") - cmdBigObject.Flag.StringVar(&pluralLabel, "p", "", "big object plural label") - cmdBigObject.Run = runBigObject -} - -func runBigObject(cmd *Command, args []string) { - if len(args) == 0 { - cmd.PrintUsage() - } else { - if err := cmd.Flag.Parse(args[1:]); err != nil { - os.Exit(2) - } - switch args[0] { - case "list": - if len(args) <= 2 { - getBigObjectList(args[1:]) - } else { - cmd.InvalidInvocation(args) - } - case "create": - runBigObjectCreate(args[1:]) - default: - ErrorAndExit("no such command: %s", args[0]) - } - } + Args: cobra.MaximumNArgs(0), } func parseField(fielddata string) (result BigObjectField) { @@ -159,16 +144,15 @@ func validateField(originField BigObjectField) (field BigObjectField) { return } -func getBigObjectList(args []string) (l []ForceSobject) { - force, _ := ActiveForce() +func getBigObjectList(object string) (l []ForceSobject) { sobjects, err := force.ListSobjects() if err != nil { ErrorAndExit(fmt.Sprintf("ERROR: %s\n", err)) } for _, sobject := range sobjects { - if len(args) == 1 { - if strings.Contains(sobject["name"].(string), args[0]) { + if len(object) > 0 { + if strings.Contains(strings.ToLower(sobject["name"].(string)), strings.ToLower(object)) { l = append(l, sobject) } } else { @@ -178,28 +162,23 @@ func getBigObjectList(args []string) (l []ForceSobject) { return } -func runBigObjectCreate(args []string) { +func runBigObjectCreate(fields []string, label, plural string) { var fieldObjects = make([]BigObjectField, len(fields)) for i, field := range fields { fieldObjects[i] = parseField(field) } var object = BigObject{ - DeploymentStatus: deploymentStatus, - Label: objectLabel, - PluralLabel: pluralLabel, + DeploymentStatus: "Deployed", + Label: label, + PluralLabel: plural, Fields: fieldObjects, } - if len(object.Label) == 0 { - ErrorAndExit("Please provide a label for your big object using the -l flag.") - } if len(object.PluralLabel) == 0 { object.PluralLabel = inflect.Pluralize(object.Label) } - force, _ := ActiveForce() if err := force.Metadata.CreateBigObject(object); err != nil { ErrorAndExit(err.Error()) } fmt.Println("Big object created") - } diff --git a/command/bulk.go b/command/bulk.go index 8026af5e..35832071 100644 --- a/command/bulk.go +++ b/command/bulk.go @@ -1,7 +1,6 @@ package command /* - bulk command force bulk insert mydata.csv @@ -44,11 +43,6 @@ force bulk batches bulk command force bulk batch - - - - - */ import ( @@ -64,166 +58,177 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdBulk = &Command{ - // Run: runBulk, - Usage: "bulk -[command, c]=[insert, update, ...] -flags args", - Short: "Load csv file use Bulk API", - Long: ` -Load csv file use Bulk API - -Commands: - insert upload a .csv file to insert records - update upload a .csv file to update records - upsert upload a .csv file to upsert records - delete upload a .csv file to delete records - hardDelete upload a .csv file to delete records permanently - query run a SOQL statement to generate a .csv file on the server - retrieve retrieve a query generated .csv file from the server - job get information about a job based on job Id - batch get detailed information about a batch within a job based on job Id and batch Id - batches get a list of batches associated with a job based on job Id - -Examples using flags - more flexible, flags can be in any order with arguments after all flags. - - force bulk -c=insert -[concurrencyMode, m]=Serial -[objectType, o]=Account mydata.csv - force bulk -c=update -[concurrencyMode, m]=Parallel -[objectType, o]=Account mydata.csv - force bulk -c=delete -[concurrencyMode, m]=Parallel -[objectType, o]=Account mydata.csv - force bulk -c=query -[objectType, o]=Account "SOQL" - force bulk -c=job -[jobId, j]=jobid - force bulk -c=batches -[jobId, j]=jobid - force bulk -c=batch -[jobId, j]=jobid -[batchId, b]=batchid - force bulk -c=retrieve -[jobId, j]=jobid -[batchId, b]=batchid - force bulk -c=retrieve -j=jobid -b=batchid > mydata.csv - force bulk -c=upsert -[concurrencyMode, m]=Serial -[objectType, o]=Account -[externalId, e]=ExternalIdField__c mydata.csv - -Examples using positional arguments - less flexible, arguments must be in the correct order. - - force bulk insert Account [csv file] [] - force bulk update Account [csv file] [] - force bulk delete Account [csv file] [] - force bulk upsert ExternalIdField__c Account [csv file] [] +func init() { + cmds := []*cobra.Command{bulkInsertCmd, bulkUpdateCmd, bulkUpsertCmd, bulkDeleteCmd, bulkHardDeleteCmd, bulkQueryCmd} + for _, cmd := range cmds { + cmd.Flags().StringP("format", "f", "CSV", "file `format`") + cmd.Flags().StringP("concurrencymode", "m", "Parallel", "Concurrency `mode`. Valid options are Serial and Parallel.") + cmd.Flags().BoolP("wait", "w", false, "Wait for job to complete") + } + + bulkUpsertCmd.Flags().StringP("externalid", "e", "", "The external Id field for upserting data") + bulkUpsertCmd.MarkFlagRequired("externalid") + + bulkQueryCmd.Flags().IntP("chunk", "p", 0, "PK chunking size (number of `records`)") + bulkQueryCmd.Flags().String("parent", "", "Parent `object` to use for PK chunking") + + // Start Bulk API Job + bulkCmd.AddCommand(bulkInsertCmd) + bulkCmd.AddCommand(bulkUpdateCmd) + bulkCmd.AddCommand(bulkUpsertCmd) + bulkCmd.AddCommand(bulkDeleteCmd) + bulkCmd.AddCommand(bulkHardDeleteCmd) + bulkCmd.AddCommand(bulkQueryCmd) + + // Get Bulk Job Status + bulkCmd.AddCommand(bulkRetrieveCmd) + bulkCmd.AddCommand(bulkJobCmd) + bulkCmd.AddCommand(bulkBatchCmd) + bulkCmd.AddCommand(bulkBatchesCmd) + + RootCmd.AddCommand(bulkCmd) +} + +var bulkInsertCmd = &cobra.Command{ + Use: "insert ", + Short: "Create records from csv file using Bulk API", + Run: runBulkCmd, + Args: cobra.ExactArgs(2), +} + +var bulkUpdateCmd = &cobra.Command{ + Use: "update ", + Short: "Update records from csv file using Bulk API", + Run: runBulkCmd, + Args: cobra.ExactArgs(2), +} + +var bulkUpsertCmd = &cobra.Command{ + Use: "upsert ", + Short: "Upsert records from csv file using Bulk API", + Run: runBulkCmd, + Args: cobra.ExactArgs(3), +} + +var bulkDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete records using Bulk API", + Run: runBulkCmd, + Args: cobra.ExactArgs(2), +} + +var bulkHardDeleteCmd = &cobra.Command{ + Use: "hardDelete ", + Short: "Hard delete records using Bulk API", + Run: runBulkCmd, + Args: cobra.ExactArgs(2), +} + +var bulkQueryCmd = &cobra.Command{ + Use: "query ", + Short: "Query records using Bulk API", + Run: func(cmd *cobra.Command, args []string) { + objectType := args[0] + query := args[1] + format, _ := cmd.Flags().GetString("format") + concurrencyMode, _ := cmd.Flags().GetString("concurrencymode") + pkChunkSize, _ := cmd.Flags().GetInt("chunk") + pkChunkParent, _ := cmd.Flags().GetString("parent") + jobInfo, batchId := startBulkQuery(objectType, query, format, concurrencyMode, pkChunkSize, pkChunkParent) + wait, _ := cmd.Flags().GetBool("wait") + if !wait { + fmt.Println("Query Submitted") + if pkChunkSize == 0 { + fmt.Printf("To retrieve batch status use\nforce bulk batch %s %s\n\n", jobInfo.Id, batchId) + fmt.Printf("To retrieve query data use\nforce bulk retrieve %s %s\n\n", jobInfo.Id, batchId) + } else { + fmt.Printf("To retrieve batch status use\nforce bulk batches %s\n\n", jobInfo.Id) + } + return + } + waitForJob(jobInfo) + displayQueryResults(jobInfo) + }, + Args: cobra.ExactArgs(2), +} + +var bulkRetrieveCmd = &cobra.Command{ + Use: "retrieve ", + Short: "Retrieve query results using Bulk API", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(string(getBulkQueryResults(args[0], args[1]))) + }, + Args: cobra.ExactArgs(2), +} + +var bulkJobCmd = &cobra.Command{ + Use: "job ", + Short: "Show bulk job details", + Run: func(cmd *cobra.Command, args []string) { + showJobDetails(args[0]) + }, + Args: cobra.ExactArgs(1), +} + +var bulkBatchCmd = &cobra.Command{ + Use: "batch ", + Short: "Show bulk job batch details", + Run: func(cmd *cobra.Command, args []string) { + DisplayBatchInfo(getBatchDetails(args[0], args[1]), os.Stdout) + }, + Args: cobra.ExactArgs(2), +} + +var bulkBatchesCmd = &cobra.Command{ + Use: "batches ", + Short: "List bulk job batches", + Run: func(cmd *cobra.Command, args []string) { + listBatches(args[0]) + }, + Args: cobra.ExactArgs(1), +} + +var bulkCmd = &cobra.Command{ + Use: "bulk", + Short: "Load csv file or query data using Bulk API", + Example: ` + force bulk insert Account [csv file] + force bulk update Account [csv file] + force bulk delete Account [csv file] + force bulk upsert ExternalIdField__c Account [csv file] force bulk job [job id] force bulk batches [job id] force Bulk batch [job id] [batch id] - force bulk batch retrieve [job id] [batch id] - force bulk [-wait | -w] query Account [SOQL] - force bulk [-chunk | -p]=50000 query Account [SOQL] - force bulk query retrieve [job id] [batch id] - + force bulk query [-wait | -w] Account [SOQL] + force bulk query [-chunk | -p]=50000 Account [SOQL] + force bulk retrieve [job id] [batch id] `, - MaxExpectedArgs: -1, -} - -var ( - command string - objectType string - jobId string - batchId string - fileFormat string - externalId string - concurrencyMode string - pkChunkSize int - pkChunkParent string - waitForCompletion bool -) -var commandVersion = "old" - -func init() { - cmdBulk.Flag.StringVar(&command, "command", "", "Sub command for bulk api. Can be insert, update, delete, job, batches, batch, retrieve or query.") - cmdBulk.Flag.StringVar(&command, "c", "", "Sub command for bulk api. Can be insert, update, delete, job, batches, batch, retrieve or query.") - cmdBulk.Flag.StringVar(&objectType, "objectType", "", "Type of sObject for CRUD commands.") - cmdBulk.Flag.StringVar(&objectType, "o", "", "Type of sObject for CRUD commands.") - cmdBulk.Flag.StringVar(&jobId, "jobId", "", "A batch job id.") - cmdBulk.Flag.StringVar(&jobId, "j", "", "A batch job id.") - cmdBulk.Flag.StringVar(&batchId, "batchId", "", "A batch id.") - cmdBulk.Flag.StringVar(&batchId, "b", "", "A batch id.") - cmdBulk.Flag.StringVar(&fileFormat, "format", "CSV", "File format.") - cmdBulk.Flag.StringVar(&fileFormat, "f", "CSV", "File format.") - cmdBulk.Flag.StringVar(&externalId, "externalId", "", "The external Id field for upserts of data.") - cmdBulk.Flag.StringVar(&externalId, "e", "", "The External Id Field for upserts of data.") - cmdBulk.Flag.StringVar(&concurrencyMode, "m", "Parallel", "Concurrency mode for bulk api inserts, updates, deletes and upserts. Valid options are `Serial` and `Parallel` (default).") - cmdBulk.Flag.StringVar(&concurrencyMode, "concurrencyMode", "Parallel", "Concurrency mode for bulk api inserts, updates, deletes and upserts. Valid options are `Serial` and `Parallel` (default).") - cmdBulk.Flag.BoolVar(&waitForCompletion, "wait", false, "Wait for job to complete") - cmdBulk.Flag.BoolVar(&waitForCompletion, "w", false, "Wait for job to complete") - cmdBulk.Flag.IntVar(&pkChunkSize, "chunk", 0, "PK chunk size") - cmdBulk.Flag.IntVar(&pkChunkSize, "p", 0, "PK chunk size") - cmdBulk.Flag.StringVar(&pkChunkParent, "parent", "", "PK chunk parent") - cmdBulk.Run = runBulk } -func runBulk2(cmd *Command, args []string) { - if len(command) == 0 { - cmd.PrintUsage() - return - } - commandVersion = "new" - command = strings.ToLower(command) - switch command { - case "insert", "update", "delete", "harddelete", "upsert", "query": - runDBCommand(args[0]) - case "job", "retrieve", "batch", "batches": - runBulkInfoCommand() - default: - ErrorAndExit("Unknown sub-command: " + command) +func runBulkCmd(cmd *cobra.Command, args []string) { + externalId := "" + if cmd.Name() == "upsert" { + externalId, _ = cmd.Flags().GetString("externalid") + args = args[1:] } -} -func runBulkInfoCommand() { - if len(jobId) == 0 { - ErrorAndExit("For the " + command + " command you need to specify a job id.") - } - switch command { - case "job": - showJobDetails(jobId) - case "batches": - listBatches(jobId) - case "batch", "retrieve", "status": - if len(batchId) == 0 { - ErrorAndExit("For the " + command + " command you need to provide a batch id in addition to a job id.") - } - if command == "retrieve" { - fmt.Println(string(getBulkQueryResults(jobId, batchId))) - } else /* batch or status */ { - DisplayBatchInfo(getBatchDetails(jobId, batchId), os.Stdout) - } - default: - ErrorAndExit("Unknown sub-command " + command + ".") + objectType := args[0] + file := args[1] + format, _ := cmd.Flags().GetString("format") + concurrencyMode, _ := cmd.Flags().GetString("concurrencymode") + wait, _ := cmd.Flags().GetBool("wait") + jobInfo, batchInfo := startBulkJob(cmd.Name(), file, objectType, externalId, format, concurrencyMode) + if !wait { + fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) + return } + waitForJob(jobInfo) } -func runDBCommand(arg string) { - if len(objectType) == 0 { - ErrorAndExit("Database commands need to have an sObject specified.") - } - if len(arg) == 0 { - ErrorAndExit("You need to supply a path to a data file (csv) for insert and update or a SOQL statement for query.") - } - if command == "upsert" && len(externalId) == 0 { - ErrorAndExit("Upsert commands must have ExternalId specified. -[externalId, e]") - } - - var jobInfo JobInfo - - switch command { - case "insert": - jobInfo = createBulkInsertJob(arg, objectType, fileFormat, concurrencyMode) - case "update": - jobInfo = createBulkUpdateJob(arg, objectType, fileFormat, concurrencyMode) - case "delete": - jobInfo = createBulkDeleteJob(arg, objectType, fileFormat, concurrencyMode) - case "harddelete": - jobInfo = createBulkHardDeleteJob(arg, objectType, fileFormat, concurrencyMode) - case "upsert": - jobInfo = createBulkUpsertJob(arg, objectType, fileFormat, externalId, concurrencyMode) - case "query": - jobInfo = doBulkQuery(objectType, arg, fileFormat, concurrencyMode) - } - if !waitForCompletion { - return - } - force, _ := ActiveForce() +func waitForJob(jobInfo JobInfo) { for { status, err := force.GetJobInfo(jobInfo.Id) if err != nil { @@ -236,108 +241,40 @@ func runDBCommand(arg string) { } time.Sleep(2000 * time.Millisecond) } - if command == "query" { - displayQueryResults(jobInfo) - } } -func runBulk(cmd *Command, args []string) { - if len(command) > 0 { - runBulk2(cmd, args) - return - } - if len(args) == 0 { - cmd.PrintUsage() - return - } - - command = strings.ToLower(args[0]) - - switch command { - case "query": - handleQuery(args) - case "insert", "update", "upsert", "delete", "harddelete": - handleDML(args) - case "batch", "batches", "job": - handleInfo(args) - default: - ErrorAndExit("Unknown command - " + command + ".") +func startBulkJob(jobType string, csvFilePath string, objectType string, externalId string, format string, concurrencyMode string) (JobInfo, BatchInfo) { + jobInfo, err := createBulkJob(objectType, jobType, format, externalId, concurrencyMode, nil) + if err != nil { + ErrorAndExit(err.Error()) } -} - -func handleInfo(args []string) { - if len(args) == 4 && args[1] == "retrieve" { - jobId = args[2] - batchId = args[3] - command = "retrieve" - } else if len(args) == 3 && command == "batch" { - jobId = args[1] - batchId = args[2] - } else if len(args) == 2 { - jobId = args[1] - } else { - ErrorAndExit("Problem parsing the command.") + batchInfo, err := addBatchToJob(csvFilePath, jobInfo) + closeBulkJob(jobInfo.Id) + if err != nil { + ErrorAndExit(err.Error()) } - runBulkInfoCommand() + return jobInfo, batchInfo } -func handleDML(args []string) { - var argLength = len(args) - if args[0] == "upsert" { - externalId = args[1] - objectType = args[2] - file := args[3] - if argLength == 5 || argLength == 6 { - setConcurrencyModeOrFileFormat(args[4]) - if argLength == 6 { - setConcurrencyModeOrFileFormat(args[5]) - } - } - runDBCommand(file) - } else { - objectType = args[1] - file := args[2] - if argLength == 4 || argLength == 5 { - setConcurrencyModeOrFileFormat(args[3]) - if argLength == 5 { - setConcurrencyModeOrFileFormat(args[4]) - } - } - runDBCommand(file) +func startBulkQuery(objectType string, soql string, contenttype string, concurrencyMode string, pkChunkSize int, pkChunkParent string) (JobInfo, string) { + headers := make(map[string]string) + var pkChunkOptions []string + if pkChunkSize != 0 { + pkChunkOptions = append(pkChunkOptions, fmt.Sprintf("chunkSize=%d", pkChunkSize)) } -} - -func handleQuery(args []string) { - if len(args) == 3 { - objectType = args[1] - runDBCommand(args[2]) - } else if len(args) == 4 { - jobId = args[2] - batchId = args[3] - command = args[1] - runBulkInfoCommand() - } else { - ErrorAndExit("Bad command, check arguments...") + if pkChunkParent != "" { + pkChunkOptions = append(pkChunkOptions, fmt.Sprintf("parent=%s", pkChunkParent)) } -} - -func setConcurrencyModeOrFileFormat(argument string) { - if strings.EqualFold(argument, "parallel") || strings.EqualFold(argument, "serial") { - concurrencyMode = argument - } else { - fileFormat = argument + if len(pkChunkOptions) > 0 { + headers["Sforce-Enable-PKChunking"] = strings.Join(pkChunkOptions, ";") } -} - -func startBulkQuery(objectType string, soql string, contenttype string, concurrencyMode string) (jobInfo JobInfo, batchId string) { - jobInfo, err := createBulkJob(objectType, "query", contenttype, "", concurrencyMode) + jobInfo, err := createBulkJob(objectType, "query", contenttype, "", concurrencyMode, headers) if err != nil { ErrorAndExit(err.Error()) } - force, _ := ActiveForce() result, err := force.BulkQuery(soql, jobInfo.Id, contenttype) - batchId = result.Id + batchId := result.Id if err != nil { closeBulkJob(jobInfo.Id) ErrorAndExit(err.Error()) @@ -363,22 +300,7 @@ func startBulkQuery(objectType string, soql string, contenttype string, concurre } closeBulkJob(jobInfo.Id) - return -} - -func doBulkQuery(objectType string, soql string, contenttype string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, batchId := startBulkQuery(objectType, soql, contenttype, concurrencyMode) - if !waitForCompletion { - fmt.Println("Query Submitted") - if commandVersion == "new" { - fmt.Printf("To retrieve query status use\nforce bulk -c=batch -j=%s -b=%s\n\n", jobInfo.Id, batchId) - fmt.Printf("To retrieve query data use\nforce bulk -c=retrieve -j=%s -b=%s\n\n", jobInfo.Id, batchId) - } else { - fmt.Printf("To retrieve query status use\nforce bulk query status %s %s\n\n", jobInfo.Id, batchId) - fmt.Printf("To retrieve query data use\nforce bulk query retrieve %s %s\n\n", jobInfo.Id, batchId) - } - } - return + return jobInfo, batchId } func displayQueryResults(jobInfo JobInfo) { @@ -434,8 +356,6 @@ func getBulkQueryResults(jobId string, batchId string) (data []byte) { } func retrieveBulkQuery(jobId string, batchId string) (resultIds []string) { - force, _ := ActiveForce() - jobInfo, err := force.RetrieveBulkQuery(jobId, batchId) if err != nil { ErrorAndExit(err.Error()) @@ -451,8 +371,6 @@ func retrieveBulkQuery(jobId string, batchId string) (resultIds []string) { } func retrieveBulkQueryResults(jobId string, batchId string, resultId string) (data []byte) { - force, _ := ActiveForce() - data, err := force.RetrieveBulkQueryResults(jobId, batchId, resultId) if err != nil { ErrorAndExit(err.Error()) @@ -471,8 +389,6 @@ func listBatches(jobId string) { } func getJobDetails(jobId string) (jobInfo JobInfo) { - force, _ := ActiveForce() - jobInfo, err := force.GetJobInfo(jobId) if err != nil { @@ -482,8 +398,6 @@ func getJobDetails(jobId string) (jobInfo JobInfo) { } func getBatches(jobId string) (batchInfos []BatchInfo) { - force, _ := ActiveForce() - batchInfos, err := force.GetBatches(jobId) if err != nil { @@ -493,8 +407,6 @@ func getBatches(jobId string) (batchInfos []BatchInfo) { } func getBatchDetails(jobId string, batchId string) (batchInfo BatchInfo) { - force, _ := ActiveForce() - batchInfo, err := force.GetBatchInfo(jobId, batchId) if err != nil { @@ -503,109 +415,7 @@ func getBatchDetails(jobId string, batchId string) (batchInfo BatchInfo) { return } -func createBulkInsertJob(csvFilePath string, objectType string, format string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, err := createBulkJob(objectType, "insert", format, "", concurrencyMode) - if err != nil { - ErrorAndExit(err.Error()) - } - batchInfo, err := addBatchToJob(csvFilePath, jobInfo) - closeBulkJob(jobInfo.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - if !waitForCompletion { - if commandVersion == "old" { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } else { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk -c=batch -j=%s -b=%s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } - } - return -} - -func createBulkUpdateJob(csvFilePath string, objectType string, format string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, err := createBulkJob(objectType, "update", format, "", concurrencyMode) - if err != nil { - ErrorAndExit(err.Error()) - } - batchInfo, err := addBatchToJob(csvFilePath, jobInfo) - closeBulkJob(jobInfo.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - if !waitForCompletion { - if commandVersion == "old" { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } else { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk -c=batch -j=%s -b=%s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } - } - return -} - -func createBulkDeleteJob(csvFilePath string, objectType string, format string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, err := createBulkJob(objectType, "delete", format, "", concurrencyMode) - if err != nil { - ErrorAndExit(err.Error()) - } - batchInfo, err := addBatchToJob(csvFilePath, jobInfo) - closeBulkJob(jobInfo.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - if !waitForCompletion { - if commandVersion == "old" { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } else { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk -c=batch -j=%s -b=%s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } - } - return -} - -func createBulkHardDeleteJob(csvFilePath string, objectType string, format string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, err := createBulkJob(objectType, "hardDelete", format, "", concurrencyMode) - if err != nil { - ErrorAndExit(err.Error()) - } - batchInfo, err := addBatchToJob(csvFilePath, jobInfo) - closeBulkJob(jobInfo.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - if !waitForCompletion { - if commandVersion == "old" { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } else { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk -c=batch -j=%s -b=%s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } - } - return -} - -func createBulkUpsertJob(csvFilePath string, objectType string, format string, externalId string, concurrencyMode string) (jobInfo JobInfo) { - jobInfo, err := createBulkJob(objectType, "upsert", format, externalId, concurrencyMode) - if err != nil { - ErrorAndExit(err.Error()) - } - batchInfo, err := addBatchToJob(csvFilePath, jobInfo) - closeBulkJob(jobInfo.Id) - if err != nil { - ErrorAndExit(err.Error()) - } - if !waitForCompletion { - if commandVersion == "old" { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk batch %s %s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } else { - fmt.Printf("Job created ( %s ) - for job status use\n force bulk -c=batch -j=%s -b=%s\n", jobInfo.Id, jobInfo.Id, batchInfo.Id) - } - } - return -} - func addBatchToJob(csvFilePath string, job JobInfo) (result BatchInfo, err error) { - force, _ := ActiveForce() - batches, err := SplitCSV(csvFilePath, 10000) if err != nil { return @@ -653,15 +463,13 @@ func splitFileIntoBatches(rows [][]string, batchsize int) (batches []string) { return } -func createBulkJob(objectType string, operation string, fileFormat string, externalId string, concurrencyMode string) (jobInfo JobInfo, err error) { +func createBulkJob(objectType string, operation string, fileFormat string, externalId string, concurrencyMode string, jobHeaders map[string]string) (jobInfo JobInfo, err error) { if !(strings.EqualFold(concurrencyMode, "serial")) { if !(strings.EqualFold(concurrencyMode, "parallel")) { ErrorAndExit("Concurrency Mode must be set to either Serial or Parallel") } } - force, _ := ActiveForce() - job := JobInfo{ Operation: operation, Object: objectType, @@ -677,16 +485,11 @@ func createBulkJob(objectType string, operation string, fileFormat string, exter } var options []func(*http.Request) - var pkChunkOptions []string - if pkChunkSize != 0 { - pkChunkOptions = append(pkChunkOptions, fmt.Sprintf("chunkSize=%d", pkChunkSize)) - } - if pkChunkParent != "" { - pkChunkOptions = append(pkChunkOptions, fmt.Sprintf("parent=%s", pkChunkParent)) - } - if len(pkChunkOptions) > 0 { + if len(jobHeaders) > 0 { options = append(options, func(req *http.Request) { - req.Header.Add("Sforce-Enable-PKChunking", strings.Join(pkChunkOptions, ";")) + for k, v := range jobHeaders { + req.Header.Add(k, v) + } }) } @@ -695,8 +498,6 @@ func createBulkJob(objectType string, operation string, fileFormat string, exter } func closeBulkJob(jobId string) (jobInfo JobInfo, err error) { - force, _ := ActiveForce() - jobInfo, err = force.CloseBulkJob(jobId) if err != nil { ErrorAndExit(err.Error()) diff --git a/command/command.go b/command/command.go deleted file mode 100644 index 7da2ebb6..00000000 --- a/command/command.go +++ /dev/null @@ -1,87 +0,0 @@ -package command - -import ( - "flag" - "fmt" - "strings" -) - -var Commands = []*Command{ - cmdActive, - cmdApex, - cmdApiVersion, - cmdAura, - cmdBigObject, - cmdBulk, - cmdCreate, - cmdDataPipe, - cmdDescribe, - cmdEventLogFile, - cmdExport, - cmdFetch, - cmdField, - cmdHelp, - cmdImport, - cmdLimits, - cmdLog, - cmdLogin, - cmdLogins, - cmdLogout, - cmdNotifySet, - cmdOauth, - cmdOpen, - cmdPackage, - cmdPassword, - cmdPush, - cmdQuery, - cmdQuickDeploy, - cmdRecord, - cmdRest, - cmdSecurity, - cmdSobject, - cmdTest, - cmdTrace, - cmdUseDXAuth, - cmdVersion, - cmdWhoami, -} - -type Command struct { - // args does not include the command name - Run func(cmd *Command, args []string) - Flag flag.FlagSet - - Usage string // first word is the command name - Short string // `forego help` output - Long string // `forego help cmd` output - MaxExpectedArgs int // the maximum number of arguments this command expects, -1 if it is variadic -} - -func (c *Command) PrintUsage() { - if c.Runnable() { - fmt.Printf("Usage: force %s\n\n", c.Usage) - } - fmt.Println(strings.Trim(c.Long, "\n")) -} - -func (c *Command) Name() string { - name := c.Usage - i := strings.Index(name, " ") - if i >= 0 { - name = name[:i] - } - return name -} - -func (c *Command) Runnable() bool { - return c.Run != nil -} - -func (c *Command) List() bool { - return c.Short != "" -} - -func (c *Command) InvalidInvocation(args []string) { - fmt.Printf("Invalid invocation: force %s\n\n", strings.Join(args, " ")) - c.PrintUsage() -} diff --git a/command/command_test.go b/command/command_test.go deleted file mode 100644 index 839059d9..00000000 --- a/command/command_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package command_test - -import ( - "testing" - - . "github.com/ForceCLI/force/command" - "github.com/bmizerany/assert" -) - -// test that all avialable commands come with at least a name and short usage information -func TestUsage(t *testing.T) { - for _, cmd := range Commands { - assert.NotEqual(t, cmd.Name(), "") - assert.NotEqual(t, cmd.Short, "") - } -} diff --git a/command/create.go b/command/create.go index 7e092d88..07cbf5a0 100644 --- a/command/create.go +++ b/command/create.go @@ -5,77 +5,97 @@ import ( "strings" . "github.com/ForceCLI/force/error" - . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdCreate = &Command{ - Usage: "create --type --name [--sobject ]", - Short: "Creates a new, empty Apex Class, Trigger, Visualforce page, or Component.", - Long: ` -Creates a new, empty Apex Class, Trigger, Visualforce page, or Component. +func init() { + createApexClassCmd.Flags().StringP("name", "n", "", "Name of Apex Class") + createApexTriggerCmd.Flags().StringP("name", "n", "", "Name of Apex Trigger") + createApexComponentCmd.Flags().StringP("name", "n", "", "Name of Visualforce Component") + createApexPageCmd.Flags().StringP("name", "n", "", "Name of Visualforce Page") + createApexTriggerCmd.Flags().StringP("sobject", "s", "", "For which sobject should the trigger be created") -Examples: + createApexClassCmd.MarkFlagRequired("name") + createApexTriggerCmd.MarkFlagRequired("name") + createApexTriggerCmd.MarkFlagRequired("sobject") + createApexComponentCmd.MarkFlagRequired("name") + createApexPageCmd.MarkFlagRequired("name") + createCmd.AddCommand(createApexClassCmd) + createCmd.AddCommand(createApexTriggerCmd) + createCmd.AddCommand(createApexComponentCmd) + createCmd.AddCommand(createApexPageCmd) + RootCmd.AddCommand(createCmd) +} - force create -t ApexClass -n NewController +var createCmd = &cobra.Command{ + Use: "create", + Short: "Creates a new, empty Apex Class, Trigger, Visualforce page, or Component.", + Args: cobra.MaximumNArgs(0), +} - force create -t ApexTrigger -n NewTrigger -s Account +var createApexClassCmd = &cobra.Command{ + Use: "apexclass", + Short: "Create an Apex Class", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + itemName, _ := cmd.Flags().GetString("name") + runCreate("apexclass", itemName, "") + }, +} - force create -t ApexPage -n CoolPage +var createApexTriggerCmd = &cobra.Command{ + Use: "apextrigger", + Short: "Create an Apex Trigger", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + itemName, _ := cmd.Flags().GetString("name") + sobjectName, _ := cmd.Flags().GetString("sobject") + runCreate("apextrigger", itemName, sobjectName) + }, +} - force create -t ApexComponent -n CoolComponent -`, - MaxExpectedArgs: 0, +var createApexComponentCmd = &cobra.Command{ + Use: "apexcomponent", + Short: "Create a Visualforce Component", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + itemName, _ := cmd.Flags().GetString("name") + runCreate("apexcomponent", itemName, "") + }, } -var ( - what string - sObjectName string - itemName string -) -func init() { - cmdCreate.Flag.StringVar(&what, "type", "", "What type of thing to create (currently only Apex or Visualforce).") - cmdCreate.Flag.StringVar(&what, "t", "", "What type of thing to create (currently only Apex or Visualforce).") - cmdCreate.Flag.StringVar(&sObjectName, "sobject", "", "For which sobject should the trigger be created.") - cmdCreate.Flag.StringVar(&sObjectName, "s", "", "For which sobject should the trigger be created.") - cmdCreate.Flag.StringVar(&itemName, "n", "", "Name of thing to be created.") - cmdCreate.Flag.StringVar(&itemName, "name", "", "Name of thing to be created.") - cmdCreate.Flag.StringVar(&what, "what", "", "What type of thing to create [deprecated: use type]") - cmdCreate.Flag.StringVar(&what, "w", "", "What type of thing to create [deprecated: use t]") - cmdCreate.Run = runCreate +var createApexPageCmd = &cobra.Command{ + Use: "apexpage", + Short: "Create a Visualforce Page", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + itemName, _ := cmd.Flags().GetString("name") + runCreate("apexpage", itemName, "") + }, } -func runCreate(cmd *Command, args []string) { - force, _ := ActiveForce() - if len(what) == 0 || len(itemName) == 0 { - cmd.PrintUsage() - } else { - attrs := make(map[string]string) - switch strings.ToLower(what) { - case "apexclass": - attrs = getApexDefinition() - case "apextrigger": - if len(sObjectName) == 0 { - cmd.PrintUsage() - return - } - attrs = getTriggerDefinition() - case "apexcomponent": - attrs = getVFComponentDefinition() - case "visualforce", "apexpage": - what = "apexpage" - attrs = getVFDefinition() - } +func runCreate(what, itemName, sobjectName string) { + attrs := make(map[string]string) + switch strings.ToLower(what) { + case "apexclass": + attrs = getApexDefinition(itemName) + case "apextrigger": + attrs = getTriggerDefinition(itemName, sobjectName) + case "apexcomponent": + attrs = getVFComponentDefinition(itemName) + case "apexpage": + attrs = getVFDefinition(itemName) + } - _, err := force.CreateToolingRecord(what, attrs) - if err != nil { - ErrorAndExit(fmt.Sprintf("Failed to create %s %s: %s", itemName, what, err.Error())) - } else { - fmt.Printf("Created new %s named %s.\n", what, itemName) - } + _, err := force.CreateToolingRecord(what, attrs) + if err != nil { + ErrorAndExit(fmt.Sprintf("Failed to create %s %s: %s", itemName, what, err.Error())) + } else { + fmt.Printf("Created new %s named %s.\n", what, itemName) } } -func getVFDefinition() (attrs map[string]string) { +func getVFDefinition(itemName string) (attrs map[string]string) { attrs = make(map[string]string) attrs["markup"] = "\n\n" attrs["name"] = itemName @@ -83,7 +103,7 @@ func getVFDefinition() (attrs map[string]string) { return } -func getVFComponentDefinition() (attrs map[string]string) { +func getVFComponentDefinition(itemName string) (attrs map[string]string) { attrs = make(map[string]string) attrs["markup"] = "\n\n" attrs["name"] = itemName @@ -91,7 +111,7 @@ func getVFComponentDefinition() (attrs map[string]string) { return } -func getApexDefinition() (attrs map[string]string) { +func getApexDefinition(itemName string) (attrs map[string]string) { attrs = make(map[string]string) attrs["status"] = "Active" attrs["body"] = fmt.Sprintf("public with sharing class %s {\n\n}", itemName) @@ -99,7 +119,7 @@ func getApexDefinition() (attrs map[string]string) { return } -func getTriggerDefinition() (attrs map[string]string) { +func getTriggerDefinition(itemName, sObjectName string) (attrs map[string]string) { attrs = make(map[string]string) attrs["status"] = "Active" attrs["body"] = fmt.Sprintf("trigger %s on %s (before insert, after insert, before update, after update, before delete, after delete, after undelete) { \n\n }", itemName, sObjectName) diff --git a/command/datapipe.go b/command/datapipe.go index 9ffbe8b6..2777dc72 100644 --- a/command/datapipe.go +++ b/command/datapipe.go @@ -7,53 +7,131 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdDataPipe = &Command{ - Usage: "datapipe []", - Short: "Manage DataPipes", - Long: ` -Manage DataPipes +func init() { + dataPipeCreateCmd.Flags().StringP("name", "n", "", "data pipeline name") + dataPipeCreateCmd.Flags().StringP("masterlabel", "l", "", "master label") + dataPipeCreateCmd.Flags().StringP("scriptcontent", "c", defaultContent, "script content") + dataPipeCreateCmd.Flags().StringP("apiversion", "v", ApiVersionNumber(), "script content") + dataPipeCreateCmd.Flags().StringP("scripttype", "t", "Pig", "script type") + + dataPipeUpdateCmd.Flags().StringP("name", "n", "", "data pipeline name") + dataPipeUpdateCmd.Flags().StringP("masterlabel", "l", "", "master label") + dataPipeUpdateCmd.Flags().StringP("scriptcontent", "c", defaultContent, "script content") + dataPipeUpdateCmd.Flags().StringP("apiversion", "v", ApiVersionNumber(), "script content") + dataPipeUpdateCmd.Flags().StringP("scripttype", "t", "Pig", "script type") + + dataPipeListCmd.Flags().StringP("format", "f", "json", "format (csv or json)") + + dataPipeDeleteCmd.Flags().StringP("name", "n", "", "data pipeline name") + + dataPipeCreateJobCmd.Flags().StringP("name", "n", "", "data pipeline name") + dataPipeQueryJobCmd.Flags().StringP("jobid", "j", "", "id of data pipeline job") -Usage: + dataPipeCreateCmd.MarkFlagRequired("name") + dataPipeUpdateCmd.MarkFlagRequired("name") + dataPipeDeleteCmd.MarkFlagRequired("name") - force datapipe create -n [-l masterlabel] [-t scripttype] [-c scriptcontent] [-v apiversion] + dataPipeCreateJobCmd.MarkFlagRequired("name") + dataPipeQueryJobCmd.MarkFlagRequired("jobid") - force datapipe update -n [-l masterlabel] [-t scripttype] [-c scriptcontent] [-v apiversion] + dataPipeCmd.AddCommand(dataPipeCreateCmd) + dataPipeCmd.AddCommand(dataPipeUpdateCmd) + dataPipeCmd.AddCommand(dataPipeDeleteCmd) + dataPipeCmd.AddCommand(dataPipeListCmd) + dataPipeCmd.AddCommand(dataPipeCreateJobCmd) + dataPipeCmd.AddCommand(dataPipeListJobsCmd) + dataPipeCmd.AddCommand(dataPipeQueryJobCmd) - force datapipe delete -n + RootCmd.AddCommand(dataPipeCmd) +} + +var dataPipeCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create Data Pipeline", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + masterLabel, _ := cmd.Flags().GetString("masterlabel") + apiVersion, _ := cmd.Flags().GetString("apiversion") + scriptContent, _ := cmd.Flags().GetString("scriptcontent") + scriptType, _ := cmd.Flags().GetString("scriptType") + runDataPipelineCreate(name, masterLabel, apiVersion, scriptContent, scriptType) + }, +} - force datapipe list -f <"csv" or "json"> +var dataPipeUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update Data Pipeline", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + masterLabel, _ := cmd.Flags().GetString("masterlabel") + scriptContent, _ := cmd.Flags().GetString("scriptcontent") + runDataPipelineUpdate(name, masterLabel, scriptContent) + }, +} + +var dataPipeDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete Data Pipeline", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + runDataPipelineDelete(name) + }, +} - force datapipe query -q +var dataPipeListCmd = &cobra.Command{ + Use: "list", + Short: "List Data Pipelines", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + format, _ := cmd.Flags().GetString("format") + runDataPipelineList(format) + }, +} - force datapipe createjob -n +var dataPipeCreateJobCmd = &cobra.Command{ + Use: "createjob", + Short: "Create Data Pipeline Job", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + runDataPipelineJob(name) + }, +} -Commands: - create creates a new dataPipe - update update a dataPipe - delete delete a datapipe - list list all datapipes - query query for a specific datapipe(s) - createjob creates a new job for a specific datapipe - listjobs list the status of submitted jobs - queryjob returns data about a datapipeline job (not implemented) - retrieve (not implemented) +var dataPipeListJobsCmd = &cobra.Command{ + Use: "listjobs", + Short: "List Data Pipeline Jobs", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + runDataPipelineListJobs() + }, +} -Examples: +var dataPipeQueryJobCmd = &cobra.Command{ + Use: "queryjob", + Short: "Query Data Pipeline Job", + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + jobId, _ := cmd.Flags().GetString("jobid") + runDataPipelineQueryJob(jobId) + }, +} +var dataPipeCmd = &cobra.Command{ + Use: "datapipe []", + Short: "Manage DataPipes", + Example: ` force datapipe create -n=MyPipe -l="My Pipe" -t=Pig -v=34.0 \ -c="A = load 'force://soql/Select Id, Name From Contact' using \ gridforce.hadoop.pig.loadstore.func.ForceStorage();" - -Defaults - -l Defaults to the name - -t Pig (only option available currently) - -c Pig script template - -v Current API version *Number only - `, - MaxExpectedArgs: -1, + Args: cobra.MaximumNArgs(0), } var defaultContent = ` @@ -62,85 +140,17 @@ A = load 'ffx://REPLACE_ME' using gridforce.hadoop.pig.loadstore.func.ForceStora Store A into 'ffx://REPLACE_ME_TOO' using gridforce.hadoop.pig.loadstore.func.ForceStorage(); ` -var ( - dpname string - masterlabel string - scriptcontent string - apiversion string - scripttype string - query string - format string - jobid string -) - -func init() { - cmdDataPipe.Flag.StringVar(&dpname, "name", "", "set datapipeline name") - cmdDataPipe.Flag.StringVar(&dpname, "n", "", "set datapipeline name") - cmdDataPipe.Flag.StringVar(&masterlabel, "masterlabel", "", "set master label") - cmdDataPipe.Flag.StringVar(&masterlabel, "l", "", "set master label") - cmdDataPipe.Flag.StringVar(&scriptcontent, "scriptcontent", defaultContent, "set script content") - cmdDataPipe.Flag.StringVar(&scriptcontent, "c", defaultContent, "set script content") - cmdDataPipe.Flag.StringVar(&apiversion, "apiversion", ApiVersionNumber(), "set api version") - cmdDataPipe.Flag.StringVar(&apiversion, "v", ApiVersionNumber(), "set api version") - cmdDataPipe.Flag.StringVar(&scripttype, "scripttype", "Pig", "set script type") - cmdDataPipe.Flag.StringVar(&scripttype, "t", "Pig", "set script type") - cmdDataPipe.Flag.StringVar(&query, "q", "", "SOQL query string on DataPipeline object") - cmdDataPipe.Flag.StringVar(&query, "query", "", "SOQL query string on DataPipeline object") - cmdDataPipe.Flag.StringVar(&format, "f", "json", "format for listing datapipelines (csv or json)") - cmdDataPipe.Flag.StringVar(&format, "format", "json", "format for listing datapipelines (csv or json)") - cmdDataPipe.Flag.StringVar(&jobid, "j", "json", "Id of data pipline job to retrieve") - cmdDataPipe.Flag.StringVar(&jobid, "jobid", "json", "Id of data pipline job to retrieve") - cmdDataPipe.Run = runDataPipe -} - -func runDataPipe(cmd *Command, args []string) { - if len(args) == 0 { - cmd.PrintUsage() - } else { - if err := cmd.Flag.Parse(args[1:]); err != nil { - os.Exit(2) - } - - switch args[0] { - case "create": - runDataPipelineCreate() - case "update": - runDataPipelineUpdate() - case "delete": - runDataPipelineDelete() - case "list": - runDataPipelineList() - case "query": - runDataPipelineQuery() - case "createjob": - runDataPipelineJob() - case "listjobs": - runDataPipelineListJobs() - case "queryjob": - runDataPipelineQueryJob() - default: - ErrorAndExit("no such command: %s", args[0]) - } - } -} - -func runDataPipelineJob() { - if len(dpname) == 0 { - ErrorAndExit("You need to provide the name of a pipeline to create a job for.") - } - force, _ := ActiveForce() - id := GetDataPipelineId(dpname) +func runDataPipelineJob(name string) { + id := GetDataPipelineId(name) _, err, _ := force.CreateDataPipelineJob(id) if err != nil { ErrorAndExit(err.Error()) } - fmt.Printf("Successfully created DataPipeline job for %s\n", dpname) + fmt.Printf("Successfully created DataPipeline job for %s\n", name) } func runDataPipelineListJobs() { - //query = "SELECT Id, DataPipeline.DeveloperName, Status, FailureState, LastModifiedDate, CreatedDate, CreatedById, DataPipelineId, JobErrorMessage FROM DataPipelineJob" - query = "SELECT Id, DataPipeline.DeveloperName, Status, FailureState FROM DataPipelineJob" - force, _ := ActiveForce() + query := "SELECT Id, DataPipeline.DeveloperName, Status, FailureState FROM DataPipelineJob" result, err := force.QueryDataPipelineJob(query) if err != nil { ErrorAndExit(err.Error()) @@ -149,10 +159,8 @@ func runDataPipelineListJobs() { force.DisplayAllForceRecordsf(result, "csv") } -func runDataPipelineQueryJob() { - //query = "SELECT Id, DataPipeline.DeveloperName, Status, FailureState, LastModifiedDate, CreatedDate, CreatedById, DataPipelineId, JobErrorMessage FROM DataPipelineJob" - query = fmt.Sprintf("SELECT Id, DataPipeline.DeveloperName, Status, FailureState, JobErrorMessage FROM DataPipelineJob Where id = '%s'", jobid) - force, _ := ActiveForce() +func runDataPipelineQueryJob(jobId string) { + query := fmt.Sprintf("SELECT Id, DataPipeline.DeveloperName, Status, FailureState, JobErrorMessage FROM DataPipelineJob Where id = '%s'", jobId) result, err := force.QueryDataPipelineJob(query) if err != nil { ErrorAndExit(err.Error()) @@ -161,52 +169,29 @@ func runDataPipelineQueryJob() { force.DisplayAllForceRecordsf(result, "csv") } -func runDataPipelineQuery() { - if len(query) == 0 { - ErrorAndExit("You have to supply a SOQL query using the -q flag.") - } - force, _ := ActiveForce() - result, err := force.QueryDataPipeline(query) - if err != nil { - ErrorAndExit(err.Error()) - } - - fmt.Println("Result: \n", result) -} - -func runDataPipelineCreate() { - if len(dpname) == 0 { - ErrorAndExit("You must specify a name for the datapipeline using the -n flag.") - } +func runDataPipelineCreate(name, masterlabel, apiversion, scriptcontent, scripttype string) { if len(masterlabel) == 0 { - masterlabel = dpname + masterlabel = name } - - force, _ := ActiveForce() - _, err, _ := force.CreateDataPipeline(dpname, masterlabel, apiversion, scriptcontent, scripttype) + _, err, _ := force.CreateDataPipeline(name, masterlabel, apiversion, scriptcontent, scripttype) if err != nil { ErrorAndExit(err.Error()) } - fmt.Printf("DataPipeline %s successfully created.\n", dpname) + fmt.Printf("DataPipeline %s successfully created.\n", name) } -func runDataPipelineUpdate() { - if len(dpname) == 0 { - ErrorAndExit("You must specify a name for the datapipeline using the -n flag.") - } +func runDataPipelineUpdate(name, masterlabel, scriptcontent string) { if len(masterlabel) == 0 && len(scriptcontent) == 0 { ErrorAndExit("You can change the master label or the script content.") } - force, _ := ActiveForce() - - result, err := force.GetDataPipeline(dpname) + result, err := force.GetDataPipeline(name) if err != nil { ErrorAndExit(err.Error()) } if len(result.Records) == 0 { - ErrorAndExit("No data pipeline found named " + dpname) + ErrorAndExit("No data pipeline found named " + name) } for _, record := range result.Records { var id string @@ -228,51 +213,48 @@ func runDataPipelineUpdate() { if err != nil { ErrorAndExit(err.Error()) } - fmt.Printf("%s successfully updated.\n", dpname) + fmt.Printf("%s successfully updated.\n", name) } } func readScriptFile(path string) (content string, err error) { data, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } content = string(data) - return + return content, nil } func GetDataPipelineId(name string) (id string) { - force, _ := ActiveForce() - - result, err := force.GetDataPipeline(dpname) + result, err := force.GetDataPipeline(name) if err != nil { ErrorAndExit(err.Error()) } if len(result.Records) == 0 { - ErrorAndExit("No data pipeline found named " + dpname) + ErrorAndExit("No data pipeline found named " + name) } record := result.Records[0] id = record["Id"].(string) - return + return id } -func runDataPipelineDelete() { - force, _ := ActiveForce() - - id := GetDataPipelineId(dpname) +func runDataPipelineDelete(name string) { + id := GetDataPipelineId(name) err := force.DeleteDataPipeline(id) if err != nil { ErrorAndExit(err.Error()) } - fmt.Printf("%s successfully deleted.\n", dpname) + fmt.Printf("%s successfully deleted.\n", name) } -func runDataPipelineList() { - force, _ := ActiveForce() - query = "SELECT Id, MasterLabel, DeveloperName, ScriptType FROM DataPipeline" +func runDataPipelineList(format string) { + query := "SELECT Id, MasterLabel, DeveloperName, ScriptType FROM DataPipeline" result, err := force.QueryDataPipeline(query) if err != nil { ErrorAndExit(err.Error()) } - force.DisplayAllForceRecordsf(result, format) } diff --git a/command/describe.go b/command/describe.go index bcaf7d4f..680e1111 100644 --- a/command/describe.go +++ b/command/describe.go @@ -5,94 +5,98 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdDescribe = &Command{ - Usage: "describe (metadata|sobject) [-n= -json]", - Short: "Describe the object or list of available objects", - Long: ` - -n, -name # name of specific metadata to retrieve - -json # output in JSON format - - Examples +func init() { + describeMetadataCmd.Flags().StringP("name", "n", "", "name of metadata") + describeMetadataCmd.Flags().BoolP("json", "j", false, "json output") + describeSobjectCmd.Flags().StringP("name", "n", "", "name of sobject") + describeSobjectCmd.Flags().BoolP("json", "j", false, "json output") - force describe -t=metadata -n=CustomObject - force describe -t=sobject -n=Account - `, - MaxExpectedArgs: 0, + describeCmd.AddCommand(describeMetadataCmd) + describeCmd.AddCommand(describeSobjectCmd) + RootCmd.AddCommand(describeCmd) } -var ( - jsonout bool - metaItem string -) - -func init() { - cmdDescribe.Flag.StringVar(&metaItem, "name", "", "name of metadata") - cmdDescribe.Flag.StringVar(&metaItem, "n", "", "name of metadata") - cmdDescribe.Flag.StringVar(&metadataType, "t", "", "Type of metadata to describe") - cmdDescribe.Flag.StringVar(&metadataType, "type", "", "Type of metadata to describe") - cmdDescribe.Flag.BoolVar(&jsonout, "j", false, "Unpage any static resources") - cmdDescribe.Flag.BoolVar(&jsonout, "json", false, "Unpage any static resources") - cmdDescribe.Run = runDescribe +var describeMetadataCmd = &cobra.Command{ + Use: "metadata", + Short: "Describe metadata", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + item, _ := cmd.Flags().GetString("name") + json, _ := cmd.Flags().GetBool("json") + describeMetadata(item, json) + }, } -func runDescribe(cmd *Command, args []string) { - if len(metadataType) == 0 { - ErrorAndExit("You must specify metadata or sobject for description\nexample: force describe -t metadata") - } - if metadataType != "metadata" && metadataType != "sobject" { - ErrorAndExit("Only metadata and sobject can be described") - } +var describeSobjectCmd = &cobra.Command{ + Use: "sobject", + Short: "Describe sobject", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + item, _ := cmd.Flags().GetString("name") + json, _ := cmd.Flags().GetBool("json") + describeSObject(item, json) + }, +} - force, _ := ActiveForce() +var describeCmd = &cobra.Command{ + Use: "describe (metadata|sobject) [flags]", + Short: "Describe the object or list of available objects", + Example: ` + force describe metadata -n=CustomObject + force describe sobject -n=Account + `, + Args: cobra.ExactArgs(0), +} - if metadataType == "metadata" { - if len(metaItem) == 0 { - // List all metadata - describe, err := force.Metadata.DescribeMetadata() - if err != nil { - ErrorAndExit(err.Error()) - } - if jsonout { - DisplayMetadataListJson(describe.MetadataObjects) - } else { - DisplayMetadataList(describe.MetadataObjects) - } +func describeMetadata(item string, json bool) { + if len(item) == 0 { + // List all metadata + describe, err := force.Metadata.DescribeMetadata() + if err != nil { + ErrorAndExit(err.Error()) + } + if json { + DisplayMetadataListJson(describe.MetadataObjects) } else { - // List all metdata object of metaItem type - body, err := force.Metadata.ListMetadata(metaItem) - if err != nil { - ErrorAndExit(err.Error()) - } - var res struct { - Response ListMetadataResponse `xml:"Body>listMetadataResponse"` - } - if err = xml.Unmarshal(body, &res); err != nil { - ErrorAndExit(err.Error()) - } - if jsonout { - DisplayListMetadataResponseJson(res.Response) - } else { - DisplayListMetadataResponse(res.Response) - } + DisplayMetadataList(describe.MetadataObjects) } } else { - if len(metaItem) == 0 { - // list all sobject - if jsonout { - l := getSobjectList(make([]string, 0)) - DisplayForceSobjectsJson(l) - } else { - runSobjectList(make([]string, 0)) - } + // List all metdata object of metaItem type + body, err := force.Metadata.ListMetadata(item) + if err != nil { + ErrorAndExit(err.Error()) + } + var res struct { + Response ListMetadataResponse `xml:"Body>listMetadataResponse"` + } + if err = xml.Unmarshal(body, &res); err != nil { + ErrorAndExit(err.Error()) + } + if json { + DisplayListMetadataResponseJson(res.Response) } else { - // describe sobject - desc, err := force.DescribeSObject(metaItem) - if err != nil { - ErrorAndExit(err.Error()) - } - DisplayForceSobjectDescribe(desc) + DisplayListMetadataResponse(res.Response) + } + } +} +func describeSObject(item string, json bool) { + if len(item) == 0 { + // list all sobject + if json { + l := getSobjectList("") + DisplayForceSobjectsJson(l) + } else { + runSobjectList("") + } + } else { + // describe sobject + desc, err := force.DescribeSObject(item) + if err != nil { + ErrorAndExit(err.Error()) } + DisplayForceSobjectDescribe(desc) } } diff --git a/command/eventlogfile.go b/command/eventlogfile.go index 2bfd9d00..203a09f1 100644 --- a/command/eventlogfile.go +++ b/command/eventlogfile.go @@ -5,36 +5,42 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdEventLogFile = &Command{ - Run: getEventLogFile, - Usage: "eventlogfile [eventlogfileId]", - Short: "List and fetch event log file", - Long: ` -List and fetch event log file +func init() { + RootCmd.AddCommand(eventLogFileCmd) +} -Examples: +var eventLogFileCmd = &cobra.Command{ + Use: "eventlogfile [eventlogfileId]", + Short: "List and fetch event log file", + Example: ` force eventlogfile force eventlogfile 0AT300000000XQ7GAM `, - MaxExpectedArgs: 1, + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + listEventLogFiles() + return + } + getEventLogFile(args[0]) + }, } -func getEventLogFile(cmd *Command, args []string) { - force, _ := ActiveForce() - if len(args) == 0 { - records, err := force.QueryEventLogFiles() - if err != nil { - ErrorAndExit(err.Error()) - } - DisplayForceRecords(records) - } else { - logId := args[0] - log, err := force.RetrieveEventLogFile(logId) - if err != nil { - ErrorAndExit(err.Error()) - } - fmt.Println(log) +func listEventLogFiles() { + records, err := force.QueryEventLogFiles() + if err != nil { + ErrorAndExit(err.Error()) + } + DisplayForceRecords(records) +} + +func getEventLogFile(logId string) { + log, err := force.RetrieveEventLogFile(logId) + if err != nil { + ErrorAndExit(err.Error()) } + fmt.Println(log) } diff --git a/command/export.go b/command/export.go index 212c123e..0f9ea629 100644 --- a/command/export.go +++ b/command/export.go @@ -11,65 +11,48 @@ import ( "github.com/ForceCLI/force/config" . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdExport = &Command{ - Run: runExport, - Usage: "export [options] [dir]", - Short: "Export metadata to a local directory", - Long: ` -Export metadata to a local directory - -Export Options - -w, -warnings # Display warnings about metadata that cannot be retrieved - -x, -exclude # Exclude given metadata type +func init() { + exportCmd.Flags().BoolP("warnings", "w", false, "display warnings about metadata that cannot be retrieved") + exportCmd.Flags().StringSliceP("exclude", "x", []string{}, "exclude metadata type") -Examples: + RootCmd.AddCommand(exportCmd) +} +var exportCmd = &cobra.Command{ + Use: "export [dir]", + Short: "Export metadata to a local directory", + Example: ` force export - force export org/schema - force export -x ApexClass -x CustomObject `, - MaxExpectedArgs: 1, -} - -type metadataList []string - -func (i *metadataList) String() string { - return fmt.Sprint(*i) -} - -func (i *metadataList) Set(value string) error { - *i = append(*i, value) - return nil -} - -var ( - showWarnings bool - excludeMetadataNames metadataList -) - -func init() { - cmdExport.Flag.BoolVar(&showWarnings, "w", false, "show warnings") - cmdExport.Flag.BoolVar(&showWarnings, "warnings", false, "show warnings") - cmdExport.Flag.Var(&excludeMetadataNames, "x", "exclude metadata type") - cmdExport.Flag.Var(&excludeMetadataNames, "exclude", "exclude metadata type") + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var root string + var err error + if len(args) == 1 { + root, err = filepath.Abs(args[0]) + if err != nil { + fmt.Printf("Error obtaining file path\n") + ErrorAndExit(err.Error()) + } + } else { + root, err = config.GetSourceDir() + if err != nil { + fmt.Printf("Error obtaining root directory\n") + ErrorAndExit(err.Error()) + } + } + excludeMetadataNames, _ := cmd.Flags().GetStringSlice("exclude") + showWarnings, _ := cmd.Flags().GetBool("warnings") + runExport(root, excludeMetadataNames, showWarnings) + }, } -func runExport(cmd *Command, args []string) { - // Get path from args if available - var err error - var root string - if len(args) == 1 { - root, err = filepath.Abs(args[0]) - } - if err != nil { - fmt.Printf("Error obtaining file path\n") - ErrorAndExit(err.Error()) - } - force, _ := ActiveForce() +func runExport(root string, excludeMetadataNames []string, showWarnings bool) { sobjects, err := force.ListSobjects() if err != nil { ErrorAndExit(err.Error()) @@ -79,7 +62,7 @@ func runExport(cmd *Command, args []string) { sort.Strings(excludeMetadataNames) - if !isExcluded(customObject) { + if !isExcluded(excludeMetadataNames, customObject) { stdObjects := make([]string, 1, len(sobjects)+1) stdObjects[0] = "*" for _, sobject := range sobjects { @@ -195,7 +178,7 @@ func runExport(cmd *Command, args []string) { } for _, name := range metadataNames { - if !isExcluded(name) { + if !isExcluded(excludeMetadataNames, name) { query = append(query, ForceMetadataQueryElement{Name: []string{name}, Members: []string{"*"}}) } } @@ -215,19 +198,11 @@ func runExport(cmd *Command, args []string) { ErrorAndExit(err.Error()) } - if !isExcluded(string(foldersType)) { + if !isExcluded(excludeMetadataNames, string(foldersType)) { query = append(query, ForceMetadataQueryElement{Name: []string{string(foldersType)}, Members: members}) } } - if root == "" { - root, err = config.GetSourceDir() - if err != nil { - fmt.Printf("Error obtaining root directory\n") - ErrorAndExit(err.Error()) - } - } - files, problems, err := force.Metadata.Retrieve(query) if err != nil { fmt.Printf("Encountered and error with retrieve...\n") @@ -251,7 +226,7 @@ func runExport(cmd *Command, args []string) { fmt.Printf("Exported to %s\n", root) } -func isExcluded(name string) bool { +func isExcluded(excludeMetadataNames []string, name string) bool { index := sort.SearchStrings(excludeMetadataNames, name) return index < len(excludeMetadataNames) && excludeMetadataNames[index] == name diff --git a/command/export_test.go b/command/export_test.go index f1aa70ab..90a20f9f 100644 --- a/command/export_test.go +++ b/command/export_test.go @@ -6,7 +6,6 @@ import ( func TestIsExcluded(t *testing.T) { excluded := []string{"ApexClass", "CustomThing"} - excludeMetadataNames = append(excludeMetadataNames, excluded...) testCases := []struct { input string @@ -18,7 +17,7 @@ func TestIsExcluded(t *testing.T) { for _, test := range testCases { t.Run(test.input, func(t *testing.T) { - got := isExcluded(test.input) + got := isExcluded(excluded, test.input) if got != test.expected { t.Errorf("Expected %v got %v for %s entry", test.expected, got, test.input) diff --git a/command/fetch.go b/command/fetch.go index 122a8fe0..04235045 100644 --- a/command/fetch.go +++ b/command/fetch.go @@ -15,45 +15,39 @@ import ( "github.com/ForceCLI/force/config" . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdFetch = &Command{ - Usage: "fetch -t ApexClass", +func init() { + fetchCmd.Flags().StringSliceVarP(&metadataName, "name", "n", []string{}, "names of metadata") + fetchCmd.Flags().StringSliceVarP(&metadataTypes, "type", "t", []string{}, "Type of metadata to fetch") + fetchCmd.Flags().StringVarP(&targetDirectory, "directory", "d", "", "Use to specify the root directory of your project") + fetchCmd.Flags().BoolVarP(&unpack, "unpack", "u", false, "Unpack any static resources") + fetchCmd.Flags().BoolVarP(&preserveZip, "preserve", "p", false, "keep zip file on disk") + fetchCmd.Flags().StringVarP(&packageXml, "xml", "x", "", "Package.xml file to use for fetch.") + makefile = true + RootCmd.AddCommand(fetchCmd) +} + +var fetchCmd = &cobra.Command{ + Use: "fetch -t ApexClass", Short: "Export specified artifact(s) to a local directory", Long: ` - -t, -type # type of metadata to retrieve (multiple ok if -n not used) - -n, -name # name of specific metadata to retrieve (must be used with -type) - -d, -directory # override the default target directory - -u, -unpack # unpack any zipped static resources (ignored if type is not StaticResource) - -p, -preserve # preserve the zip file - -x, -xml # provide a package.xml file to fetch data specified within - Export specified artifact(s) to a local directory. Use "package" type to retrieve an unmanaged package. - -Examples - +`, + Example: ` force fetch -t=CustomObject -n=Book__c -n=Author__c force fetch -t Aura -n MyComponent -d /Users/me/Documents/Project/home force fetch -t AuraDefinitionBundle -t ApexClass force fetch -x myproj/metadata/package.xml `, - MaxExpectedArgs: 0, -} - -type metaName []string - -func (i *metaName) String() string { - return fmt.Sprint(*i) + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + runFetch() + }, } -func (i *metaName) Set(value string) error { - // That would permit usages such as - // -deltaT 10s -deltaT 15s - for _, name := range strings.Split(value, ",") { - *i = append(*i, name) - } - return nil -} +type metaName = []string var ( metadataTypes metaName @@ -66,26 +60,7 @@ var ( packageXml string ) -func init() { - cmdFetch.Flag.Var(&metadataName, "name", "names of metadata") - cmdFetch.Flag.Var(&metadataName, "n", "names of metadata") - cmdFetch.Flag.Var(&metadataTypes, "t", "Type of metadata to fetch") - cmdFetch.Flag.Var(&metadataTypes, "type", "Type of metadata to fetch") - cmdFetch.Flag.StringVar(&targetDirectory, "d", "", "Use to specify the root directory of your project") - cmdFetch.Flag.StringVar(&targetDirectory, "directory", "", "Use to specify the root directory of your project") - cmdFetch.Flag.BoolVar(&unpack, "u", false, "Unpack any static resources") - cmdFetch.Flag.BoolVar(&unpack, "unpack", false, "Unpack any static resources") - cmdFetch.Flag.BoolVar(&preserveZip, "p", false, "keep zip file on disk") - cmdFetch.Flag.BoolVar(&preserveZip, "preserve", false, "keep zip file on disk") - cmdFetch.Flag.StringVar(&packageXml, "x", "", "Package.xml file to use for fetch.") - cmdFetch.Flag.StringVar(&packageXml, "xml", "", "Package.xml file to use for fetch.") - cmdFetch.Run = runFetch - makefile = true -} - -func runFetchAura2(cmd *Command, entityname string) { - force, _ := ActiveForce() - +func runFetchAura2(entityname string) { var bundles AuraDefinitionBundleResult var definitions AuraDefinitionBundleResult var err error @@ -229,10 +204,7 @@ func getWildcardQuery(force *Force, metadataTypes metaName) (query ForceMetadata return } -func runFetch(cmd *Command, args []string) { - - force, _ := ActiveForce() - +func runFetch() { if len(packageXml) == 0 && len(metadataTypes) == 0 { ErrorAndExit("must specify object type and/or object name or package xml path") } @@ -248,10 +220,10 @@ func runFetch(cmd *Command, args []string) { if len(metadataTypes) == 1 && strings.ToLower(metadataTypes[0]) == "aura" { if len(metadataName) > 0 { for names := range metadataName { - runFetchAura2(cmd, metadataName[names]) + runFetchAura2(metadataName[names]) } } else { - runFetchAura2(cmd, "") + runFetchAura2("") } } else if len(metadataTypes) == 1 && strings.ToLower(metadataTypes[0]) == "package" { if len(metadataName) > 0 { diff --git a/command/field.go b/command/field.go index a6fa8a68..fe6ea05f 100644 --- a/command/field.go +++ b/command/field.go @@ -6,11 +6,19 @@ import ( . "github.com/ForceCLI/force/error" . "github.com/ForceCLI/force/lib" + "github.com/spf13/cobra" ) -var cmdField = &Command{ - Run: runField, - Usage: "field", +func init() { + fieldCmd.AddCommand(fieldListCmd) + fieldCmd.AddCommand(fieldCreateCmd) + fieldCmd.AddCommand(fieldDeleteCmd) + fieldCmd.AddCommand(fieldTypeCmd) + RootCmd.AddCommand(fieldCmd) +} + +var fieldCmd = &cobra.Command{ + Use: "field", Short: "Manage SObject fields", Long: ` Manage SObject fields @@ -22,51 +30,69 @@ Usage: force field delete force field type force field type + `, -Examples: - + Example: ` force field list Todo__c - force field create Inspection__c "Final Outcome":picklist picklist:"Pass, Fail, Redo" + force field create Inspection__c "Final Outcome":picklist picklist:"Pass, Fail, Redo" force field create Todo__c Due:DateTime required:true force field delete Todo__c Due - force field type #displays all the supported field types - force field type email #displays the required and optional attributes + force field type # displays all the supported field types + force field type email # displays the required and optional attributes +`, + DisableFlagsInUseLine: true, +} +var fieldListCmd = &cobra.Command{ + Use: "list ", + Short: "List SObject fields", + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + runFieldList(args[0]) + }, +} + +var fieldCreateCmd = &cobra.Command{ + Use: "create : [