-
Notifications
You must be signed in to change notification settings - Fork 0
/
organization.go
362 lines (325 loc) · 13.5 KB
/
organization.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// Copyright 2022 API7.ai, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cloud
import (
"context"
"encoding/json"
"path"
"time"
)
const (
// RoleScopeOrganization indicates an organization scoped role.
RoleScopeOrganization = "organization"
// RoleScopeCluster indicates a cluster scoped role.
RoleScopeCluster = "cluster"
// MemberStatePending means the member is still in pending state.
MemberStatePending = "Pending"
// MemberStateActive means the member is active.
MemberStateActive = "Active"
)
// Organization is the specification of an API7 Cloud organization.
type Organization struct {
// ID is the unique identify to mark an object.
ID ID `json:"id,inline" yaml:"id"`
// Name is the object name.
Name string `json:"name" yaml:"name"`
// CreatedAt is the object creation time.
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
// UpdatedAt is the last modified time of this object.
UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
// PlanID indicates which plan is used by this organization.
// PlanID should refer to a valid Plan object.
PlanID ID `json:"plan_id" yaml:"plan_id"`
// PlanExpireTime indicates the binding plan expire time for this organization.
PlanExpireTime time.Time `json:"plan_expire_time" yaml:"plan_expire_time"`
// SubscriptionStartedAt is the time when the organization subscribed to the plan.
SubscriptionStartedAt *time.Time `json:"subscription_started_at" yaml:"subscription_started_at"`
// OwnerID indicates who create the organization.
OwnerID string `json:"owner_id" yaml:"owner_id"`
}
// Member is the member of organization.
// It contains the member specification and some management fields.
type Member struct {
MemberSpec `json:"-"`
// ID is the unique identify to mark an object.
ID ID `json:"id,omitempty,inline" yaml:"id"`
// CreatedAt is the object creation time.
CreatedAt time.Time `json:"created_at,omitempty" yaml:"created_at"`
// UpdatedAt is the last modified time of this object.
UpdatedAt time.Time `json:"updated_at,omitempty" yaml:"updated_at"`
// OrgId indicates the organization where the member is in.
OrgId ID `json:"org_id,omitempty,inline" yaml:"org_id"`
// Status is the user data status.
Status EntityStatus `json:"status,omitempty" yaml:"status"`
}
// MemberSpec contains the information
type MemberSpec struct {
// FirstName is the member first name
FirstName string `json:"first_name,omitempty" yaml:"first_name"`
// LastName is the member last name
LastName string `json:"last_name,omitempty" yaml:"last_name"`
// Roles indicates the roles of the member.
Roles []Role `json:"roles"`
// Email is the email address of the member.
Email string `json:"email"`
// UserId refers to a user, since a 3rd party User Management
// Service might be used so the type is not uint64.
UserId string `json:"user_id,omitempty" yaml:"user_id"`
// State is the user state. Optional values can be:
// * MemberStatePending
// * MemberStateActive
State string `json:"state" yaml:"state"`
}
// Methods means the operations that can be performed on an organization.
type Methods struct {
// Get is a get method
Get bool `json:"get,omitempty"`
// Put is a put method
Put bool `json:"put,omitempty"`
// Post is a post method
Post bool `json:"post,omitempty"`
// Patch is a patch method
Patch bool `json:"patch,omitempty"`
// Delete is a delete method
Delete bool `json:"delete,omitempty"`
}
// Permissions means the permissions that can be performed on an organization.
type Permissions struct {
// Organization is the organization scope of permission
Organization map[string]Methods `json:"organization"`
// Cluster is the cluster scope of permission
Cluster map[string]Methods `json:"cluster"`
// Billing is the billing scope of permission
Billing map[string]Methods `json:"billing"`
// APIManagement is the API management scope of permission
APIManagement map[string]Methods `json:"api_management"`
}
// Role is the role of a member.
type Role struct {
// ID is the id of role
ID ID `json:"id" gorm:"primaryKey"`
// Name is the name of role
Name string `json:"name" gorm:"column:name"`
// OrgID is the id of organization
OrgID ID `json:"org_id" gorm:"column:org_id"`
// Owner is the owner of role
Owner bool `json:"owner" gorm:"column:owner"`
// Permissions is the permissions of role
// Key means the name of resource
// Value means the permissions of resource
Permissions Permissions `json:"permissions" gorm:"serializer:json;"`
// Scope is the scope of role. Optional values can be:
// * RoleScopeOrganization
// * RoleScopeCluster
Scope string `json:"scope" gorm:"column:scope"`
CreatedAt time.Time `json:"-" yaml:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"-" yaml:"updated_at" gorm:"autoUpdateTime"`
}
// RoleBinding binds a role to an organization member.
type RoleBinding struct {
// RoleID is the id of role
RoleID ID `json:"role_id"`
// ClusterID is the id of cluster
// This field is used only if the role is not
// organization scoped.
ClusterID ID `json:"cluster_id"`
}
// OrganizationInterface is the interface for manipulating Organization and Member.
type OrganizationInterface interface {
// GetOrganization gets an existing API7 Cloud Organization.
// The given `orgID` parameter should specify the Organization that you want to get.
// Currently, the `opts` parameter doesn't matter, users can pass the `nil` value.
GetOrganization(ctx context.Context, orgID ID, opts *ResourceGetOptions) (*Organization, error)
// ListMembers returns an iterator for listing Members in the specified Organization with the
// given list conditions.
// Users need to specify the Organization, Paging in the `opts`.
ListMembers(ctx context.Context, opts *ResourceListOptions) (MemberListIterator, error)
// InviteMember invites a new member to the organization.
// The given `email` parameter should specify a correct mail address.
// The given `role` parameter should specify an appropriate member role.
// Users need to specify the Organization in the `opts`.
InviteMember(ctx context.Context, email string, role *Role, opts *ResourceCreateOptions) (*Member, error)
// ReInviteMember re-invites an existing member (which state is MemberStatePending) to the organization.
// The given `memberID` parameter should specify the existing member.
// Users need to specify the Organization in the `opts`.
ReInviteMember(ctx context.Context, memberID ID, opts *ResourceUpdateOptions) (*Member, error)
// RemoveMember removes an existing member from the organization.
// The given `memberID` parameter should specify the existing member.
// Users need to specify the Organization in the `opts`.
RemoveMember(ctx context.Context, memberID ID, opts *ResourceDeleteOptions) error
// GetMember gets an existing member from the organization.
// The given `memberID` parameter should specify the existing member.
// Users need to specify the Organization in the `opts`.
GetMember(ctx context.Context, memberID ID, opts *ResourceGetOptions) (*Member, error)
// UpdateMemberRoles updates the roles for the specified member.
// The given `memberID` parameter should specify the existing member.
// The given `roleBindings` parameter specifies new roles for this member.
// Users need to specify the Organization in the `opts`.
UpdateMemberRoles(ctx context.Context, memberID ID, roleBindings []RoleBinding, opts *ResourceUpdateOptions) error
// ListRoles returns an iterator for listing Roles in the specified Organization with the
// given list conditions.
// Users need to specify the Organization, Paging in the `opts`.
ListRoles(ctx context.Context, opts *ResourceListOptions) (RoleListIterator, error)
// TransferOwnership transfers the organization ownership from yourself to another member.
// The `toMember` parameter should specify the existing member in the same organization.
// Users need to specify the Organization, Paging in the `opts`.
// Note the operation will fail if you're not the owner of this organization.
// After the transferring, your role will be downgraded to organization admin.
TransferOwnership(ctx context.Context, toMember ID, opts *ResourceUpdateOptions) error
}
// MemberListIterator is an iterator for listing Members.
type MemberListIterator interface {
// Next returns the next Member according to the filter conditions.
Next() (*Member, error)
}
// RoleListIterator is an iterator for listing Roles.
type RoleListIterator interface {
// Next returns the next Role according to the filter conditions.
Next() (*Role, error)
}
type organizationImpl struct {
client httpClient
}
type memberListIterator struct {
iter listIterator
}
func (iter *memberListIterator) Next() (*Member, error) {
var member Member
rawData, err := iter.iter.Next()
if err != nil {
return nil, err
}
if rawData == nil {
return nil, nil
}
if err = json.Unmarshal(rawData, &member); err != nil {
return nil, err
}
return &member, nil
}
type roleListIterator struct {
iter listIterator
}
func (iter *roleListIterator) Next() (*Role, error) {
var role Role
rawData, err := iter.iter.Next()
if err != nil {
return nil, err
}
if rawData == nil {
return nil, nil
}
if err = json.Unmarshal(rawData, &role); err != nil {
return nil, err
}
return &role, nil
}
func newOrganization(cli httpClient) OrganizationInterface {
return &organizationImpl{
client: cli,
}
}
func (impl *organizationImpl) GetOrganization(ctx context.Context, orgID ID, _ *ResourceGetOptions) (*Organization, error) {
var org Organization
uri := path.Join(_apiPathPrefix, "orgs", orgID.String())
err := impl.client.sendGetRequest(ctx, uri, "", jsonPayloadDecodeFactory(&org), appendHeader())
if err != nil {
return nil, err
}
return &org, nil
}
func (impl *organizationImpl) ListMembers(ctx context.Context, opts *ResourceListOptions) (MemberListIterator, error) {
iter := listIterator{
ctx: ctx,
resource: "member",
client: impl.client,
path: path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members"),
paging: mergePagination(opts.Pagination),
headers: appendHeader(mapClusterIdFromOpts(opts)),
}
return &memberListIterator{
iter: iter,
}, nil
}
func (impl *organizationImpl) ListRoles(ctx context.Context, opts *ResourceListOptions) (RoleListIterator, error) {
iter := listIterator{
ctx: ctx,
resource: "role",
client: impl.client,
path: path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "roles"),
paging: mergePagination(opts.Pagination),
headers: appendHeader(mapClusterIdFromOpts(opts)),
}
return &roleListIterator{
iter: iter,
}, nil
}
func (impl *organizationImpl) InviteMember(ctx context.Context, email string, role *Role, opts *ResourceCreateOptions) (*Member, error) {
var member Member
body := struct {
Email string `json:"email"`
RoleID string `json:"role_id"`
}{
Email: email,
RoleID: role.ID.String(),
}
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members")
err := impl.client.sendPostRequest(ctx, uri, "", body, jsonPayloadDecodeFactory(&member), appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return nil, err
}
return &member, nil
}
func (impl *organizationImpl) ReInviteMember(ctx context.Context, memberID ID, opts *ResourceUpdateOptions) (*Member, error) {
var member Member
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members", memberID.String(), "re_invite")
err := impl.client.sendPutRequest(ctx, uri, "", nil, jsonPayloadDecodeFactory(&member), appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return nil, err
}
return &member, nil
}
func (impl *organizationImpl) RemoveMember(ctx context.Context, memberID ID, opts *ResourceDeleteOptions) error {
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members", memberID.String())
err := impl.client.sendDeleteRequest(ctx, uri, "", nil, appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return err
}
return nil
}
func (impl *organizationImpl) GetMember(ctx context.Context, memberID ID, opts *ResourceGetOptions) (*Member, error) {
var member Member
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members", memberID.String())
err := impl.client.sendGetRequest(ctx, uri, "", jsonPayloadDecodeFactory(&member), appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return nil, err
}
return &member, nil
}
func (impl *organizationImpl) TransferOwnership(ctx context.Context, targetMemberID ID, opts *ResourceUpdateOptions) error {
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members", targetMemberID.String(), "transfer_ownership")
err := impl.client.sendPostRequest(ctx, uri, "", nil, nil, appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return err
}
return nil
}
func (impl *organizationImpl) UpdateMemberRoles(ctx context.Context, memberID ID, roleBindings []RoleBinding, opts *ResourceUpdateOptions) error {
uri := path.Join(_apiPathPrefix, "orgs", opts.Organization.ID.String(), "members", memberID.String())
err := impl.client.sendPutRequest(ctx, uri, "", roleBindings, nil, appendHeader(mapClusterIdFromOpts(opts)))
if err != nil {
return err
}
return nil
}