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

Resource: service #219

Merged
merged 68 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
f6c08a5
WIP
vmanilo Aug 19, 2022
e1d4695
updated project layout
vmanilo Aug 22, 2022
8dd6619
fix linter
vmanilo Aug 22, 2022
3f23300
ignore sec warn
vmanilo Aug 22, 2022
29a7fdd
Merge branch 'main' into restructure-layout
vmanilo Aug 31, 2022
1e0b57c
fix: re-generated docs
vmanilo Sep 5, 2022
5630e22
fix users test
vmanilo Sep 5, 2022
b0148a7
added connector model
vmanilo Sep 6, 2022
ff7fea3
fix convertion graphql.ID to string
vmanilo Sep 6, 2022
d5e6a9e
added connector tokens model
vmanilo Sep 6, 2022
621e396
added group model
vmanilo Sep 6, 2022
c1803dd
added remote-network model
vmanilo Sep 7, 2022
f940fec
fix fmt
vmanilo Sep 7, 2022
9caeb87
added user model
vmanilo Sep 7, 2022
c419c4b
fix fmt
vmanilo Sep 7, 2022
2d2767d
WIP
vmanilo Sep 13, 2022
14fc36c
added connectors pages
vmanilo Nov 1, 2022
531ffcf
Merge remote-tracking branch 'upstream/main' into feature/datasource-…
vmanilo Nov 1, 2022
1e24caf
added featching all pages for groups
vmanilo Nov 2, 2022
091108d
Merge remote-tracking branch 'upstream/main' into feature/datasource-…
vmanilo Nov 2, 2022
d52af8f
added featching all pages for resources
vmanilo Nov 2, 2022
d6a292c
added featching all pages for users
vmanilo Nov 2, 2022
1b5f6a8
Merge branch 'main' into feature/datasource-load-all-pages
vmanilo Nov 2, 2022
0480bbc
Merge branch 'main' into restructure-layout
vmanilo Nov 5, 2022
fc4e5c0
Merge branch 'feature/datasource-load-all-pages' into restructure-layout
vmanilo Nov 5, 2022
d25a954
updated test results location
vmanilo Nov 5, 2022
2893d74
wip: fixing tests
vmanilo Nov 7, 2022
0d0b123
added generic paginated resource
vmanilo Nov 8, 2022
2ef2789
added tests
vmanilo Nov 8, 2022
3e7777c
Merge branch 'feature/datasource-load-all-pages' into restructure-layout
vmanilo Nov 9, 2022
62269e2
Merge branch 'main' into feature/datasource-load-all-pages
vmanilo Nov 9, 2022
99c0d99
Merge remote-tracking branch 'origin/feature/datasource-load-all-page…
vmanilo Nov 9, 2022
bb243b0
renamed transport pkg to client
vmanilo Nov 9, 2022
2ea5508
fixed tests
vmanilo Nov 10, 2022
aa89c02
fix path to generated test coverage report
vmanilo Nov 10, 2022
71ec3a1
fix read resources
vmanilo Nov 10, 2022
dd5664b
remove parallel resource tests
vmanilo Nov 10, 2022
e7263b7
added debug log
vmanilo Nov 10, 2022
b380e58
debug error
vmanilo Nov 10, 2022
2c0a2dd
debug error
vmanilo Nov 10, 2022
96f0b70
fix test
vmanilo Nov 10, 2022
a7e2d7a
remove logs
vmanilo Nov 10, 2022
21005c3
Merge remote-tracking branch 'upstream/main' into restructure-layout
vmanilo Nov 11, 2022
0165eb3
revert changes in ci.yml
vmanilo Nov 11, 2022
1bfafad
simplify converters
vmanilo Nov 11, 2022
be04a4f
added tests
vmanilo Nov 13, 2022
3810400
added tests for models
vmanilo Nov 13, 2022
447484c
fix fmt
vmanilo Nov 13, 2022
fef2ede
added test coverage
vmanilo Nov 14, 2022
d718edd
added test coverage
vmanilo Nov 14, 2022
7843068
added test coverage
vmanilo Nov 14, 2022
b8aa0ae
fix test
vmanilo Nov 14, 2022
1fffa30
added test coverage
vmanilo Nov 14, 2022
16bbb33
run acc test
vmanilo Nov 15, 2022
63f2d0a
revert changes
vmanilo Nov 15, 2022
595d82c
added service-account resource
vmanilo Nov 16, 2022
a7348c1
refactor acc tests
vmanilo Nov 17, 2022
87c6337
Merge branch 'restructure-layout' into feature/service-account-resource
vmanilo Nov 21, 2022
3882988
Fix http_max_retry doc
ekampf Nov 23, 2022
62f8011
regenerated docs
vmanilo Nov 24, 2022
ce44a5a
Merge branch 'restructure-layout' into feature/service-account-resource
vmanilo Nov 24, 2022
9705524
renamed resource: service-account -> service
vmanilo Nov 24, 2022
b58eba7
fix acctest
vmanilo Nov 25, 2022
add7a8b
Merge branch 'main' into feature/service-account-resource
vmanilo Nov 26, 2022
bbf40ca
renamed resource to twingate_service_account
vmanilo Nov 29, 2022
07624e2
added test coverage
vmanilo Dec 3, 2022
ad360b3
added test coverage
vmanilo Dec 3, 2022
84de94b
updated doc description
vmanilo Dec 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/resources/service_account.md
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: |-
Service Accounts offer a way to provide programmatic, centrally-controlled, and consistent access controls.
---

# twingate_service_account (Resource)

Service Accounts offer a way to provide programmatic, centrally-controlled, and consistent access controls.

## 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

### Read-Only

- `id` (String) Autogenerated ID of the Service Account


8 changes: 8 additions & 0 deletions examples/resources/twingate_service_account/resource.tf
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"
}
28 changes: 28 additions & 0 deletions twingate/internal/client/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,31 @@ func (r ResourceNode) ToModel() *model.Resource {
func (q readResourcesByNameQuery) ToModel() []*model.Resource {
return q.Resources.ToModel()
}

Copy link
Contributor

Choose a reason for hiding this comment

The 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 converter.go - we define createServiceAccountQuery in service-account.go why cant its methods be there?
(same for all the other structs that their methods are here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe the struct defintion should be here too?
or maybe we need queries/createServiceAccountQuery.go ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here in converter.go I've collected simple utilities functions which converts queries objects to models

I think in service-account.go would be better to have only code related to API request, and this object conversions like lower layer details

I like the idea with separate package for queries, I think this can help organise code better :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so lets merge this and refactor to queries on a separate PR

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()
})
}
84 changes: 84 additions & 0 deletions twingate/internal/client/pagination_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package client
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here and not in twingate/internal/test/client ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I'm testing private func fetchPages and it's not accessible from other packages


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)
})
}
}
186 changes: 186 additions & 0 deletions twingate/internal/client/service-account.go
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
}
14 changes: 14 additions & 0 deletions twingate/internal/model/service-account.go
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
}
1 change: 1 addition & 0 deletions twingate/internal/provider/resource/all-resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ const (
TwingateConnectorTokens = "twingate_connector_tokens"
TwingateGroup = "twingate_group"
TwingateResource = "twingate_resource"
TwingateServiceAccount = "twingate_service_account"
)