Skip to content

Commit

Permalink
add default cloud functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
kian99 committed Aug 26, 2024
1 parent db18dbf commit aa71655
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 166 deletions.
22 changes: 22 additions & 0 deletions internal/jimm/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ func (j *JIMM) GetCloud(ctx context.Context, user *openfga.User, tag names.Cloud
}
}

// DefaultCloud returns the default cloud to use for user requests.
// If the user has access to 0 or more than 1 cloud an error will be returned.
func (j *JIMM) DefaultCloud(ctx context.Context, user *openfga.User) (names.CloudTag, error) {
const op = errors.Op("jimm.DefaultCloud")
var userClouds []*dbmodel.Cloud
err := j.ForEachUserCloud(ctx, user, func(c *dbmodel.Cloud) error {
userClouds = append(userClouds, c)
return nil
})
if err != nil {
return names.CloudTag{}, errors.E(op, err)
}
switch len(userClouds) {
case 0:
return names.CloudTag{}, errors.E(op, "no clouds available")
case 1:
return userClouds[0].ResourceTag(), nil
default:
return names.CloudTag{}, errors.E(op, "multiple clouds available; please specify one")
}
}

// ForEachUserCloud iterates through all of the clouds a user has access to
// calling the given function for each cloud. If the user has admin level
// access to the cloud then the provided cloud will include all user
Expand Down
58 changes: 58 additions & 0 deletions internal/jimm/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,64 @@ func TestGetCloud(t *testing.T) {
})
}

func TestDefaultCloud(t *testing.T) {
c := qt.New(t)

client, _, _, err := jimmtest.SetupTestOFGAClient(c.Name())
c.Assert(err, qt.IsNil)

ctx := context.Background()
now := time.Now().UTC().Round(time.Millisecond)
j := &jimm.JIMM{
UUID: uuid.NewString(),
OpenFGAClient: client,
Database: db.Database{
DB: jimmtest.PostgresDB(c, func() time.Time { return now }),
},
}

err = j.Database.Migrate(ctx, false)
c.Assert(err, qt.IsNil)

aliceIdentity, err := dbmodel.NewIdentity("[email protected]")
c.Assert(err, qt.IsNil)
alice := openfga.NewUser(
aliceIdentity,
client,
)

cloud := &dbmodel.Cloud{
Name: "test-cloud-1",
}
err = j.Database.AddCloud(ctx, cloud)
c.Assert(err, qt.IsNil)

cloud2 := &dbmodel.Cloud{
Name: "test-cloud-2",
}
err = j.Database.AddCloud(ctx, cloud2)
c.Assert(err, qt.IsNil)

// Test access to 0 clouds.
_, err = j.DefaultCloud(ctx, alice)
c.Assert(err, qt.ErrorMatches, "no clouds available")

// Test access to 1 cloud.
err = alice.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.AdministratorRelation)
c.Assert(err, qt.IsNil)

defaultCloud, err := j.DefaultCloud(ctx, alice)
c.Assert(err, qt.IsNil)
c.Assert(defaultCloud.String(), qt.Equals, "cloud-test-cloud-1")

// Test access to more than 1 cloud.
err = alice.SetCloudAccess(context.Background(), cloud2.ResourceTag(), ofganames.AdministratorRelation)
c.Assert(err, qt.IsNil)

_, err = j.DefaultCloud(ctx, alice)
c.Assert(err, qt.ErrorMatches, "multiple clouds available; please specify one")
}

func TestForEachCloud(t *testing.T) {
c := qt.New(t)

Expand Down
36 changes: 27 additions & 9 deletions internal/jimm/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,21 @@ type ModelCreateArgs struct {
CloudCredential names.CloudCredentialTag
}

type ModelCreateDefaults struct {
DefaultCloud func() (names.CloudTag, error)
}

// FromJujuModelCreateArgs converts jujuparams.ModelCreateArgs into AddModelArgs.
func (a *ModelCreateArgs) FromJujuModelCreateArgs(args *jujuparams.ModelCreateArgs) error {
func (a *ModelCreateArgs) FromJujuModelCreateArgs(args *jujuparams.ModelCreateArgs, opts ModelCreateDefaults) error {
if args.Name == "" {
return errors.E("name not specified")
}
a.Name = args.Name
a.Config = args.Config
a.CloudRegion = args.CloudRegion
if args.CloudTag == "" {
return errors.E("no cloud specified for model; please specify one")
}
ct, err := names.ParseCloudTag(args.CloudTag)
if err != nil {
return errors.E(err, errors.CodeBadRequest)
if err := a.parseCloud(args.CloudTag, opts.DefaultCloud); err != nil {
return err
}
a.Cloud = ct

if args.OwnerTag == "" {
return errors.E("owner tag not specified")
Expand All @@ -87,6 +86,25 @@ func (a *ModelCreateArgs) FromJujuModelCreateArgs(args *jujuparams.ModelCreateAr
return nil
}

func (a *ModelCreateArgs) parseCloud(cloudTag string, defaultCloudTag func() (names.CloudTag, error)) error {
var err error
if cloudTag != "" {
a.Cloud, err = names.ParseCloudTag(cloudTag)
if err != nil {
return errors.E(err, errors.CodeBadRequest)
}
return nil
}
if defaultCloudTag != nil {
a.Cloud, err = defaultCloudTag()
if err != nil {
return err
}
return nil
}
return errors.E("no cloud specified for model; please specify one")
}

func newModelBuilder(ctx context.Context, j *JIMM) *modelBuilder {
return &modelBuilder{
ctx: ctx,
Expand Down Expand Up @@ -299,7 +317,7 @@ func (b *modelBuilder) CreateDatabaseModel() *modelBuilder {
if b.credential == nil {
// try to select a valid credential
if err := b.selectCloudCredentials(); err != nil {
b.err = errors.E(err, "could not select cloud credentials")
b.err = errors.E(fmt.Sprintf("could not select cloud credentials: %s", err))
return b
}
}
Expand Down
27 changes: 23 additions & 4 deletions internal/jimm/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func TestModelCreateArgs(t *testing.T) {
tests := []struct {
about string
args jujuparams.ModelCreateArgs
defaults jimm.ModelCreateDefaults
expectedArgs jimm.ModelCreateArgs
expectedError string
}{{
Expand Down Expand Up @@ -105,13 +106,30 @@ func TestModelCreateArgs(t *testing.T) {
},
expectedError: "owner tag not specified",
}, {
about: "cloud tag not specified",
about: "cloud tag not specified and no default configured",
args: jujuparams.ModelCreateArgs{
Name: "test-model",
OwnerTag: names.NewUserTag("[email protected]").String(),
CloudCredentialTag: names.NewCloudCredentialTag("test-cloud/alice/test-credential-1").String(),
},
expectedError: "no cloud specified for model; please specify one",
}, {
about: "cloud tag not specified but default is available",
args: jujuparams.ModelCreateArgs{
Name: "test-model",
OwnerTag: names.NewUserTag("[email protected]").String(),
CloudCredentialTag: names.NewCloudCredentialTag("test-cloud/alice/test-credential-1").String(),
},
defaults: jimm.ModelCreateDefaults{
DefaultCloud: func() (names.CloudTag, error) {
return names.NewCloudTag("test-cloud"), nil
}},
expectedArgs: jimm.ModelCreateArgs{
Name: "test-model",
Owner: names.NewUserTag("[email protected]"),
Cloud: names.NewCloudTag("test-cloud"),
CloudCredential: names.NewCloudCredentialTag("test-cloud/alice/test-credential-1"),
},
}}

opts := []cmp.Option{
Expand All @@ -128,7 +146,7 @@ func TestModelCreateArgs(t *testing.T) {
for _, test := range tests {
c.Run(test.about, func(c *qt.C) {
var a jimm.ModelCreateArgs
err := a.FromJujuModelCreateArgs(&test.args)
err := a.FromJujuModelCreateArgs(&test.args, test.defaults)
if test.expectedError == "" {
c.Assert(err, qt.IsNil)
c.Assert(a, qt.CmpEquals(opts...), test.expectedArgs)
Expand Down Expand Up @@ -924,7 +942,8 @@ func TestAddModel(t *testing.T) {
user.JimmAdmin = test.jimmAdmin

args := jimm.ModelCreateArgs{}
err = args.FromJujuModelCreateArgs(&test.args)
defaults := jimm.ModelCreateDefaults{}
err = args.FromJujuModelCreateArgs(&test.args, defaults)
c.Assert(err, qt.IsNil)

_, err = j.AddModel(context.Background(), user, &args)
Expand Down Expand Up @@ -3626,7 +3645,7 @@ controllers:
CloudTag: names.NewCloudTag("test-cloud").String(),
CloudRegion: "test-region-1",
CloudCredentialTag: names.NewCloudCredentialTag("test-cloud/[email protected]/test-credential-1").String(),
})
}, jimm.ModelCreateDefaults{})
c.Assert(err, qt.IsNil)

// According to controller priority for test-region-1, we would
Expand Down
Loading

0 comments on commit aa71655

Please sign in to comment.