-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1939 from chirino/multi-org-owners
apiserver: support multiple Organization owners
- Loading branch information
Showing
28 changed files
with
492 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package datatype | ||
|
||
import ( | ||
"context" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"github.com/lib/pq" | ||
"gorm.io/gorm" | ||
"gorm.io/gorm/clause" | ||
"gorm.io/gorm/schema" | ||
) | ||
|
||
type StringArray []string | ||
|
||
// GormDataType gorm common data type | ||
func (StringArray) GormDataType() string { | ||
return "string_array" | ||
} | ||
|
||
// GormDBDataType gorm db data type | ||
func (StringArray) GormDBDataType(db *gorm.DB, field *schema.Field) string { | ||
switch db.Dialector.Name() { | ||
case "postgres": | ||
return "text[]" | ||
default: | ||
return "JSON" | ||
} | ||
} | ||
|
||
// Value return json value, implement driver.Valuer interface | ||
func (j StringArray) Value() (driver.Value, error) { | ||
return json.Marshal(j) | ||
} | ||
|
||
func (j StringArray) GormValue(ctx context.Context, db *gorm.DB) clause.Expr { | ||
switch db.Dialector.Name() { | ||
case "postgres": | ||
return gorm.Expr("?", pq.StringArray(j)) | ||
default: | ||
data, err := json.Marshal(j) | ||
if err != nil { | ||
db.Error = err | ||
} | ||
return gorm.Expr("?", string(data)) | ||
} | ||
} | ||
|
||
// implements sql.Scanner interface | ||
func (j *StringArray) Scan(value interface{}) error { | ||
var bytes []byte | ||
switch v := value.(type) { | ||
case []byte: | ||
bytes = v | ||
case string: | ||
bytes = []byte(v) | ||
default: | ||
return errors.New(fmt.Sprint("Failed to unmarshal string array value:", value)) | ||
} | ||
|
||
if len(bytes) == 0 { | ||
*j = nil | ||
return nil | ||
} | ||
if bytes[0] == '[' { | ||
err := json.Unmarshal(bytes, j) | ||
return err | ||
} | ||
if bytes[0] == '{' { | ||
// Unmarshal as Postgres text array | ||
var a pq.StringArray | ||
err := a.Scan(value) | ||
if err != nil { | ||
return err | ||
} | ||
*j = StringArray(a) | ||
return nil | ||
} | ||
return errors.New(fmt.Sprint("Failed to unmarshal string array value:", value)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package migration_20240221_0000 | ||
|
||
import ( | ||
"github.com/google/uuid" | ||
"github.com/lib/pq" | ||
"github.com/nexodus-io/nexodus/internal/database/datatype" | ||
. "github.com/nexodus-io/nexodus/internal/database/migrations" | ||
"github.com/nexodus-io/nexodus/internal/models" | ||
"gorm.io/gorm" | ||
) | ||
|
||
type UserOrganization struct { | ||
Roles datatype.StringArray `json:"roles" swaggertype:"array,string"` | ||
} | ||
type Invitation struct { | ||
Roles datatype.StringArray `json:"roles" swaggertype:"array,string"` | ||
} | ||
|
||
func init() { | ||
migrationId := "20240221-0000" | ||
CreateMigrationFromActions(migrationId, | ||
AddTableColumnsAction(&UserOrganization{}), | ||
AddTableColumnsAction(&Invitation{}), | ||
ExecActionIf( | ||
`CREATE INDEX IF NOT EXISTS "idx_user_organizations_roles" ON "user_organizations" USING GIN ("roles")`, | ||
`DROP INDEX IF EXISTS idx_user_organizations_roles`, | ||
NotOnSqlLite, | ||
), | ||
|
||
// While inspecting the DB I realized a bunch of id columns are not uuids... this should fix that | ||
ChangeColumnTypeActionIf(`devices`, `owner_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`devices`, `vpc_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`devices`, `organization_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`devices`, `security_group_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`devices`, `reg_key_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`organizations`, `owner_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`reg_keys`, `owner_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`reg_keys`, `vpc_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`reg_keys`, `organization_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`reg_keys`, `device_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`reg_keys`, `security_group_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`security_groups`, `organization_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`security_groups`, `vpc_id`, `text`, `uuid`, NotOnSqlLite), | ||
ChangeColumnTypeActionIf(`vpcs`, `organization_id`, `text`, `uuid`, NotOnSqlLite), | ||
|
||
func(tx *gorm.DB, apply bool) error { | ||
if apply && NotOnSqlLite(tx) { | ||
|
||
// this will fill in the role for the user_organizations table | ||
type UserOrganization struct { | ||
UserID uuid.UUID `json:"user_id" gorm:"type:uuid;primary_key"` | ||
OrganizationID uuid.UUID `json:"organization_id" gorm:"type:uuid;primary_key"` | ||
Roles pq.StringArray `json:"roles" gorm:"type:text[]" swaggertype:"array,string"` | ||
} | ||
|
||
result := tx.Model(&models.UserOrganization{}). | ||
Where("roles IS NULL"). | ||
Update("roles", pq.StringArray{"member"}) | ||
if result.Error != nil { | ||
return result.Error | ||
} | ||
|
||
type Organization struct { | ||
ID uuid.UUID | ||
OwnerID uuid.UUID | ||
} | ||
rows := []Organization{} | ||
|
||
// make all sure all orgs have a member with the owner role | ||
sql := `SELECT DISTINCT id, owner_id FROM organizations LEFT JOIN user_organizations ON user_organizations.organization_id = organizations.id WHERE organization_id is NULL` | ||
result = tx.Raw(sql).FindInBatches(&rows, 100, func(tx *gorm.DB, batch int) error { | ||
for _, r := range rows { | ||
result := tx.Create(&UserOrganization{ | ||
UserID: r.OwnerID, | ||
OrganizationID: r.ID, | ||
Roles: []string{"owner"}, | ||
}) | ||
if result.Error != nil { | ||
return result.Error | ||
} | ||
} | ||
return nil | ||
}) | ||
if result.Error != nil { | ||
return result.Error | ||
} | ||
|
||
// make sure the owner's memberhip has the owner role | ||
sql = `select * FROM organizations LEFT JOIN user_organizations ON user_organizations.organization_id = organizations.id WHERE organizations.owner_id = user_organizations.user_id AND roles <> '{owner}'` | ||
result = tx.Raw(sql).FindInBatches(&rows, 100, func(tx *gorm.DB, batch int) error { | ||
for _, r := range rows { | ||
result := tx.Save(&UserOrganization{ | ||
UserID: r.OwnerID, | ||
OrganizationID: r.ID, | ||
Roles: []string{"owner"}, | ||
}) | ||
if result.Error != nil { | ||
return result.Error | ||
} | ||
} | ||
return nil | ||
}) | ||
if result.Error != nil { | ||
return result.Error | ||
} | ||
|
||
} | ||
return nil | ||
}, | ||
) | ||
} |
Oops, something went wrong.