github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/credentialcommon/modelcredential.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package credentialcommon
     5  
     6  import (
     7  	"github.com/juju/collections/set"
     8  	"github.com/juju/errors"
     9  	"gopkg.in/juju/names.v2"
    10  
    11  	"github.com/juju/juju/apiserver/common"
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/caas"
    14  	"github.com/juju/juju/cloud"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/state"
    18  )
    19  
    20  // ValidateExistingModelCredential checks if the cloud credential that a given model uses is valid for it.
    21  func ValidateExistingModelCredential(backend PersistentBackend, callCtx context.ProviderCallContext) (params.ErrorResults, error) {
    22  	model, err := backend.Model()
    23  	if err != nil {
    24  		return params.ErrorResults{}, errors.Trace(err)
    25  	}
    26  
    27  	credentialTag, isSet := model.CloudCredential()
    28  	if !isSet {
    29  		return params.ErrorResults{}, nil
    30  	}
    31  
    32  	storedCredential, err := backend.CloudCredential(credentialTag)
    33  	if err != nil {
    34  		return params.ErrorResults{}, errors.Trace(err)
    35  	}
    36  
    37  	if !storedCredential.IsValid() {
    38  		return params.ErrorResults{}, errors.NotValidf("credential %q", storedCredential.Name)
    39  	}
    40  	credential := cloud.NewCredential(cloud.AuthType(storedCredential.AuthType), storedCredential.Attributes)
    41  	return ValidateNewModelCredential(backend, callCtx, credentialTag, &credential)
    42  }
    43  
    44  // ValidateNewModelCredential checks if a new cloud credential could be valid for a given model.
    45  func ValidateNewModelCredential(backend PersistentBackend, callCtx context.ProviderCallContext, credentialTag names.CloudCredentialTag, credential *cloud.Credential) (params.ErrorResults, error) {
    46  	openParams, err := buildOpenParams(backend, credentialTag, credential)
    47  	if err != nil {
    48  		return params.ErrorResults{}, errors.Trace(err)
    49  	}
    50  	model, err := backend.Model()
    51  	if err != nil {
    52  		return params.ErrorResults{}, errors.Trace(err)
    53  	}
    54  	switch model.Type() {
    55  	case state.ModelTypeCAAS:
    56  		return checkCAASModelCredential(openParams)
    57  	case state.ModelTypeIAAS:
    58  		return checkIAASModelCredential(openParams, backend, callCtx)
    59  	default:
    60  		return params.ErrorResults{}, errors.NotSupportedf("model type %q", model.Type())
    61  	}
    62  }
    63  
    64  func checkCAASModelCredential(brokerParams environs.OpenParams) (params.ErrorResults, error) {
    65  	broker, err := newCAASBroker(brokerParams)
    66  	if err != nil {
    67  		return params.ErrorResults{}, errors.Trace(err)
    68  	}
    69  	_, err = broker.Namespaces()
    70  	if err != nil {
    71  		// If this call could not be made with provided credential, we know that the credential is invalid.
    72  		return params.ErrorResults{}, errors.Trace(err)
    73  	}
    74  	return params.ErrorResults{}, nil
    75  }
    76  
    77  func checkIAASModelCredential(openParams environs.OpenParams, backend PersistentBackend, callCtx context.ProviderCallContext) (params.ErrorResults, error) {
    78  	env, err := newEnv(openParams)
    79  	if err != nil {
    80  		return params.ErrorResults{}, errors.Trace(err)
    81  	}
    82  	// We only check persisted machines vs known cloud instances.
    83  	// In the future, this check may be extended to other cloud resources,
    84  	// entities and operation-level authorisations such as interfaces,
    85  	// ability to CRUD storage, etc.
    86  	return checkMachineInstances(backend, env, callCtx)
    87  }
    88  
    89  // checkMachineInstances compares model machines from state with
    90  // the ones reported by the provider using supplied credential.
    91  // This only makes sense for non-k8s providers.
    92  func checkMachineInstances(backend PersistentBackend, provider CloudProvider, callCtx context.ProviderCallContext) (params.ErrorResults, error) {
    93  	fail := func(original error) (params.ErrorResults, error) {
    94  		return params.ErrorResults{}, original
    95  	}
    96  
    97  	// Get machines from state
    98  	machines, err := backend.AllMachines()
    99  	if err != nil {
   100  		return fail(errors.Trace(err))
   101  	}
   102  
   103  	var results []params.ErrorResult
   104  
   105  	serverError := func(received error) params.ErrorResult {
   106  		return params.ErrorResult{Error: common.ServerError(received)}
   107  	}
   108  
   109  	machinesByInstance := make(map[string]string)
   110  	for _, machine := range machines {
   111  		if machine.IsContainer() {
   112  			// Containers don't correspond to instances at the
   113  			// provider level.
   114  			continue
   115  		}
   116  		if manual, err := machine.IsManual(); err != nil {
   117  			return fail(errors.Trace(err))
   118  		} else if manual {
   119  			continue
   120  		}
   121  		instanceId, err := machine.InstanceId()
   122  		if errors.IsNotProvisioned(err) {
   123  			// Skip over this machine; we wouldn't expect the cloud
   124  			// to know about it.
   125  			continue
   126  		} else if err != nil {
   127  			results = append(results, serverError(errors.Annotatef(err, "getting instance id for machine %s", machine.Id())))
   128  			continue
   129  		}
   130  		machinesByInstance[string(instanceId)] = machine.Id()
   131  	}
   132  
   133  	// Check can see all machines' instances
   134  	instances, err := provider.AllInstances(callCtx)
   135  	if err != nil {
   136  		return fail(errors.Trace(err))
   137  	}
   138  
   139  	instanceIds := set.NewStrings()
   140  	for _, instance := range instances {
   141  		id := string(instance.Id())
   142  		instanceIds.Add(id)
   143  		if _, found := machinesByInstance[id]; !found {
   144  			results = append(results, serverError(errors.Errorf("no machine with instance %q", id)))
   145  		}
   146  	}
   147  
   148  	for instanceId, name := range machinesByInstance {
   149  		if !instanceIds.Contains(instanceId) {
   150  			results = append(results, serverError(errors.Errorf("couldn't find instance %q for machine %s", instanceId, name)))
   151  		}
   152  	}
   153  
   154  	return params.ErrorResults{Results: results}, nil
   155  }
   156  
   157  var (
   158  	newEnv        = environs.New
   159  	newCAASBroker = caas.New
   160  )
   161  
   162  func buildOpenParams(backend PersistentBackend, credentialTag names.CloudCredentialTag, credential *cloud.Credential) (environs.OpenParams, error) {
   163  	fail := func(original error) (environs.OpenParams, error) {
   164  		return environs.OpenParams{}, original
   165  	}
   166  
   167  	model, err := backend.Model()
   168  	if err != nil {
   169  		return fail(errors.Trace(err))
   170  	}
   171  
   172  	modelCloud, err := backend.Cloud(model.Cloud())
   173  	if err != nil {
   174  		return fail(errors.Trace(err))
   175  	}
   176  
   177  	err = model.ValidateCloudCredential(credentialTag, *credential)
   178  	if err != nil {
   179  		return fail(errors.Trace(err))
   180  	}
   181  
   182  	tempCloudSpec, err := environs.MakeCloudSpec(modelCloud, model.CloudRegion(), credential)
   183  	if err != nil {
   184  		return fail(errors.Trace(err))
   185  	}
   186  
   187  	cfg, err := model.Config()
   188  	if err != nil {
   189  		return fail(errors.Trace(err))
   190  	}
   191  	return environs.OpenParams{
   192  		Cloud:  tempCloudSpec,
   193  		Config: cfg,
   194  	}, nil
   195  }