Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: Add support for Parent/Child account switching #455

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ citest: lint test

testunit:
go test -v $(PACKAGES) $(ARGS)
cd test && make unit-test

testint:
cd test && make test
Expand Down Expand Up @@ -76,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
2 changes: 1 addition & 1 deletion go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,4 @@ golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c h1:GohjlNKauSai7gN4wsJkeZ3WAJx4Sh+oT/b5IYn5suA=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
7 changes: 6 additions & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,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)
2 changes: 2 additions & 0 deletions test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/jarcoal/httpmock v1.3.1
github.com/linode/linodego v1.25.0
github.com/linode/linodego/k8s v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0
k8s.io/client-go v0.28.1
Expand All @@ -31,6 +32,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down
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
13 changes: 8 additions & 5 deletions test/integration/fixtures/TestAccountAvailability_Get.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ interactions:
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Cache-Control:
- private, max-age=0, s-maxage=0, no-cache, no-store
- private, max-age=60, s-maxage=60
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Length:
- "40"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Server:
- nginx
Expires:
- Tue, 13 Feb 2024 18:57:39 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
Expand All @@ -52,7 +55,7 @@ interactions:
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "1200"
- "400"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
Expand Down
38 changes: 25 additions & 13 deletions test/integration/fixtures/TestAccountAvailability_List.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ interactions:
url: https://api.linode.com/v4beta/account/availability
method: GET
response:
body: '{"data": [{"region": "ap-west", "unavailable": []}, {"region": "ca-central",
"unavailable": []}, {"region": "ap-southeast", "unavailable": []}, {"region":
"us-central", "unavailable": []}, {"region": "us-west", "unavailable": []},
{"region": "us-southeast", "unavailable": []}, {"region": "us-east", "unavailable":
[]}, {"region": "eu-west", "unavailable": []}, {"region": "ap-south", "unavailable":
[]}, {"region": "eu-central", "unavailable": []}, {"region": "ap-northeast",
"unavailable": []}], "page": 1, "pages": 1, "results": 11}'
body: '{"data": [{"region": "us-central", "unavailable": []}, {"region": "us-west",
"unavailable": []}, {"region": "us-southeast", "unavailable": []}, {"region":
"us-east", "unavailable": []}, {"region": "eu-west", "unavailable": []}, {"region":
"ap-south", "unavailable": []}, {"region": "eu-central", "unavailable": []},
{"region": "ap-northeast", "unavailable": ["Linodes", "NodeBalancers", "Block
Storage", "Kubernetes"]}, {"region": "ap-west", "unavailable": []}, {"region":
"ca-central", "unavailable": []}, {"region": "ap-southeast", "unavailable":
[]}, {"region": "us-iad", "unavailable": ["NodeBalancers", "Block Storage"]},
{"region": "us-ord", "unavailable": []}, {"region": "fr-par", "unavailable":
[]}, {"region": "us-sea", "unavailable": []}, {"region": "br-gru", "unavailable":
[]}, {"region": "nl-ams", "unavailable": []}, {"region": "se-sto", "unavailable":
[]}, {"region": "es-mad", "unavailable": []}, {"region": "in-maa", "unavailable":
[]}, {"region": "jp-osa", "unavailable": []}, {"region": "it-mil", "unavailable":
[]}, {"region": "us-mia", "unavailable": []}, {"region": "id-cgk", "unavailable":
[]}, {"region": "us-lax", "unavailable": []}], "page": 1, "pages": 1, "results":
25}'
headers:
Access-Control-Allow-Credentials:
- "true"
Expand All @@ -33,20 +42,23 @@ interactions:
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Cache-Control:
- private, max-age=0, s-maxage=0, no-cache, no-store
- private, max-age=60, s-maxage=60
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Server:
- nginx
Expires:
- Tue, 13 Feb 2024 18:57:39 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
- Accept-Encoding
- Authorization, X-Filter
- Authorization, X-Filter
- Accept-Encoding
X-Accepted-Oauth-Scopes:
- account:read_only
X-Content-Type-Options:
Expand All @@ -57,7 +69,7 @@ interactions:
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "1200"
- "400"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
Expand Down
Loading