@@ -26,11 +26,13 @@ import (
26
26
"fmt"
27
27
"time"
28
28
29
+ "github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/projects"
29
30
krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/serviceusage/v1beta1"
30
31
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
31
32
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
32
33
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
33
34
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
35
+ kccpredicate "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/predicate"
34
36
35
37
gcp "google.golang.org/api/serviceusage/v1beta1"
36
38
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -39,8 +41,38 @@ import (
39
41
"sigs.k8s.io/controller-runtime/pkg/client"
40
42
)
41
43
44
+ // GoogleApisServiceAgent was historically used by multiple services, but for more granular permission control we moved to P4SAs instead.
45
+ // MIGs were tricky to move though, and so instead of switching to a P4SA, they adopted the GoogleApisServiceAgent as their P4SA
46
+ // We still want to support this though, so we recognize this "magic value"
47
+ const GoogleApisServiceAgent = "cloudservices.gserviceaccount.com"
48
+
42
49
func init () {
43
- registry .RegisterModel (krm .ServiceIdentityGVK , NewServiceIdentityModel )
50
+ reconcileGate := & serviceIdentityReconcileGate {}
51
+ registry .RegisterModelWithReconcileGate (krm .ServiceIdentityGVK , NewServiceIdentityModel , reconcileGate )
52
+ }
53
+
54
+ // serviceIdentityReconcileGate opts in some reconciliation to direct.
55
+ // Specifically if the service is the "magic" cloudservices.gserviceaccount.com P4SA,
56
+ // we use direct - it is not supported by legacy reconcilers.
57
+ type serviceIdentityReconcileGate struct {
58
+ optIn kccpredicate.OptInToDirectReconciliation
59
+ }
60
+
61
+ var _ kccpredicate.ReconcileGate = & serviceIdentityReconcileGate {}
62
+
63
+ func (r * serviceIdentityReconcileGate ) ShouldReconcile (o * unstructured.Unstructured ) bool {
64
+ if r .optIn .ShouldReconcile (o ) {
65
+ return true
66
+ }
67
+ obj := & krm.ServiceIdentity {}
68
+ if err := runtime .DefaultUnstructuredConverter .FromUnstructured (o .Object , & obj ); err != nil {
69
+ return false
70
+ }
71
+ serviceName := direct .ValueOf (obj .Spec .ResourceID )
72
+ if serviceName == "" {
73
+ serviceName = obj .GetName ()
74
+ }
75
+ return serviceName == GoogleApisServiceAgent
44
76
}
45
77
46
78
func NewServiceIdentityModel (ctx context.Context , config * config.ControllerConfig ) (directbase.Model , error ) {
@@ -74,9 +106,10 @@ func (m *serviceIdentityModel) AdapterForObject(ctx context.Context, reader clie
74
106
return nil , err
75
107
}
76
108
return & serviceIdentityAdapter {
77
- gcpBeta : gcpBeta ,
78
- id : id .(* krm.ServiceIdentityIdentity ),
79
- desired : obj ,
109
+ gcpBeta : gcpBeta ,
110
+ id : id .(* krm.ServiceIdentityIdentity ),
111
+ desired : obj ,
112
+ projectMapper : m .config .ProjectMapper ,
80
113
}, nil
81
114
}
82
115
@@ -86,7 +119,9 @@ func (m *serviceIdentityModel) AdapterForURL(ctx context.Context, url string) (d
86
119
}
87
120
88
121
type serviceIdentityAdapter struct {
89
- gcpBeta * gcp.APIService
122
+ projectMapper * projects.ProjectMapper
123
+ gcpBeta * gcp.APIService
124
+
90
125
id * krm.ServiceIdentityIdentity
91
126
desired * krm.ServiceIdentity
92
127
actual * gcp.ServiceIdentity // This will be populated from status or after generation
@@ -122,55 +157,64 @@ func (a *serviceIdentityAdapter) Create(ctx context.Context, createOp *directbas
122
157
fqn := a .id .String ()
123
158
log .V (2 ).Info ("generating service identity" , "fqn" , fqn )
124
159
125
- // - parent: Name of the consumer and service to generate an identity for. The
126
- // `GenerateServiceIdentity` methods currently support projects, folders,
127
- // organizations. Example parents would be:
128
- // `projects/123/services/example.googleapis.com`
129
- // `folders/123/services/example.googleapis.com`
130
- // `organizations/123/services/example.googleapis.com`.
131
-
132
- op , err := a .gcpBeta .Services .GenerateServiceIdentity (fqn ).Context (ctx ).Do ()
133
- if err != nil {
134
- return fmt .Errorf ("generating service identity for %q: %w" , fqn , err )
135
- }
160
+ status := & krm.ServiceIdentityStatus {}
161
+ if a .id .Service == GoogleApisServiceAgent {
162
+ // Special case: the MIG "P4SA" (which is also the "Google APIs Service Agent")
163
+ // Format is always <projectNumber>@cloudservices.gserviceaccount.com
136
164
137
- var serviceIdentity * gcp.GoogleApiServiceusageV1beta1ServiceIdentity
138
- for {
139
- if op .Done {
140
- klog .Warningf ("RESPONSE IS %v" , string (op .Response ))
141
- klog .Warningf ("FULL OPERATION IS %v" , op )
142
- identity := & gcp.GoogleApiServiceusageV1beta1ServiceIdentity {}
143
- if err := json .Unmarshal (op .Response , identity ); err != nil {
144
- return fmt .Errorf ("error parsing response: %w" , err )
145
- }
146
- serviceIdentity = identity
147
- break
148
- }
149
- time .Sleep (2 * time .Second )
150
- if err := ctx .Err (); err != nil {
151
- return err
165
+ projectNumber , err := a .projectMapper .LookupProjectNumber (ctx , a .id .ParentID .ProjectID )
166
+ if err != nil {
167
+ return fmt .Errorf ("looking up project number for %q: %w" , a .id .ParentID .ProjectID , err )
152
168
}
153
- op , err = a .gcpBeta .Operations .Get (op .Name ).Context (ctx ).Do ()
169
+ email := fmt .
Sprintf (
"%[email protected] " ,
projectNumber )
170
+ status .Email = & email
171
+ } else {
172
+ // - parent: Name of the consumer and service to generate an identity for. The
173
+ // `GenerateServiceIdentity` methods currently support projects, folders,
174
+ // organizations. Example parents would be:
175
+ // `projects/123/services/example.googleapis.com`
176
+ // `folders/123/services/example.googleapis.com`
177
+ // `organizations/123/services/example.googleapis.com`.
178
+
179
+ op , err := a .gcpBeta .Services .GenerateServiceIdentity (fqn ).Context (ctx ).Do ()
154
180
if err != nil {
155
- return fmt .Errorf ("waiting for service identity generation for %q: %w" , fqn , err )
181
+ return fmt .Errorf ("generating service identity for %q: %w" , fqn , err )
156
182
}
157
- }
158
183
159
- log .V (2 ).Info ("successfully generated service identity" , "fqn" , fqn , "identity.email" , serviceIdentity .Email , "identity.uniqueId" , serviceIdentity .UniqueId )
184
+ var serviceIdentity * gcp.GoogleApiServiceusageV1beta1ServiceIdentity
185
+ for {
186
+ if op .Done {
187
+ identity := & gcp.GoogleApiServiceusageV1beta1ServiceIdentity {}
188
+ if err := json .Unmarshal (op .Response , identity ); err != nil {
189
+ return fmt .Errorf ("error parsing response: %w" , err )
190
+ }
191
+ serviceIdentity = identity
192
+ break
193
+ }
194
+ time .Sleep (2 * time .Second )
195
+ if err := ctx .Err (); err != nil {
196
+ return err
197
+ }
198
+ op , err = a .gcpBeta .Operations .Get (op .Name ).Context (ctx ).Do ()
199
+ if err != nil {
200
+ return fmt .Errorf ("waiting for service identity generation for %q: %w" , fqn , err )
201
+ }
202
+ }
160
203
161
- // It really doesn't seem worthwhile to use the mapper here
204
+ log . V ( 2 ). Info ( "successfully generated service identity" , "fqn" , fqn , "identity.email" , serviceIdentity . Email , "identity.uniqueId" , serviceIdentity . UniqueId )
162
205
163
- status := & krm. ServiceIdentityStatus {}
206
+ // It really doesn't seem worthwhile to use the mapper here
164
207
165
- // observedState := krm.ServiceIdentityObservedState{
166
- // Email: direct.ValueOf(serviceIdentity.Email),
167
- // UniqueID: direct.ValueOf(serviceIdentity.UniqueId),
168
- // }
208
+ // observedState := krm.ServiceIdentityObservedState{
209
+ // Email: direct.ValueOf(serviceIdentity.Email),
210
+ // UniqueID: direct.ValueOf(serviceIdentity.UniqueId),
211
+ // }
169
212
170
- // status.ObservedState = &observedState
213
+ // status.ObservedState = &observedState
171
214
172
- status .Email = direct .LazyPtr (serviceIdentity .Email )
173
- // status.UniqueID = direct.LazyPtr(serviceIdentity.UniqueId)
215
+ status .Email = direct .LazyPtr (serviceIdentity .Email )
216
+ // status.UniqueID = direct.LazyPtr(serviceIdentity.UniqueId)
217
+ }
174
218
175
219
// status.ExternalRef = direct.LazyPtr(parent)
176
220
return createOp .UpdateStatus (ctx , status , nil )
0 commit comments