code.cloudfoundry.org/cli@v7.1.0+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  }