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 = ¶ms.CloudCredential{ 160 AuthType: string(spec.Credential.AuthType()), 161 Attributes: spec.Credential.Attributes(), 162 } 163 } 164 result.Result = ¶ms.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 }