-
Notifications
You must be signed in to change notification settings - Fork 11
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
Resource: service #219
Resource: service #219
Changes from 67 commits
f6c08a5
e1d4695
8dd6619
3f23300
29a7fdd
1e0b57c
5630e22
b0148a7
ff7fea3
d5e6a9e
621e396
c1803dd
f940fec
9caeb87
c419c4b
2d2767d
14fc36c
531ffcf
1e24caf
091108d
d52af8f
d6a292c
1b5f6a8
0480bbc
fc4e5c0
d25a954
2893d74
0d0b123
2ef2789
3e7777c
62269e2
99c0d99
bb243b0
2ea5508
aa89c02
71ec3a1
dd5664b
e7263b7
b380e58
2c0a2dd
96f0b70
a7e2d7a
21005c3
0165eb3
1bfafad
be04a4f
3810400
447484c
fef2ede
d718edd
7843068
b8aa0ae
1fffa30
16bbb33
63f2d0a
595d82c
a7348c1
87c6337
3882988
62f8011
ce44a5a
9705524
b58eba7
add7a8b
bbf40ca
07624e2
ad360b3
84de94b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "twingate_service_account Resource - terraform-provider-twingate" | ||
subcategory: "" | ||
description: |- | ||
Services offer a way to provide programmatic, centrally-controlled, and consistent access controls. For more information, see Twingate's documentation https://www.twingate.com/docs/services. | ||
--- | ||
|
||
# twingate_service_account (Resource) | ||
|
||
Services offer a way to provide programmatic, centrally-controlled, and consistent access controls. For more information, see Twingate's [documentation](https://www.twingate.com/docs/services). | ||
ekampf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Example Usage | ||
|
||
```terraform | ||
provider "twingate" { | ||
api_token = "1234567890abcdef" | ||
network = "mynetwork" | ||
} | ||
|
||
resource "twingate_service_account" "github_actions_prod" { | ||
name = "Github Actions PROD" | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `name` (String) The name of the service account in Twingate | ||
ekampf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Read-Only | ||
|
||
- `id` (String) Autogenerated ID of the service account | ||
ekampf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
provider "twingate" { | ||
api_token = "1234567890abcdef" | ||
network = "mynetwork" | ||
} | ||
|
||
resource "twingate_service_account" "github_actions_prod" { | ||
name = "Github Actions PROD" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -230,3 +230,31 @@ func (r ResourceNode) ToModel() *model.Resource { | |
func (q readResourcesByNameQuery) ToModel() []*model.Resource { | ||
return q.Resources.ToModel() | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its something I missed in the restructure review but Im not sure I understand the purpose of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or maybe the struct defintion should be here too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here in I think in I like the idea with separate package for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok so lets merge this and refactor to |
||
func (q createServiceAccountQuery) ToModel() *model.ServiceAccount { | ||
return &model.ServiceAccount{ | ||
ID: q.ServiceAccountCreate.Entity.StringID(), | ||
Name: q.ServiceAccountCreate.Entity.StringName(), | ||
} | ||
} | ||
|
||
func (q readServiceAccountQuery) ToModel() *model.ServiceAccount { | ||
if q.ServiceAccount == nil { | ||
return nil | ||
} | ||
|
||
return q.ServiceAccount.ToModel() | ||
} | ||
|
||
func (q gqlServiceAccount) ToModel() *model.ServiceAccount { | ||
return &model.ServiceAccount{ | ||
ID: q.StringID(), | ||
Name: q.StringName(), | ||
} | ||
} | ||
|
||
func (s *ServiceAccounts) ToModel() []*model.ServiceAccount { | ||
return utils.Map[*ServiceAccountEdge, *model.ServiceAccount](s.Edges, func(edge *ServiceAccountEdge) *model.ServiceAccount { | ||
return edge.Node.ToModel() | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package client | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this here and not in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here I'm testing private func |
||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/twingate/go-graphql-client" | ||
) | ||
|
||
func TestPagination(t *testing.T) { | ||
badError := errors.New("bad error") | ||
|
||
cases := []struct { | ||
resource *PaginatedResource[int] | ||
nextPage nextPageFunc[int] | ||
|
||
expected *PaginatedResource[int] | ||
expectedErr error | ||
}{ | ||
{}, | ||
{ | ||
resource: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: false, | ||
}, | ||
}, | ||
expected: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: false, | ||
}, | ||
}, | ||
}, | ||
{ | ||
resource: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: true, | ||
}, | ||
}, | ||
nextPage: func(ctx context.Context, variables map[string]interface{}, cursor graphql.String) (*PaginatedResource[int], error) { | ||
return nil, badError | ||
}, | ||
expected: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: true, | ||
}, | ||
}, | ||
expectedErr: badError, | ||
}, | ||
{ | ||
resource: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: true, | ||
}, | ||
Edges: []int{1, 2}, | ||
}, | ||
nextPage: func(ctx context.Context, variables map[string]interface{}, cursor graphql.String) (*PaginatedResource[int], error) { | ||
return &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: false, | ||
}, | ||
Edges: []int{3, 4}, | ||
}, nil | ||
}, | ||
expected: &PaginatedResource[int]{ | ||
PageInfo: PageInfo{ | ||
HasNextPage: true, | ||
}, | ||
Edges: []int{1, 2, 3, 4}, | ||
}, | ||
}, | ||
} | ||
|
||
for n, c := range cases { | ||
t.Run(fmt.Sprintf("case_%d", n), func(t *testing.T) { | ||
err := c.resource.fetchPages(context.TODO(), c.nextPage, map[string]interface{}{}) | ||
|
||
assert.Equal(t, c.expected, c.resource) | ||
assert.Equal(t, c.expectedErr, err) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/Twingate/terraform-provider-twingate/twingate/internal/model" | ||
"github.com/twingate/go-graphql-client" | ||
) | ||
|
||
const serviceAccountResourceName = "service account" | ||
|
||
type gqlServiceAccount struct { | ||
IDName | ||
} | ||
|
||
type createServiceAccountQuery struct { | ||
ServiceAccountCreate struct { | ||
Entity IDName | ||
OkError | ||
} `graphql:"serviceAccountCreate(name: $name)"` | ||
} | ||
|
||
func (client *Client) CreateServiceAccount(ctx context.Context, serviceAccountName string) (*model.ServiceAccount, error) { | ||
if serviceAccountName == "" { | ||
return nil, NewAPIError(ErrGraphqlNameIsEmpty, "create", serviceAccountResourceName) | ||
} | ||
|
||
variables := newVars(gqlField(serviceAccountName, "name")) | ||
response := createServiceAccountQuery{} | ||
|
||
err := client.GraphqlClient.NamedMutate(ctx, "createServiceAccount", &response, variables) | ||
if err != nil { | ||
return nil, NewAPIError(err, "create", serviceAccountResourceName) | ||
} | ||
|
||
if !response.ServiceAccountCreate.Ok { | ||
message := response.ServiceAccountCreate.Error | ||
|
||
return nil, NewAPIError(NewMutationError(message), "create", serviceAccountResourceName) | ||
} | ||
|
||
return response.ToModel(), nil | ||
} | ||
|
||
type readServiceAccountQuery struct { | ||
ServiceAccount *gqlServiceAccount `graphql:"serviceAccount(id: $id)"` | ||
} | ||
|
||
func (client *Client) ReadServiceAccount(ctx context.Context, serviceAccountID string) (*model.ServiceAccount, error) { | ||
if serviceAccountID == "" { | ||
return nil, NewAPIError(ErrGraphqlIDIsEmpty, "read", serviceAccountResourceName) | ||
} | ||
|
||
variables := newVars(gqlID(serviceAccountID)) | ||
response := readServiceAccountQuery{} | ||
|
||
err := client.GraphqlClient.NamedQuery(ctx, "readServiceAccount", &response, variables) | ||
if err != nil { | ||
return nil, NewAPIErrorWithID(err, "read", serviceAccountResourceName, serviceAccountID) | ||
} | ||
|
||
if response.ServiceAccount == nil { | ||
return nil, NewAPIErrorWithID(ErrGraphqlResultIsEmpty, "read", serviceAccountResourceName, serviceAccountID) | ||
} | ||
|
||
return response.ToModel(), nil | ||
} | ||
|
||
type updateServiceAccountQuery struct { | ||
ServiceAccountUpdate struct { | ||
Entity *gqlServiceAccount | ||
OkError | ||
} `graphql:"serviceAccountUpdate(id: $id, name: $name)"` | ||
} | ||
|
||
func (client *Client) UpdateServiceAccount(ctx context.Context, serviceAccount *model.ServiceAccount) (*model.ServiceAccount, error) { | ||
if serviceAccount == nil || serviceAccount.ID == "" { | ||
return nil, NewAPIError(ErrGraphqlIDIsEmpty, "update", serviceAccountResourceName) | ||
} | ||
|
||
if serviceAccount.Name == "" { | ||
return nil, NewAPIError(ErrGraphqlNameIsEmpty, "update", serviceAccountResourceName) | ||
} | ||
|
||
variables := newVars( | ||
gqlID(serviceAccount.ID), | ||
gqlField(serviceAccount.Name, "name"), | ||
) | ||
|
||
response := updateServiceAccountQuery{} | ||
|
||
err := client.GraphqlClient.NamedMutate(ctx, "updateServiceAccount", &response, variables) | ||
if err != nil { | ||
return nil, NewAPIErrorWithID(err, "update", serviceAccountResourceName, serviceAccount.ID) | ||
} | ||
|
||
if !response.ServiceAccountUpdate.Ok { | ||
return nil, NewAPIErrorWithID(NewMutationError(response.ServiceAccountUpdate.Error), "update", serviceAccountResourceName, serviceAccount.ID) | ||
} | ||
|
||
if response.ServiceAccountUpdate.Entity == nil { | ||
return nil, NewAPIErrorWithID(ErrGraphqlResultIsEmpty, "update", serviceAccountResourceName, serviceAccount.ID) | ||
} | ||
|
||
return response.ServiceAccountUpdate.Entity.ToModel(), nil | ||
} | ||
|
||
type deleteServiceAccountQuery struct { | ||
ServiceAccountDelete *OkError `graphql:"serviceAccountDelete(id: $id)"` | ||
} | ||
|
||
func (client *Client) DeleteServiceAccount(ctx context.Context, serviceAccountID string) error { | ||
if serviceAccountID == "" { | ||
return NewAPIError(ErrGraphqlIDIsEmpty, "delete", serviceAccountResourceName) | ||
} | ||
|
||
variables := newVars(gqlID(serviceAccountID)) | ||
response := deleteServiceAccountQuery{} | ||
|
||
err := client.GraphqlClient.NamedMutate(ctx, "deleteServiceAccount", &response, variables) | ||
if err != nil { | ||
return NewAPIErrorWithID(err, "delete", serviceAccountResourceName, serviceAccountID) | ||
} | ||
|
||
if !response.ServiceAccountDelete.Ok { | ||
return NewAPIErrorWithID(NewMutationError(response.ServiceAccountDelete.Error), "delete", serviceAccountResourceName, serviceAccountID) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type ServiceAccountEdge struct { | ||
Node *gqlServiceAccount | ||
} | ||
|
||
type ServiceAccounts struct { | ||
PaginatedResource[*ServiceAccountEdge] | ||
} | ||
|
||
type readServiceAccountsQuery struct { | ||
ServiceAccounts ServiceAccounts | ||
} | ||
|
||
func (client *Client) ReadServiceAccounts(ctx context.Context) ([]*model.ServiceAccount, error) { | ||
response := readServiceAccountsQuery{} | ||
|
||
err := client.GraphqlClient.NamedQuery(ctx, "readServiceAccounts", &response, nil) | ||
if err != nil { | ||
return nil, NewAPIErrorWithID(err, "read", serviceAccountResourceName, "All") | ||
} | ||
|
||
if len(response.ServiceAccounts.Edges) == 0 { | ||
return nil, NewAPIErrorWithID(ErrGraphqlResultIsEmpty, "read", serviceAccountResourceName, "All") | ||
} | ||
|
||
err = response.ServiceAccounts.fetchPages(ctx, client.readServiceAccountsAfter, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return response.ServiceAccounts.ToModel(), nil | ||
} | ||
|
||
type readServiceAccountsAfter struct { | ||
ServiceAccounts ServiceAccounts `graphql:"serviceAccounts(after: $serviceAccountsEndCursor)"` | ||
} | ||
|
||
func (client *Client) readServiceAccountsAfter(ctx context.Context, variables map[string]interface{}, cursor graphql.String) (*PaginatedResource[*ServiceAccountEdge], error) { | ||
if variables == nil { | ||
variables = make(map[string]interface{}) | ||
} | ||
|
||
variables["serviceAccountsEndCursor"] = cursor | ||
response := readServiceAccountsAfter{} | ||
|
||
err := client.GraphqlClient.NamedQuery(ctx, "readServiceAccounts", &response, variables) | ||
if err != nil { | ||
return nil, NewAPIErrorWithID(err, "read", serviceAccountResourceName, "All") | ||
} | ||
|
||
if len(response.ServiceAccounts.Edges) == 0 { | ||
return nil, NewAPIErrorWithID(ErrGraphqlResultIsEmpty, "read", serviceAccountResourceName, "All") | ||
} | ||
|
||
return &response.ServiceAccounts.PaginatedResource, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package model | ||
|
||
type ServiceAccount struct { | ||
ID string | ||
Name string | ||
} | ||
|
||
func (s ServiceAccount) GetName() string { | ||
return s.Name | ||
} | ||
|
||
func (s ServiceAccount) GetID() string { | ||
return s.ID | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is taken from the public docs, but let's do this everywhere: s/Services/Service Accounts/g
In this description, let's remove the sentence "For more information [...]"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexmensch I've updated doc strings