github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/azure/internal/azurecli/az.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azurecli
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"os/exec"
    11  	"strings"
    12  
    13  	"github.com/Azure/go-autorest/autorest/adal"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  )
    17  
    18  // Logger for the Azure provider.
    19  var logger = loggo.GetLogger("juju.provider.azure.internal.azurecli")
    20  
    21  // AzureCLI
    22  type AzureCLI struct {
    23  	// Exec is a function that executes system commands and returns
    24  	// the output. If this is nil then a default implementation using
    25  	// os.exec will be used.
    26  	Exec func(cmd string, args []string) (stdout []byte, err error)
    27  }
    28  
    29  // Error represents an error returned from the Azure CLI.
    30  type Error struct {
    31  	exec.ExitError
    32  }
    33  
    34  // Error implements the error interface.
    35  func (e *Error) Error() string {
    36  	if len(e.Stderr) == 0 {
    37  		return e.ExitError.Error()
    38  	}
    39  	n := bytes.IndexByte(e.Stderr, '\n')
    40  	if n < 0 {
    41  		return string(e.Stderr)
    42  	}
    43  	return string(e.Stderr[:n])
    44  }
    45  
    46  // exec runs the given command using Exec if specified, or
    47  // os.exec.Command.
    48  func (a AzureCLI) exec(cmd string, args []string) ([]byte, error) {
    49  	var out []byte
    50  	var err error
    51  	if a.Exec != nil {
    52  		out, err = a.Exec(cmd, args)
    53  	} else {
    54  		out, err = exec.Command(cmd, args...).Output()
    55  	}
    56  	if exitError, ok := errors.Cause(err).(*exec.ExitError); ok {
    57  		err = &Error{
    58  			ExitError: *exitError,
    59  		}
    60  	}
    61  	return out, err
    62  }
    63  
    64  // run attempts to execute "az" with the given arguments. Unmarshalling
    65  // the json output into v.
    66  func (a AzureCLI) run(v interface{}, args ...string) error {
    67  	args = append(args, "-o", "json")
    68  	logger.Debugf("running az %s", strings.Join(args, " "))
    69  	b, err := a.exec("az", args)
    70  	if err != nil {
    71  		return errors.Annotate(err, "execution failure")
    72  	}
    73  	if err := json.Unmarshal(b, v); err != nil {
    74  		return errors.Annotate(err, "cannot unmarshal output")
    75  	}
    76  	return nil
    77  }
    78  
    79  // AccessToken contains the result of the GetAccessToken function.
    80  type AccessToken struct {
    81  	AccessToken  string `json:"accessToken"`
    82  	ExpiresOn    string `json:"expiresOn"`
    83  	Subscription string `json:"subscription"`
    84  	Tenant       string `json:"tenant"`
    85  	TokenType    string `json:"tokenType"`
    86  }
    87  
    88  // Token creates an adal.Token from the AccessToken. This token can be
    89  // used with go-autorest to access azure endpoints.
    90  func (t AccessToken) Token() *adal.Token {
    91  	return &adal.Token{
    92  		AccessToken: t.AccessToken,
    93  		Type:        t.TokenType,
    94  	}
    95  }
    96  
    97  // GetAccessToken gets an access token from the Azure CLI to access the
    98  // given resource using the given subscription. Either subscription or
    99  // resource may be empty in which case the default from the az
   100  // application are used.
   101  func (a AzureCLI) GetAccessToken(subscription, resource string) (*AccessToken, error) {
   102  	cmd := []string{"account", "get-access-token"}
   103  	if subscription != "" {
   104  		cmd = append(cmd, "--subscription", subscription)
   105  	}
   106  	if resource != "" {
   107  		cmd = append(cmd, "--resource", resource)
   108  	}
   109  	var tok AccessToken
   110  	if err := a.run(&tok, cmd...); err != nil {
   111  		return nil, errors.Trace(err)
   112  	}
   113  	return &tok, nil
   114  }
   115  
   116  // Account contains details of an azure account (subscription).
   117  type Account struct {
   118  	CloudName string `json:"cloudName"`
   119  	ID        string `json:"id"`
   120  	IsDefault bool   `json:"isDefault"`
   121  	Name      string `json:"name"`
   122  	State     string `json:"state"`
   123  	TenantId  string `json:"tenantId"`
   124  }
   125  
   126  // showAccount is a version of Account, but that can handle the subtle
   127  // difference in output from az account show.
   128  type showAccount struct {
   129  	Account
   130  	EnvironmentName string `json:"environmentName"`
   131  }
   132  
   133  // ShowAccount returns the account details for the account with the given
   134  // subscription ID. If the subscription is empty then the default Azure
   135  // CLI account is returned.
   136  func (a AzureCLI) ShowAccount(subscription string) (*Account, error) {
   137  	cmd := []string{"account", "show"}
   138  	if subscription != "" {
   139  		cmd = append(cmd, "--subscription", subscription)
   140  	}
   141  	var acc showAccount
   142  	if err := a.run(&acc, cmd...); err != nil {
   143  		return nil, errors.Trace(err)
   144  	}
   145  	if acc.Account.CloudName == "" {
   146  		acc.Account.CloudName = acc.EnvironmentName
   147  	}
   148  	return &acc.Account, nil
   149  }
   150  
   151  // ListAccounts returns the details for all accounts available in the
   152  // Azure CLI.
   153  func (a AzureCLI) ListAccounts() ([]Account, error) {
   154  	var accounts []Account
   155  	if err := a.run(&accounts, "account", "list"); err != nil {
   156  		return nil, errors.Trace(err)
   157  	}
   158  	return accounts, nil
   159  }
   160  
   161  // FindAccountsWithCloudName returns the details for all accounts with
   162  // the given cloud name..
   163  func (a AzureCLI) FindAccountsWithCloudName(name string) ([]Account, error) {
   164  	var accounts []Account
   165  	cmd := []string{
   166  		"account",
   167  		"list",
   168  		"--query", fmt.Sprintf("[?cloudName=='%s']", name),
   169  	}
   170  	if err := a.run(&accounts, cmd...); err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	return accounts, nil
   174  }
   175  
   176  // Cloud contains details of a cloud configured in the Azure CLI.
   177  type Cloud struct {
   178  	Endpoints CloudEndpoints `json:"endpoints"`
   179  	IsActive  bool           `json:"isActive"`
   180  	Name      string         `json:"name"`
   181  	Profile   string         `json:"profile"`
   182  	Suffixes  CloudSuffixes  `json:"suffixes"`
   183  }
   184  
   185  // CloudEndpoints contains the endpoints used by a cloud.
   186  type CloudEndpoints struct {
   187  	ActiveDirectory                string `json:"activeDirectory"`
   188  	ActiveDirectoryGraphResourceID string `json:"activeDirectoryGraphResourceId"`
   189  	ActiveDirectoryResourceID      string `json:"activeDirectoryResourceId"`
   190  	BatchResourceID                string `json:"batchResourceId"`
   191  	Management                     string `json:"management"`
   192  	ResourceManager                string `json:"resourceManager"`
   193  	SQLManagement                  string `json:"sqlManagement"`
   194  }
   195  
   196  // CloudSuffixes contains the suffixes used with a cloud.
   197  type CloudSuffixes struct {
   198  	AzureDatalakeAnalyticsCatalogAndJobEndpoint string `json:"azureDatalakeAnalyticsCatalogAndJobEndpoint"`
   199  	AzureDatalakeStoreFileSystemEndpoint        string `json:"azureDatalakeStoreFileSystemEndpoint"`
   200  	KeyvaultDNS                                 string `json:"keyvaultDns"`
   201  	SQLServerHostname                           string `json:"sqlServerHostname"`
   202  	StorageEndpoint                             string `json:"storageEndpoint"`
   203  }
   204  
   205  // ShowCloud returns the details of the cloud with the given name. If the
   206  // name is empty then the details of the default cloud will be returned.
   207  func (a AzureCLI) ShowCloud(name string) (*Cloud, error) {
   208  	cmd := []string{"cloud", "show"}
   209  	if name != "" {
   210  		cmd = append(cmd, "--name", name)
   211  	}
   212  	var cloud Cloud
   213  	if err := a.run(&cloud, cmd...); err != nil {
   214  		return nil, err
   215  	}
   216  	return &cloud, nil
   217  }
   218  
   219  // FindCloudsWithResourceManagerEndpoint returns a list of clouds which
   220  // use the given url for it's resource manager endpoint.
   221  func (a AzureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]Cloud, error) {
   222  	var clouds []Cloud
   223  	cmd := []string{
   224  		"cloud",
   225  		"list",
   226  		"--query",
   227  		fmt.Sprintf("[?endpoints.resourceManager=='%s']", url),
   228  	}
   229  	if err := a.run(&clouds, cmd...); err != nil {
   230  		return nil, err
   231  	}
   232  	return clouds, nil
   233  }
   234  
   235  // ListClouds returns the details for all clouds available in the Azure
   236  // CLI.
   237  func (a AzureCLI) ListClouds() ([]Cloud, error) {
   238  	var clouds []Cloud
   239  	if err := a.run(&clouds, "cloud", "list"); err != nil {
   240  		return nil, errors.Trace(err)
   241  	}
   242  	return clouds, nil
   243  }