Skip to content

Commit

Permalink
add user_role_binding resource to manage users' roles (#161)
Browse files Browse the repository at this point in the history
* add user_role_binding resource to manage users' roles

* move back resource map; change users to be a set of strings

* bump rate limit for local testing

* implement tests

* fix tests

* fix import and lint

* fix tf import and add tests; docs; review suggestions

* bump version

* Update lightstep/resource_user_role_binding.go

Co-authored-by: Isaak Krautwurst <[email protected]>

* Update lightstep/resource_user_role_binding.go

Co-authored-by: Isaak Krautwurst <[email protected]>

* Update lightstep/resource_user_role_binding.go

Co-authored-by: Isaak Krautwurst <[email protected]>

* update docs

---------

Co-authored-by: Isaak Krautwurst <[email protected]>
  • Loading branch information
paivagustavo and idkraut authored Jul 7, 2023
1 parent 17210dd commit 6c117d2
Show file tree
Hide file tree
Showing 9 changed files with 562 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .go-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.78.1
1.80.0
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ifndef LIGHTSTEP_API_KEY_PUBLIC
$(error LIGHTSTEP_API_KEY_PUBLIC must be defined for acc-test)
endif
@TF_ACC=true \
LIGHTSTEP_API_RATE_LIMIT=5 \
LIGHTSTEP_API_KEY=${LIGHTSTEP_API_KEY_PUBLIC} \
LIGHTSTEP_ORG="terraform-provider" \
LIGHTSTEP_PROJECT="terraform-provider-test" \
Expand All @@ -56,6 +57,7 @@ test-local:
LIGHTSTEP_API_KEY=${LIGHTSTEP_LOCAL_API_KEY} \
LIGHTSTEP_ORG="terraform-provider" \
LIGHTSTEP_PROJECT="terraform-provider-test" \
LIGHTSTEP_API_RATE_LIMIT=100 \
LIGHTSTEP_ENV="public" \
go test -v ./lightstep

Expand Down
21 changes: 16 additions & 5 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"log"
"net/http"
"os"
"strconv"
"time"

"github.com/hashicorp/go-retryablehttp"
Expand Down Expand Up @@ -57,10 +58,9 @@ type Envelope struct {
Data json.RawMessage `json:"data"`
}

type Body struct {
Data interface{} `json:"data,omitempty"`
Errors []string `json:"errors,omitempty"`
Links map[string]interface{} `json:"links,omitempty"`
// genericAPIResponse represents a generic response from the Lightstep Public API
type genericAPIResponse[T any] struct {
Data T `json:"data"`
}

type Client struct {
Expand Down Expand Up @@ -95,6 +95,12 @@ func NewClientWithUserAgent(apiKey string, orgName string, env string, userAgent

fullBaseURL := fmt.Sprintf("%s/public/v0.2/%v", baseURL, orgName)

rateLimitStr := os.Getenv("LIGHTSTEP_API_RATE_LIMIT")
rateLimit, err := strconv.Atoi(rateLimitStr)
if err != nil {
rateLimit = DefaultRateLimitPerSecond
}

// Default client retries 5xx and 429 errors.
newClient := retryablehttp.NewClient()
newClient.HTTPClient.Timeout = DefaultTimeoutSeconds * time.Second
Expand All @@ -104,7 +110,7 @@ func NewClientWithUserAgent(apiKey string, orgName string, env string, userAgent
orgName: orgName,
baseURL: fullBaseURL,
userAgent: userAgent,
rateLimiter: rate.NewLimiter(rate.Limit(DefaultRateLimitPerSecond), 1),
rateLimiter: rate.NewLimiter(rate.Limit(rateLimit), 1),
client: newClient,
contentType: "application/vnd.api+json",
}
Expand Down Expand Up @@ -255,3 +261,8 @@ func (c *Client) GetStreamIDByLink(ctx context.Context, url string) (string, err

return str.ID, nil
}

// OrgName returns the name of the organization that this client will make request on behalf to.
func (c *Client) OrgName() string {
return c.orgName
}
71 changes: 71 additions & 0 deletions client/user_role_binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package client

import (
"context"
"encoding/json"
"fmt"
"net/url"
)

type RoleBinding struct {
RoleName string `json:"role-name"`
ProjectName string `json:"project-name"`
Users []string `json:"users"`
}

func (rb RoleBinding) ID() string {
if rb.ProjectName == "" {
return rb.RoleName
}
return fmt.Sprintf("%s/%s", rb.RoleName, rb.ProjectName)
}

type updateRoleBindingAPIResponse struct {
Attributes RoleBinding `json:"attributes,omitempty"`
}

func (c *Client) ListRoleBinding(
ctx context.Context,
projectName string,
roleName string,
) (RoleBinding, error) {
var resp genericAPIResponse[updateRoleBindingAPIResponse]

err := c.CallAPI(ctx, "GET", fmt.Sprintf("role-binding?role-name=%s&project=%s", url.QueryEscape(roleName), url.QueryEscape(projectName)), nil, &resp)
if err != nil {
return RoleBinding{}, err
}

return resp.Data.Attributes, nil
}

func (c *Client) UpdateRoleBinding(
ctx context.Context,
projectName string,
roleName string,
users ...string,
) (RoleBinding, error) {
var resp Envelope
var roleBinding updateRoleBindingAPIResponse

bytes, err := json.Marshal(RoleBinding{
ProjectName: projectName,
RoleName: roleName,
Users: users,
})
if err != nil {
return roleBinding.Attributes, err
}

err = c.CallAPI(ctx, "POST", "role-binding", Envelope{Data: bytes}, &resp)
if err != nil {
return roleBinding.Attributes, err
}

err = json.Unmarshal(resp.Data, &roleBinding.Attributes)
if err != nil {
return roleBinding.Attributes, err
}

return roleBinding.Attributes, nil
}
48 changes: 48 additions & 0 deletions client/user_role_binding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package client

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_UserRoleBinding(t *testing.T) {
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/public/v0.2/blars/role-binding", r.URL.Path)

assert.Equal(t, "project with spaces", r.URL.Query().Get("project"))
assert.Equal(t, "project editor", r.URL.Query().Get("role-name"))

resp, err := json.Marshal(map[string]any{
"data": map[string]any{
"attributes": RoleBinding{
RoleName: "project editor",
ProjectName: "project with spaces",
Users: []string{"[email protected]"},
},
},
})
require.NoError(t, err)

w.Write(resp)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

t.Setenv("LIGHTSTEP_API_BASE_URL", server.URL)
c := NewClient("api", "blars", "staging")
rb, err := c.ListRoleBinding(context.Background(), "project with spaces", "project editor")
assert.NoError(t, err)

assert.Equal(t, RoleBinding{
RoleName: "project editor",
ProjectName: "project with spaces",
Users: []string{"[email protected]"},
}, rb)
}
29 changes: 29 additions & 0 deletions docs/resources/user_role_binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "lightstep_user_role_binding Resource - terraform-provider-lightstep"
subcategory: ""
description: |-
This resource is under development and is not generally available yet.
---

# lightstep_user_role_binding (Resource)

This resource is under development and is not generally available yet.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `role` (String) Role's name being granted with this role binding.
- `users` (Set of String) Complete list of users that should have this specified role in the organization or in the project (if specified). Important: this list is authoritative; any users not included in this list WILL NOT have this role for the given project or organization.

### Optional

- `project` (String) Name of the project where this role will be applied; if omitted the role will be applied to the organization

### Read-Only

- `id` (String) The ID of this resource.
20 changes: 16 additions & 4 deletions lightstep/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ package lightstep
import (
"context"
"fmt"
"net/http"
"os"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/v2/meta"

"github.com/lightstep/terraform-provider-lightstep/version"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"

"github.com/lightstep/terraform-provider-lightstep/client"
"github.com/lightstep/terraform-provider-lightstep/version"
)

func Provider() *schema.Provider {
Expand Down Expand Up @@ -62,6 +61,7 @@ func Provider() *schema.Provider {
"lightstep_alerting_rule": resourceAlertingRule(),
"lightstep_dashboard": resourceUnifiedDashboard(UnifiedChartSchema),
"lightstep_alert": resourceUnifiedCondition(UnifiedConditionSchema),
"lightstep_user_role_binding": resourceUserRoleBinding(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -100,3 +100,15 @@ func configureProvider(_ context.Context, d *schema.ResourceData) (interface{},

return client, diags
}

func handleAPIError(err error, d *schema.ResourceData, resourceName string) diag.Diagnostics {
apiErr, ok := err.(client.APIResponseCarrier)
if ok {
if apiErr.GetStatusCode() == http.StatusNotFound {
d.SetId("")
return diag.FromErr(fmt.Errorf("%s not found: %v", resourceName, apiErr))
}
}

return diag.FromErr(fmt.Errorf("failed to %s: %v", resourceName, err))
}
Loading

0 comments on commit 6c117d2

Please sign in to comment.