pulumi/cmd/api.go

148 lines
4.3 KiB
Go
Raw Normal View History

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/pack"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/workspace"
)
// pulumiCloudEndpoint returns the endpoint for the Pulumi Cloud Management Console API.
// e.g. "http://localhost:8080" or "https://api.moolumi.io".
func pulumiConsoleAPI() (string, error) {
envVar := os.Getenv("PULUMI_API")
if envVar == "" {
return "", fmt.Errorf("PULUMI_API env var not set")
}
return envVar, nil
}
// usePulumiCloudCommands returns whether or not to use the "Pulumi Cloud" version of CLI commands.
func usePulumiCloudCommands() bool {
_, err := pulumiConsoleAPI()
return err == nil
}
// cloudProjectIdentifier is the set of data needed to identify a Pulumi Cloud project. This the
// logical "home" of a stack on the Pulumi Cloud.
type cloudProjectIdentifier struct {
Owner string
Repository string
Project tokens.PackageName
}
// getCloudProjectIdentifiers returns the owner, repo name, and project name of the current package and workspace.
func getCloudProjectIdentifier(w workspace.W) (*cloudProjectIdentifier, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
path, err := workspace.DetectPackage(cwd)
if err != nil {
return nil, err
}
pkg, err := pack.Load(path)
if err != nil {
return nil, err
}
repo := w.Repository()
return &cloudProjectIdentifier{
Owner: repo.Owner,
Repository: repo.Name,
Project: pkg.Name,
}, nil
}
// pulumiAPICall makes an HTTP request to the Pulumi API.
func pulumiAPICall(method string, path string, body []byte, accessToken string) (*http.Response, error) {
apiEndpoint, err := pulumiConsoleAPI()
if err != nil {
return nil, fmt.Errorf("getting Pulumi API endpoint: %v", err)
}
url := fmt.Sprintf("%s/api%s", apiEndpoint, path)
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("creating new HTTP request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
// Apply credentials if provided.
if accessToken != "" {
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("performing HTTP request: %v", err)
}
return resp, nil
}
// pulumiRESTCall calls the pulumi REST API marshalling reqObj to JSON and using that as
// the request body (use nil for GETs), and if successful, marshalling the responseObj
// as JSON and storing it in respObj (use nil for NoContent). The error return type might
// be an instance of apitype.ErrorResponse, in which case will have the response code.
func pulumiRESTCall(method, path string, reqObj interface{}, respObj interface{}) error {
creds, err := getStoredCredentials()
if err != nil {
return fmt.Errorf("getting stored credentials: %v", err)
}
return pulumiRESTCallWithAccessToken(method, path, reqObj, respObj, creds.AccessToken)
}
// pulumiRESTCallWithAccessToken requires you pass in the auth token rather than reading it from the machine's config.
func pulumiRESTCallWithAccessToken(method, path string, reqObj interface{}, respObj interface{}, token string) error {
var reqBody []byte
var err error
if reqObj != nil {
reqBody, err = json.Marshal(reqObj)
if err != nil {
return fmt.Errorf("marshalling request object as JSON: %v", err)
}
}
resp, err := pulumiAPICall(method, path, reqBody, token)
if err != nil {
return fmt.Errorf("calling API: %v", err)
}
defer contract.IgnoreClose(resp.Body)
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading response from API: %v", err)
}
// 4xx and 5xx responses should be of type ErrorResponse. See if we can unmarshal as that
// type, and if not just return the raw response text.
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
var errResp apitype.ErrorResponse
if err = json.Unmarshal(respBody, &errResp); err != nil {
errResp.Code = resp.StatusCode
errResp.Message = string(respBody)
}
return &errResp
}
if respObj != nil {
if err = json.Unmarshal(respBody, respObj); err != nil {
return fmt.Errorf("unmarshalling response object: %v", err)
}
}
return nil
}