Skip to content

Commit 3ce2de7

Browse files
authored
Merge pull request #1189 from apricote/application-credential-support
✨ Support Application Credential auth
2 parents 6ba04de + d94969e commit 3ce2de7

File tree

12 files changed

+71
-98
lines changed

12 files changed

+71
-98
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ policy may be made to more closely aligned with other providers in the Cluster A
7575

7676
**NOTE:** The minimum microversion of CAPI using nova is `2.53` now due to `server tags` support, see [code](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/c052e7e600f0e5ebddc839c08746bb636e79be87/pkg/cloud/services/compute/service.go#L38) for additional information.
7777

78+
**NOTE:** We require Keystone v3 for authentication.
79+
7880
------
7981

8082
## Development versions

controllers/openstackcluster_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ func (r *OpenStackClusterReconciler) Reconcile(ctx context.Context, req ctrl.Req
107107
}
108108
}()
109109

110-
osProviderClient, clientOpts, err := provider.NewClientFromCluster(ctx, r.Client, openStackCluster)
110+
osProviderClient, clientOpts, projectID, err := provider.NewClientFromCluster(ctx, r.Client, openStackCluster)
111111
if err != nil {
112112
return reconcile.Result{}, err
113113
}
114114

115115
scope := &scope.Scope{
116116
ProviderClient: osProviderClient,
117117
ProviderClientOpts: clientOpts,
118+
ProjectID: projectID,
118119
Logger: log,
119120
}
120121

controllers/openstackmachine_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,15 @@ func (r *OpenStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Req
140140
}
141141
}()
142142

143-
osProviderClient, clientOpts, err := provider.NewClientFromMachine(ctx, r.Client, openStackMachine)
143+
osProviderClient, clientOpts, projectID, err := provider.NewClientFromMachine(ctx, r.Client, openStackMachine)
144144
if err != nil {
145145
return reconcile.Result{}, err
146146
}
147147

148148
scope := &scope.Scope{
149149
ProviderClient: osProviderClient,
150150
ProviderClientOpts: clientOpts,
151+
ProjectID: projectID,
151152
Logger: log,
152153
}
153154

pkg/cloud/services/compute/instance.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func (s *Service) getVolumeByName(name string) (*volumes.Volume, error) {
290290
listOpts := volumes.ListOpts{
291291
AllTenants: false,
292292
Name: name,
293-
TenantID: s.projectID,
293+
TenantID: s.scope.ProjectID,
294294
}
295295
volumeList, err := s.computeService.ListVolumes(listOpts)
296296
if err != nil {

pkg/cloud/services/compute/instance_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,9 +494,9 @@ func TestService_getImageID(t *testing.T) {
494494
tt.expect(mockComputeClient.EXPECT())
495495

496496
s := Service{
497-
projectID: "",
498497
scope: &scope.Scope{
499-
Logger: logr.Discard(),
498+
ProjectID: "",
499+
Logger: logr.Discard(),
500500
},
501501
computeService: mockComputeClient,
502502
networkingService: &networking.Service{},
@@ -997,9 +997,9 @@ func TestService_ReconcileInstance(t *testing.T) {
997997
tt.expect(computeRecorder, networkRecorder)
998998

999999
s := Service{
1000-
projectID: "",
10011000
scope: &scope.Scope{
1002-
Logger: logr.Discard(),
1001+
Logger: logr.Discard(),
1002+
ProjectID: "",
10031003
},
10041004
computeService: mockComputeClient,
10051005
networkingService: networking.NewTestService(
@@ -1108,9 +1108,9 @@ func TestService_DeleteInstance(t *testing.T) {
11081108
tt.expect(computeRecorder, networkRecorder)
11091109

11101110
s := Service{
1111-
projectID: "",
11121111
scope: &scope.Scope{
1113-
Logger: logr.Discard(),
1112+
ProjectID: "",
1113+
Logger: logr.Discard(),
11141114
},
11151115
computeService: mockComputeClient,
11161116
networkingService: networking.NewTestService(

pkg/cloud/services/compute/service.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ import (
2323
"github.com/gophercloud/gophercloud/openstack"
2424

2525
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking"
26-
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/provider"
2726
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
2827
)
2928

3029
type Service struct {
31-
projectID string
3230
scope *scope.Scope
3331
computeService Client
3432
networkingService *networking.Service
@@ -75,18 +73,12 @@ func NewService(scope *scope.Scope) (*Service, error) {
7573
return nil, fmt.Errorf("authInfo must be set")
7674
}
7775

78-
projectID, err := provider.GetProjectID(scope.ProviderClient, scope.ProviderClientOpts)
79-
if err != nil {
80-
return nil, fmt.Errorf("error retrieveing project id: %v", err)
81-
}
82-
8376
networkingService, err := networking.NewService(scope)
8477
if err != nil {
8578
return nil, fmt.Errorf("failed to create networking service: %v", err)
8679
}
8780

8881
return &Service{
89-
projectID: projectID,
9082
scope: scope,
9183
computeService: computeService,
9284
networkingService: networkingService,

pkg/cloud/services/networking/securitygroups.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ func (s *Service) GetSecurityGroups(securityGroupParams []infrav1.SecurityGroupP
404404

405405
listOpts := groups.ListOpts(sg.Filter)
406406
if listOpts.ProjectID == "" {
407-
listOpts.ProjectID = s.projectID
407+
listOpts.ProjectID = s.scope.ProjectID
408408
}
409409
listOpts.Name = sg.Name
410410
listOpts.ID = sg.UUID

pkg/cloud/services/networking/service.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags"
2727
"k8s.io/apimachinery/pkg/runtime"
2828

29-
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/provider"
3029
"sigs.k8s.io/cluster-api-provider-openstack/pkg/record"
3130
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
3231
)
@@ -40,9 +39,8 @@ const (
4039
// Service interfaces with the OpenStack Networking API.
4140
// It will create a network related infrastructure for the cluster, like network, subnet, router, security groups.
4241
type Service struct {
43-
projectID string
44-
scope *scope.Scope
45-
client NetworkClient
42+
scope *scope.Scope
43+
client NetworkClient
4644
}
4745

4846
// NewService returns an instance of the networking service.
@@ -58,24 +56,18 @@ func NewService(scope *scope.Scope) (*Service, error) {
5856
return nil, fmt.Errorf("failed to get project id: authInfo must be set")
5957
}
6058

61-
projectID, err := provider.GetProjectID(scope.ProviderClient, scope.ProviderClientOpts)
62-
if err != nil {
63-
return nil, fmt.Errorf("error retrieveing project id: %v", err)
64-
}
65-
6659
return &Service{
67-
projectID: projectID,
68-
scope: scope,
69-
client: networkClient{serviceClient},
60+
scope: scope,
61+
client: networkClient{serviceClient},
7062
}, nil
7163
}
7264

7365
// NewTestService returns a Service with no initialisation. It should only be used by tests.
7466
func NewTestService(projectID string, client NetworkClient, logger logr.Logger) *Service {
7567
return &Service{
76-
projectID: projectID,
7768
scope: &scope.Scope{
78-
Logger: logger,
69+
ProjectID: projectID,
70+
Logger: logger,
7971
},
8072
client: client,
8173
}

pkg/cloud/services/provider/provider.go

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/gophercloud/gophercloud"
2727
"github.com/gophercloud/gophercloud/openstack"
28+
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
2829
osclient "github.com/gophercloud/utils/client"
2930
"github.com/gophercloud/utils/openstack/clientconfig"
3031
corev1 "k8s.io/api/core/v1"
@@ -34,43 +35,42 @@ import (
3435
"sigs.k8s.io/yaml"
3536

3637
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
37-
"sigs.k8s.io/cluster-api-provider-openstack/pkg/metrics"
3838
)
3939

4040
const (
4141
cloudsSecretKey = "clouds.yaml"
4242
caSecretKey = "cacert"
4343
)
4444

45-
func NewClientFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, error) {
45+
func NewClientFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
4646
var cloud clientconfig.Cloud
4747
var caCert []byte
4848

4949
if openStackMachine.Spec.IdentityRef != nil {
5050
var err error
5151
cloud, caCert, err = getCloudFromSecret(ctx, ctrlClient, openStackMachine.Namespace, openStackMachine.Spec.IdentityRef.Name, openStackMachine.Spec.CloudName)
5252
if err != nil {
53-
return nil, nil, err
53+
return nil, nil, "", err
5454
}
5555
}
5656
return NewClient(cloud, caCert)
5757
}
5858

59-
func NewClientFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, error) {
59+
func NewClientFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
6060
var cloud clientconfig.Cloud
6161
var caCert []byte
6262

6363
if openStackCluster.Spec.IdentityRef != nil {
6464
var err error
6565
cloud, caCert, err = getCloudFromSecret(ctx, ctrlClient, openStackCluster.Namespace, openStackCluster.Spec.IdentityRef.Name, openStackCluster.Spec.CloudName)
6666
if err != nil {
67-
return nil, nil, err
67+
return nil, nil, "", err
6868
}
6969
}
7070
return NewClient(cloud, caCert)
7171
}
7272

73-
func NewClient(cloud clientconfig.Cloud, caCert []byte) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, error) {
73+
func NewClient(cloud clientconfig.Cloud, caCert []byte) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
7474
clientOpts := new(clientconfig.ClientOpts)
7575
if cloud.AuthInfo != nil {
7676
clientOpts.AuthInfo = cloud.AuthInfo
@@ -80,13 +80,13 @@ func NewClient(cloud clientconfig.Cloud, caCert []byte) (*gophercloud.ProviderCl
8080

8181
opts, err := clientconfig.AuthOptions(clientOpts)
8282
if err != nil {
83-
return nil, nil, fmt.Errorf("auth option failed for cloud %v: %v", cloud.Cloud, err)
83+
return nil, nil, "", fmt.Errorf("auth option failed for cloud %v: %v", cloud.Cloud, err)
8484
}
8585
opts.AllowReauth = true
8686

8787
provider, err := openstack.NewClient(opts.IdentityEndpoint)
8888
if err != nil {
89-
return nil, nil, fmt.Errorf("create providerClient err: %v", err)
89+
return nil, nil, "", fmt.Errorf("create providerClient err: %v", err)
9090
}
9191

9292
config := &tls.Config{
@@ -109,9 +109,15 @@ func NewClient(cloud clientconfig.Cloud, caCert []byte) (*gophercloud.ProviderCl
109109
}
110110
err = openstack.Authenticate(provider, *opts)
111111
if err != nil {
112-
return nil, nil, fmt.Errorf("providerClient authentication err: %v", err)
112+
return nil, nil, "", fmt.Errorf("providerClient authentication err: %v", err)
113113
}
114-
return provider, clientOpts, nil
114+
115+
projectID, err := getProjectIDFromAuthResult(provider.GetAuthResult())
116+
if err != nil {
117+
return nil, nil, "", err
118+
}
119+
120+
return provider, clientOpts, projectID, nil
115121
}
116122

117123
type defaultLogger struct{}
@@ -161,42 +167,20 @@ func getCloudFromSecret(ctx context.Context, ctrlClient client.Client, secretNam
161167
return clouds.Clouds[cloudName], caCert, nil
162168
}
163169

164-
type project struct {
165-
ID string `json:"id"`
166-
Name string
167-
}
168-
169-
type projects struct {
170-
Projects []project `json:"projects"`
171-
}
172-
173-
func GetProjectID(client *gophercloud.ProviderClient, clientOpts *clientconfig.ClientOpts) (string, error) {
174-
if clientOpts.AuthInfo.ProjectID != "" {
175-
return clientOpts.AuthInfo.ProjectID, nil
176-
}
177-
178-
projectName := clientOpts.AuthInfo.ProjectName
179-
if projectName == "" {
180-
return "", fmt.Errorf("failed to get project id")
181-
}
182-
183-
c, err := openstack.NewIdentityV3(client, gophercloud.EndpointOpts{})
184-
if err != nil {
185-
return "", fmt.Errorf("failed to create identity service client: %v", err)
186-
}
170+
// getProjectIDFromAuthResult handles different auth mechanisms to retrieve the
171+
// current project id. Usually we use the Identity v3 Token mechanism that
172+
// returns the project id in the response to the initial auth request.
173+
func getProjectIDFromAuthResult(authResult gophercloud.AuthResult) (string, error) {
174+
switch authResult := authResult.(type) {
175+
case tokens.CreateResult:
176+
project, err := authResult.ExtractProject()
177+
if err != nil {
178+
return "", fmt.Errorf("unable to extract project from CreateResult: %v", err)
179+
}
187180

188-
jsonResp := projects{}
189-
mc := metrics.NewMetricPrometheusContext("project", "get")
190-
resp, err := c.Get(c.ServiceURL("auth", "projects"), &jsonResp, &gophercloud.RequestOpts{OkCodes: []int{200}})
191-
if mc.ObserveRequest(err) != nil {
192-
return "", err
193-
}
194-
defer resp.Body.Close()
181+
return project.ID, nil
195182

196-
for _, project := range jsonResp.Projects {
197-
if project.Name == projectName {
198-
return project.ID, nil
199-
}
183+
default:
184+
return "", fmt.Errorf("unable to get the project id from auth response with type %T", authResult)
200185
}
201-
return "", fmt.Errorf("project %s not found", projectName)
202186
}

pkg/scope/scope.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
type Scope struct {
3333
ProviderClient *gophercloud.ProviderClient
3434
ProviderClientOpts *clientconfig.ClientOpts
35+
ProjectID string
3536

3637
Logger logr.Logger
3738
}

0 commit comments

Comments
 (0)