@@ -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,35 @@ 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 := & SecretReconcileGate {}
51
+ registry .RegisterModelWithReconcileGate (krm .ServiceIdentityGVK , NewServiceIdentityModel , reconcileGate )
52
+ }
53
+
54
+ type SecretReconcileGate struct {
55
+ optIn kccpredicate.OptInToDirectReconciliation
56
+ }
57
+
58
+ var _ kccpredicate.ReconcileGate = & SecretReconcileGate {}
59
+
60
+ func (r * SecretReconcileGate ) ShouldReconcile (o * unstructured.Unstructured ) bool {
61
+ if r .optIn .ShouldReconcile (o ) {
62
+ return true
63
+ }
64
+ obj := & krm.ServiceIdentity {}
65
+ if err := runtime .DefaultUnstructuredConverter .FromUnstructured (o .Object , & obj ); err != nil {
66
+ return false
67
+ }
68
+ serviceName := direct .ValueOf (obj .Spec .ResourceID )
69
+ if serviceName == "" {
70
+ serviceName = obj .GetName ()
71
+ }
72
+ return serviceName == GoogleApisServiceAgent
44
73
}
45
74
46
75
func NewServiceIdentityModel (ctx context.Context , config * config.ControllerConfig ) (directbase.Model , error ) {
@@ -74,9 +103,10 @@ func (m *serviceIdentityModel) AdapterForObject(ctx context.Context, reader clie
74
103
return nil , err
75
104
}
76
105
return & serviceIdentityAdapter {
77
- gcpBeta : gcpBeta ,
78
- id : id .(* krm.ServiceIdentityIdentity ),
79
- desired : obj ,
106
+ gcpBeta : gcpBeta ,
107
+ id : id .(* krm.ServiceIdentityIdentity ),
108
+ desired : obj ,
109
+ projectMapper : m .config .ProjectMapper ,
80
110
}, nil
81
111
}
82
112
@@ -86,7 +116,9 @@ func (m *serviceIdentityModel) AdapterForURL(ctx context.Context, url string) (d
86
116
}
87
117
88
118
type serviceIdentityAdapter struct {
89
- gcpBeta * gcp.APIService
119
+ projectMapper * projects.ProjectMapper
120
+ gcpBeta * gcp.APIService
121
+
90
122
id * krm.ServiceIdentityIdentity
91
123
desired * krm.ServiceIdentity
92
124
actual * gcp.ServiceIdentity // This will be populated from status or after generation
@@ -122,55 +154,66 @@ func (a *serviceIdentityAdapter) Create(ctx context.Context, createOp *directbas
122
154
fqn := a .id .String ()
123
155
log .V (2 ).Info ("generating service identity" , "fqn" , fqn )
124
156
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
- }
157
+ status := & krm.ServiceIdentityStatus {}
158
+ if a .id .Service == GoogleApisServiceAgent {
159
+ // Special case: the MIG "P4SA" (which is also the "Google APIs Service Agent")
160
+ // Format is always <projectNumber>@cloudservices.gserviceaccount.com
136
161
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
162
+ projectNumber , err := a .projectMapper .LookupProjectNumber (ctx , a .id .ParentID .ProjectID )
163
+ if err != nil {
164
+ return fmt .Errorf ("looking up project number for %q: %w" , a .id .ParentID .ProjectID , err )
152
165
}
153
- op , err = a .gcpBeta .Operations .Get (op .Name ).Context (ctx ).Do ()
166
+ email := fmt .
Sprintf (
"%[email protected] " ,
projectNumber )
167
+ status .Email = & email
168
+ } else {
169
+ // - parent: Name of the consumer and service to generate an identity for. The
170
+ // `GenerateServiceIdentity` methods currently support projects, folders,
171
+ // organizations. Example parents would be:
172
+ // `projects/123/services/example.googleapis.com`
173
+ // `folders/123/services/example.googleapis.com`
174
+ // `organizations/123/services/example.googleapis.com`.
175
+
176
+ op , err := a .gcpBeta .Services .GenerateServiceIdentity (fqn ).Context (ctx ).Do ()
154
177
if err != nil {
155
- return fmt .Errorf ("waiting for service identity generation for %q: %w" , fqn , err )
178
+ return fmt .Errorf ("generating service identity for %q: %w" , fqn , err )
156
179
}
157
- }
158
180
159
- log .V (2 ).Info ("successfully generated service identity" , "fqn" , fqn , "identity.email" , serviceIdentity .Email , "identity.uniqueId" , serviceIdentity .UniqueId )
181
+ var serviceIdentity * gcp.GoogleApiServiceusageV1beta1ServiceIdentity
182
+ for {
183
+ if op .Done {
184
+ klog .Warningf ("RESPONSE IS %v" , string (op .Response ))
185
+ klog .Warningf ("FULL OPERATION IS %v" , op )
186
+ identity := & gcp.GoogleApiServiceusageV1beta1ServiceIdentity {}
187
+ if err := json .Unmarshal (op .Response , identity ); err != nil {
188
+ return fmt .Errorf ("error parsing response: %w" , err )
189
+ }
190
+ serviceIdentity = identity
191
+ break
192
+ }
193
+ time .Sleep (2 * time .Second )
194
+ if err := ctx .Err (); err != nil {
195
+ return err
196
+ }
197
+ op , err = a .gcpBeta .Operations .Get (op .Name ).Context (ctx ).Do ()
198
+ if err != nil {
199
+ return fmt .Errorf ("waiting for service identity generation for %q: %w" , fqn , err )
200
+ }
201
+ }
160
202
161
- // It really doesn't seem worthwhile to use the mapper here
203
+ log . V ( 2 ). Info ( "successfully generated service identity" , "fqn" , fqn , "identity.email" , serviceIdentity . Email , "identity.uniqueId" , serviceIdentity . UniqueId )
162
204
163
- status := & krm. ServiceIdentityStatus {}
205
+ // It really doesn't seem worthwhile to use the mapper here
164
206
165
- // observedState := krm.ServiceIdentityObservedState{
166
- // Email: direct.ValueOf(serviceIdentity.Email),
167
- // UniqueID: direct.ValueOf(serviceIdentity.UniqueId),
168
- // }
207
+ // observedState := krm.ServiceIdentityObservedState{
208
+ // Email: direct.ValueOf(serviceIdentity.Email),
209
+ // UniqueID: direct.ValueOf(serviceIdentity.UniqueId),
210
+ // }
169
211
170
- // status.ObservedState = &observedState
212
+ // status.ObservedState = &observedState
171
213
172
- status .Email = direct .LazyPtr (serviceIdentity .Email )
173
- // status.UniqueID = direct.LazyPtr(serviceIdentity.UniqueId)
214
+ status .Email = direct .LazyPtr (serviceIdentity .Email )
215
+ // status.UniqueID = direct.LazyPtr(serviceIdentity.UniqueId)
216
+ }
174
217
175
218
// status.ExternalRef = direct.LazyPtr(parent)
176
219
return createOp .UpdateStatus (ctx , status , nil )
0 commit comments