github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/cloudspec/cloudspec.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudspec
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names/v5"
     9  
    10  	"github.com/juju/juju/apiserver/common"
    11  	apiservererrors "github.com/juju/juju/apiserver/errors"
    12  	"github.com/juju/juju/apiserver/facade"
    13  	k8scloud "github.com/juju/juju/caas/kubernetes/cloud"
    14  	k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
    15  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    16  	"github.com/juju/juju/rpc/params"
    17  	"github.com/juju/juju/state"
    18  	"github.com/juju/juju/state/watcher"
    19  )
    20  
    21  // CloudSpecer defines the CloudSpec api interface
    22  type CloudSpecer interface {
    23  	// WatchCloudSpecsChanges returns a watcher for cloud spec changes.
    24  	WatchCloudSpecsChanges(args params.Entities) (params.NotifyWatchResults, error)
    25  
    26  	// CloudSpec returns the model's cloud spec.
    27  	CloudSpec(args params.Entities) (params.CloudSpecResults, error)
    28  
    29  	// GetCloudSpec constructs the CloudSpec for a validated and authorized model.
    30  	GetCloudSpec(tag names.ModelTag) params.CloudSpecResult
    31  }
    32  
    33  type CloudSpecAPI struct {
    34  	resources facade.Resources
    35  
    36  	getCloudSpec                           func(names.ModelTag) (environscloudspec.CloudSpec, error)
    37  	watchCloudSpec                         func(tag names.ModelTag) (state.NotifyWatcher, error)
    38  	watchCloudSpecModelCredentialReference func(tag names.ModelTag) (state.NotifyWatcher, error)
    39  	watchCloudSpecCredentialContent        func(tag names.ModelTag) (state.NotifyWatcher, error)
    40  	getAuthFunc                            common.GetAuthFunc
    41  }
    42  
    43  type CloudSpecAPIV2 struct {
    44  	CloudSpecAPI
    45  }
    46  
    47  type CloudSpecAPIV1 struct {
    48  	CloudSpecAPIV2
    49  }
    50  
    51  // NewCloudSpec returns a new CloudSpecAPI.
    52  func NewCloudSpec(
    53  	resources facade.Resources,
    54  	getCloudSpec func(names.ModelTag) (environscloudspec.CloudSpec, error),
    55  	watchCloudSpec func(tag names.ModelTag) (state.NotifyWatcher, error),
    56  	watchCloudSpecModelCredentialReference func(tag names.ModelTag) (state.NotifyWatcher, error),
    57  	watchCloudSpecCredentialContent func(tag names.ModelTag) (state.NotifyWatcher, error),
    58  	getAuthFunc common.GetAuthFunc,
    59  ) CloudSpecAPI {
    60  	return CloudSpecAPI{resources,
    61  		getCloudSpec,
    62  		watchCloudSpec,
    63  		watchCloudSpecModelCredentialReference,
    64  		watchCloudSpecCredentialContent,
    65  		getAuthFunc}
    66  }
    67  
    68  func NewCloudSpecV2(
    69  	resources facade.Resources,
    70  	getCloudSpec func(names.ModelTag) (environscloudspec.CloudSpec, error),
    71  	watchCloudSpec func(tag names.ModelTag) (state.NotifyWatcher, error),
    72  	watchCloudSpecModelCredentialReference func(tag names.ModelTag) (state.NotifyWatcher, error),
    73  	watchCloudSpecCredentialContent func(tag names.ModelTag) (state.NotifyWatcher, error),
    74  	getAuthFunc common.GetAuthFunc,
    75  ) CloudSpecAPIV2 {
    76  	api := NewCloudSpec(
    77  		resources,
    78  		getCloudSpec,
    79  		watchCloudSpec,
    80  		watchCloudSpecModelCredentialReference,
    81  		watchCloudSpecCredentialContent,
    82  		getAuthFunc,
    83  	)
    84  	return CloudSpecAPIV2{api}
    85  }
    86  
    87  func NewCloudSpecV1(
    88  	resources facade.Resources,
    89  	getCloudSpec func(names.ModelTag) (environscloudspec.CloudSpec, error),
    90  	watchCloudSpec func(tag names.ModelTag) (state.NotifyWatcher, error),
    91  	watchCloudSpecModelCredentialReference func(tag names.ModelTag) (state.NotifyWatcher, error),
    92  	watchCloudSpecCredentialContent func(tag names.ModelTag) (state.NotifyWatcher, error),
    93  	getAuthFunc common.GetAuthFunc,
    94  ) CloudSpecAPIV1 {
    95  	v2API := NewCloudSpecV2(
    96  		resources,
    97  		k8sCloudSpecChanger(getCloudSpec),
    98  		watchCloudSpec,
    99  		watchCloudSpecModelCredentialReference,
   100  		watchCloudSpecCredentialContent,
   101  		getAuthFunc,
   102  	)
   103  	return CloudSpecAPIV1{v2API}
   104  }
   105  
   106  func k8sCloudSpecChanger(
   107  	getCloudSpec func(names.ModelTag) (environscloudspec.CloudSpec, error),
   108  ) func(names.ModelTag) (environscloudspec.CloudSpec, error) {
   109  	return func(n names.ModelTag) (environscloudspec.CloudSpec, error) {
   110  		spec, err := getCloudSpec(n)
   111  		if err != nil {
   112  			return spec, err
   113  		}
   114  		if spec.Type == k8sconstants.CAASProviderType {
   115  			cred, err := k8scloud.CredentialToLegacy(spec.Credential)
   116  			if err != nil {
   117  				return spec, errors.Annotate(err, "transforming Kubernetes credential for pre 2.9")
   118  			}
   119  			spec.Credential = &cred
   120  		}
   121  		return spec, nil
   122  	}
   123  }
   124  
   125  // CloudSpec returns the model's cloud spec.
   126  func (s CloudSpecAPI) CloudSpec(args params.Entities) (params.CloudSpecResults, error) {
   127  	authFunc, err := s.getAuthFunc()
   128  	if err != nil {
   129  		return params.CloudSpecResults{}, err
   130  	}
   131  	results := params.CloudSpecResults{
   132  		Results: make([]params.CloudSpecResult, len(args.Entities)),
   133  	}
   134  	for i, arg := range args.Entities {
   135  		tag, err := names.ParseModelTag(arg.Tag)
   136  		if err != nil {
   137  			results.Results[i].Error = apiservererrors.ServerError(err)
   138  			continue
   139  		}
   140  		if !authFunc(tag) {
   141  			results.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   142  			continue
   143  		}
   144  		results.Results[i] = s.GetCloudSpec(tag)
   145  	}
   146  	return results, nil
   147  }
   148  
   149  // GetCloudSpec constructs the CloudSpec for a validated and authorized model.
   150  func (s CloudSpecAPI) GetCloudSpec(tag names.ModelTag) params.CloudSpecResult {
   151  	var result params.CloudSpecResult
   152  	spec, err := s.getCloudSpec(tag)
   153  	if err != nil {
   154  		result.Error = apiservererrors.ServerError(err)
   155  		return result
   156  	}
   157  	var paramsCloudCredential *params.CloudCredential
   158  	if spec.Credential != nil && spec.Credential.AuthType() != "" {
   159  		paramsCloudCredential = &params.CloudCredential{
   160  			AuthType:   string(spec.Credential.AuthType()),
   161  			Attributes: spec.Credential.Attributes(),
   162  		}
   163  	}
   164  	result.Result = &params.CloudSpec{
   165  		Type:              spec.Type,
   166  		Name:              spec.Name,
   167  		Region:            spec.Region,
   168  		Endpoint:          spec.Endpoint,
   169  		IdentityEndpoint:  spec.IdentityEndpoint,
   170  		StorageEndpoint:   spec.StorageEndpoint,
   171  		Credential:        paramsCloudCredential,
   172  		CACertificates:    spec.CACertificates,
   173  		SkipTLSVerify:     spec.SkipTLSVerify,
   174  		IsControllerCloud: spec.IsControllerCloud,
   175  	}
   176  	return result
   177  }
   178  
   179  // WatchCloudSpecsChanges returns a watcher for cloud spec changes.
   180  func (s CloudSpecAPI) WatchCloudSpecsChanges(args params.Entities) (params.NotifyWatchResults, error) {
   181  	authFunc, err := s.getAuthFunc()
   182  	if err != nil {
   183  		return params.NotifyWatchResults{}, err
   184  	}
   185  	results := params.NotifyWatchResults{
   186  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   187  	}
   188  	for i, arg := range args.Entities {
   189  		tag, err := names.ParseModelTag(arg.Tag)
   190  		if err != nil {
   191  			results.Results[i].Error = apiservererrors.ServerError(err)
   192  			continue
   193  		}
   194  		if !authFunc(tag) {
   195  			results.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   196  			continue
   197  		}
   198  		w, err := s.watchCloudSpecChanges(tag)
   199  		if err == nil {
   200  			results.Results[i] = w
   201  		} else {
   202  			results.Results[i].Error = apiservererrors.ServerError(err)
   203  		}
   204  	}
   205  	return results, nil
   206  }
   207  
   208  func (s CloudSpecAPI) watchCloudSpecChanges(tag names.ModelTag) (params.NotifyWatchResult, error) {
   209  	result := params.NotifyWatchResult{}
   210  	cloudWatch, err := s.watchCloudSpec(tag)
   211  	if err != nil {
   212  		return result, errors.Trace(err)
   213  	}
   214  	credentialReferenceWatch, err := s.watchCloudSpecModelCredentialReference(tag)
   215  	if err != nil {
   216  		return result, errors.Trace(err)
   217  	}
   218  
   219  	credentialContentWatch, err := s.watchCloudSpecCredentialContent(tag)
   220  	if err != nil {
   221  		return result, errors.Trace(err)
   222  	}
   223  	var watch *common.MultiNotifyWatcher
   224  	if credentialContentWatch != nil {
   225  		watch = common.NewMultiNotifyWatcher(cloudWatch, credentialReferenceWatch, credentialContentWatch)
   226  	} else {
   227  		// It's rare but possible that a model does not have a credential.
   228  		// In this case there is no point trying to 'watch' content changes.
   229  		watch = common.NewMultiNotifyWatcher(cloudWatch, credentialReferenceWatch)
   230  	}
   231  	// Consume the initial event. Technically, API
   232  	// calls to Watch 'transmit' the initial event
   233  	// in the Watch response. But NotifyWatchers
   234  	// have no state to transmit.
   235  	if _, ok := <-watch.Changes(); ok {
   236  		result.NotifyWatcherId = s.resources.Register(watch)
   237  	} else {
   238  		return result, watcher.EnsureErr(watch)
   239  	}
   240  	return result, nil
   241  }