Skip to content

Commit

Permalink
Merge pull request #513 from linode/proj/parent-child
Browse files Browse the repository at this point in the history
new: Add support for Parent/Child account switching
  • Loading branch information
jriddle-linode authored Jun 3, 2024
2 parents e7939e8 + d9e8d01 commit a86ec45
Show file tree
Hide file tree
Showing 23 changed files with 970 additions and 652 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ run_fixtures:
LINODE_API_VERSION="v4beta" \
LINODE_URL="$(LINODE_URL)" \
GO111MODULE="on" \
go test -timeout=$(TEST_TIMEOUT) -v $(ARGS)
go test --tags $(TEST_TAGS) -timeout=$(TEST_TIMEOUT) -v $(ARGS)

sanitize:
@echo "* Sanitizing fixtures"
Expand Down
31 changes: 30 additions & 1 deletion account.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package linodego

import "context"
import (
"context"
"encoding/json"
"time"

"github.com/linode/linodego/internal/parseabletime"
)

// Account associated with the token in use.
type Account struct {
Expand All @@ -20,6 +26,29 @@ type Account struct {
Phone string `json:"phone"`
CreditCard *CreditCard `json:"credit_card"`
EUUID string `json:"euuid"`
BillingSource string `json:"billing_source"`
Capabilities []string `json:"capabilities"`
ActiveSince *time.Time `json:"-"`
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (account *Account) UnmarshalJSON(b []byte) error {
type Mask Account

p := struct {
*Mask
ActiveSince *parseabletime.ParseableTime `json:"active_since"`
}{
Mask: (*Mask)(account),
}

if err := json.Unmarshal(b, &p); err != nil {
return err
}

account.ActiveSince = (*time.Time)(p.ActiveSince)

return nil
}

// CreditCard information associated with the Account.
Expand Down
44 changes: 44 additions & 0 deletions account_child.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package linodego

import (
"context"
)

// ChildAccount represents an account under the current account.
// NOTE: This is an alias to prevent any future breaking changes.
type ChildAccount = Account

// ChildAccountToken represents a short-lived token created using
// the CreateChildAccountToken(...) function.
// NOTE: This is an alias to prevent any future breaking changes.
type ChildAccountToken = Token

// ListChildAccounts lists child accounts under the current account.
func (c *Client) ListChildAccounts(ctx context.Context, opts *ListOptions) ([]ChildAccount, error) {
return getPaginatedResults[ChildAccount](
ctx,
c,
"account/child-accounts",
opts,
)
}

// GetChildAccount gets a single child accounts under the current account.
func (c *Client) GetChildAccount(ctx context.Context, euuid string) (*ChildAccount, error) {
return doGETRequest[ChildAccount](
ctx,
c,
formatAPIPath("account/child-accounts/%s", euuid),
)
}

// CreateChildAccountToken creates a short-lived token that can be used to
// access the Linode API under a child account.
// The attributes of this token are not currently configurable.
func (c *Client) CreateChildAccountToken(ctx context.Context, euuid string) (*ChildAccountToken, error) {
return doPOSTRequest[ChildAccountToken, any](
ctx,
c,
formatAPIPath("account/child-accounts/%s/token", euuid),
)
}
10 changes: 10 additions & 0 deletions account_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,20 @@ import (
"github.com/linode/linodego/internal/parseabletime"
)

type UserType string

const (
UserTypeProxy UserType = "proxy"
UserTypeParent UserType = "parent"
UserTypeChild UserType = "child"
UserTypeDefault UserType = "default"
)

// User represents a User object
type User struct {
Username string `json:"username"`
Email string `json:"email"`
UserType UserType `json:"user_type"`
Restricted bool `json:"restricted"`
TFAEnabled bool `json:"tfa_enabled"`
SSHKeys []string `json:"ssh_keys"`
Expand Down
280 changes: 3 additions & 277 deletions go.work.sum

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ smoketest:
LINODE_TOKEN="awesometokenawesometokenawesometoken" \
LINODE_API_VERSION="v4beta" \
GO111MODULE="on" \
go test -v -run smoke ./integration/...
go test -v -run smoke ./integration/...


.PHONY: unit-test
unit-test:
go test -v ./unit $(ARGS)
40 changes: 40 additions & 0 deletions test/integration/account_child_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build parent_child

package integration

import (
"context"
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)

// NOTE: These fixtures are expected to be run under a parent account.
func TestAccountChild_basic(t *testing.T) {
client, teardown := createTestClient(t, "fixtures/TestAccountChild_basic")
defer teardown()

childAccounts, err := client.ListChildAccounts(context.Background(), nil)
require.NoError(t, err)
require.Greater(
t,
len(childAccounts),
0,
"number of child accounts should be > 0",
)

childAccount, err := client.GetChildAccount(context.Background(), childAccounts[0].EUUID)
require.NoError(t, err)
require.True(
t,
reflect.DeepEqual(*childAccount, childAccounts[0]),
"child accounts should be equal",
cmp.Diff(*childAccount, childAccounts[0]),
)

token, err := client.CreateChildAccountToken(context.Background(), childAccount.EUUID)
require.NoError(t, err)
require.Greater(t, len(token.Token), 0)
}
6 changes: 6 additions & 0 deletions test/integration/account_users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func TestUser_Get_smoke(t *testing.T) {
if user.VerifiedPhoneNumber != nil {
t.Error("expected phone number is not set")
}
if user.UserType == "" {
t.Errorf("expected user type, got none")
}
}

func TestUser_Update(t *testing.T) {
Expand Down Expand Up @@ -150,6 +153,9 @@ func TestUsers_List(t *testing.T) {
if newUser.VerifiedPhoneNumber != nil {
t.Error("expected phone number is not set")
}
if newUser.UserType == "" {
t.Errorf("expected user type, got none")
}
}

func createUser(t *testing.T, client *linodego.Client, userModifiers ...userModifier) (*User, func()) {
Expand Down
12 changes: 6 additions & 6 deletions test/integration/fixtures/ExampleGetAccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ interactions:
url: https://api.linode.com/v4beta/account
method: GET
response:
body: '{"company": "Linode", "email": "lgarber@linode.com", "first_name": "Lena",
"last_name": "Garber", "address_1": "2228 Lenox Ridge Ct NE", "address_2": "NA",
"city": "Atlanta", "state": "GA", "zip": "30319", "country": "US", "phone":
"6787613864", "balance": 0.0, "tax_id": "", "billing_source": "linode", "credit_card":
{"last_four": "1488", "expiry": "02/2022"}, "balance_uninvoiced": 0.0, "active_since":
body: '{"company": "Linode", "email": "foo@linode.com", "first_name": "foo",
"last_name": "bar", "address_1": "123 Street Street", "address_2": "NA",
"city": "Philadelphia", "state": "PA", "zip": "30000", "country": "US", "phone":
"1234567891", "balance": 0.0, "tax_id": "", "billing_source": "linode", "credit_card":
{"last_four": "1234", "expiry": "02/2020"}, "balance_uninvoiced": 0.0, "active_since":
"2018-01-02T03:04:05", "capabilities": ["Linodes", "NodeBalancers", "Block Storage",
"Object Storage", "Kubernetes", "Cloud Firewall", "Vlans", "LKE HA Control Planes",
"Machine Images", "Managed Databases"], "active_promotions": [], "euuid": "590F6313-2E4D-47CE-90943D2F724A87CB"}'
"Machine Images", "Managed Databases"], "active_promotions": [], "euuid": "FFFFFFFF-2E4D-47CE-FFFFFFFFFFFFFFFFF"}'
headers:
Access-Control-Allow-Credentials:
- "true"
Expand Down
Loading

0 comments on commit a86ec45

Please sign in to comment.