github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/commands/curl.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "code.cloudfoundry.org/cli/cf/flagcontext" 14 "code.cloudfoundry.org/cli/cf/flags" 15 . "code.cloudfoundry.org/cli/cf/i18n" 16 17 "code.cloudfoundry.org/cli/cf/api" 18 "code.cloudfoundry.org/cli/cf/commandregistry" 19 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 20 cfErrors "code.cloudfoundry.org/cli/cf/errors" 21 "code.cloudfoundry.org/cli/cf/requirements" 22 "code.cloudfoundry.org/cli/cf/terminal" 23 "code.cloudfoundry.org/cli/cf/trace" 24 ) 25 26 type Curl struct { 27 ui terminal.UI 28 config coreconfig.Reader 29 curlRepo api.CurlRepository 30 pluginCall bool 31 } 32 33 func init() { 34 commandregistry.Register(&Curl{}) 35 } 36 37 func (cmd *Curl) MetaData() commandregistry.CommandMetadata { 38 fs := make(map[string]flags.FlagSet) 39 fs["i"] = &flags.BoolFlag{ShortName: "i", Usage: T("Include response headers in the output")} 40 fs["X"] = &flags.StringFlag{ShortName: "X", Usage: T("HTTP method (GET,POST,PUT,DELETE,etc)")} 41 fs["H"] = &flags.StringSliceFlag{ShortName: "H", Usage: T("Custom headers to include in the request, flag can be specified multiple times")} 42 fs["d"] = &flags.StringFlag{ShortName: "d", Usage: T("HTTP data to include in the request body, or '@' followed by a file name to read the data from")} 43 fs["output"] = &flags.StringFlag{Name: "output", Usage: T("Write curl body to FILE instead of stdout")} 44 fs["fail"] = &flags.BoolFlag{ShortName: "f", Name: "fail", Usage: "Server errors return exit code 22"} 45 46 return commandregistry.CommandMetadata{ 47 Name: "curl", 48 Description: T("Executes a request to the targeted API endpoint"), 49 Usage: []string{ 50 T(`CF_NAME curl PATH [-iv] [-X METHOD] [-H HEADER]... [-d DATA] [--output FILE] 51 52 By default 'CF_NAME curl' will perform a GET to the specified PATH. If data 53 is provided via -d, a POST will be performed instead, and the Content-Type 54 will be set to application/json. You may override headers with -H and the 55 request method with -X. 56 57 For API documentation, please visit http://apidocs.cloudfoundry.org.`), 58 }, 59 Examples: []string{ 60 `CF_NAME curl "/v2/apps" -X GET -H "Content-Type: application/x-www-form-urlencoded" -d 'q=name:myapp'`, 61 `CF_NAME curl "/v2/apps" -d @/path/to/file`, 62 }, 63 Flags: fs, 64 } 65 } 66 67 func (cmd *Curl) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) { 68 if len(fc.Args()) != 1 { 69 cmd.ui.Failed(T("Incorrect Usage. An argument is missing or not correctly enclosed.\n\n") + commandregistry.Commands.CommandUsage("curl")) 70 return nil, fmt.Errorf("Incorrect usage: %d arguments of %d required", len(fc.Args()), 1) 71 } 72 73 reqs := []requirements.Requirement{ 74 requirementsFactory.NewAPIEndpointRequirement(), 75 } 76 77 return reqs, nil 78 } 79 80 func (cmd *Curl) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { 81 cmd.ui = deps.UI 82 cmd.config = deps.Config 83 cmd.curlRepo = deps.RepoLocator.GetCurlRepository() 84 cmd.pluginCall = pluginCall 85 return cmd 86 } 87 88 func (cmd *Curl) Execute(c flags.FlagContext) error { 89 path := c.Args()[0] 90 headers := c.StringSlice("H") 91 92 var method string 93 var body string 94 95 if c.IsSet("d") { 96 method = "POST" 97 98 jsonBytes, err := flagcontext.GetContentsFromOptionalFlagValue(c.String("d")) 99 if err != nil { 100 return err 101 } 102 body = string(jsonBytes) 103 } 104 105 if c.IsSet("X") { 106 method = c.String("X") 107 } 108 109 reqHeader := strings.Join(headers, "\n") 110 111 responseHeader, responseBody, err := cmd.curlRepo.Request(method, path, reqHeader, body, c.Bool("fail")) 112 if err != nil { 113 if httpErr, ok := err.(cfErrors.HTTPError); ok && c.Bool("fail") { 114 return cfErrors.NewCurlHTTPError(httpErr.StatusCode()) 115 } 116 return errors.New(T("Error creating request:\n{{.Err}}", map[string]interface{}{"Err": err.Error()})) 117 } 118 119 if trace.LoggingToStdout && !cmd.pluginCall { 120 return nil 121 } 122 123 if c.Bool("i") { 124 cmd.ui.Say(responseHeader) 125 } 126 127 if c.String("output") != "" { 128 err := cmd.writeToFile(responseBody, c.String("output")) 129 if err != nil { 130 return errors.New(T("Error creating request:\n{{.Err}}", map[string]interface{}{"Err": err})) 131 } 132 } else { 133 if strings.Contains(responseHeader, "application/json") { 134 buffer := bytes.Buffer{} 135 err := json.Indent(&buffer, []byte(responseBody), "", " ") 136 if err == nil { 137 responseBody = buffer.String() 138 } 139 } 140 141 cmd.ui.Say(responseBody) 142 } 143 return nil 144 } 145 146 func (cmd Curl) writeToFile(responseBody, filePath string) (err error) { 147 if _, err = os.Stat(filePath); os.IsNotExist(err) { 148 err = os.MkdirAll(filepath.Dir(filePath), 0755) 149 } 150 151 if err != nil { 152 return 153 } 154 155 return ioutil.WriteFile(filePath, []byte(responseBody), 0644) 156 }