Skip to content

Commit

Permalink
refactor: move client to separate package
Browse files Browse the repository at this point in the history
  • Loading branch information
henryde committed Jun 20, 2024
1 parent 580ad99 commit 06e52a0
Show file tree
Hide file tree
Showing 19 changed files with 812 additions and 778 deletions.
50 changes: 49 additions & 1 deletion internal/provider/buildingblock.go → client/buildingblock.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
package provider
package client

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)

type MeshBuildingBlock struct {
ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
Expand Down Expand Up @@ -40,3 +48,43 @@ type MeshBuildingBlockStatus struct {
Status string `json:"status" tfsdk:"status"`
Outputs []MeshBuildingBlockIO `json:"outputs" tfsdk:"outputs"`
}

func (c *MeshStackProviderClient) ReadBuildingBlock(uuid string) (*MeshBuildingBlock, error) {
if c.ensureValidToken() != nil {
return nil, errors.New(ERROR_AUTHENTICATION_FAILURE)
}

targetUrl := c.endpoints.BuildingBlocks.JoinPath(uuid)
req, err := http.NewRequest("GET", targetUrl.String(), nil)
if err != nil {
return nil, err
}

res, err := c.doAuthenticatedRequest(req)
if err != nil {
return nil, err
}

defer res.Body.Close()

data, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}

if res.StatusCode == 404 {
return nil, nil
}

if res.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
}

var bb MeshBuildingBlock
err = json.Unmarshal(data, &bb)
if err != nil {
return nil, err
}

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

import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
)

const (
apiMeshObjectsRoot = "/api/meshobjects"
loginEndpoint = "/api/login"

ERROR_GENERIC_CLIENT_ERROR = "client error"
ERROR_GENERIC_API_ERROR = "api error"
ERROR_AUTHENTICATION_FAILURE = "Not authorized. Check api key and secret."
ERROR_ENDPOINT_LOOKUP = "Could not fetch endpoints for meshStack."

CONTENT_TYPE_PROJECT = "application/vnd.meshcloud.api.meshproject.v2.hal+json"
CONTENT_TYPE_TENANT = "application/vnd.meshcloud.api.meshtenant.v3.hal+json"
CONTENT_TYPE_PROJECT_USER_BINDINGS = "application/vnd.meshcloud.api.meshprojectuserbinding.v1.hal+json"
CONTENT_TYPE_PROJECT_USER_BINDING = "application/vnd.meshcloud.api.meshprojectuserbinding.v3.hal+json"
)

type MeshStackProviderClient struct {
url *url.URL
httpClient *http.Client
apiKey string
apiSecret string
token string
tokenExpiry time.Time
endpoints endpoints
}

type endpoints struct {
BuildingBlocks *url.URL `json:"meshbuildingblocks"`
Projects *url.URL `json:"meshprojects"`
ProjectUserBindings *url.URL `json:"meshprojectuserbindings"`
Tenants *url.URL `json:"meshtenants"`
}

type loginResponse struct {
Token string `json:"access_token"`
ExpireSec int `json:"expires_in"`
}

func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackProviderClient, error) {
client := &MeshStackProviderClient{
url: rootUrl,
httpClient: &http.Client{
Timeout: time.Minute * 5,
},
apiKey: apiKey,
apiSecret: apiSecret,
token: "",
}

// TODO: lookup endpoints
client.endpoints = endpoints{
BuildingBlocks: rootUrl.JoinPath(apiMeshObjectsRoot, "meshbuildingblocks"),
Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"),
ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"),
Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"),
}

return client, nil
}

func (c *MeshStackProviderClient) login() error {
loginPath, err := url.JoinPath(c.url.String(), loginEndpoint)
if err != nil {
return err
}

formData := url.Values{}
formData.Set("client_id", c.apiKey)
formData.Set("client_secret", c.apiSecret)
formData.Set("grant_type", "client_credentials")

req, _ := http.NewRequest(http.MethodPost, loginPath, strings.NewReader(formData.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

res, err := c.httpClient.Do(req)

if err != nil || res.StatusCode != 200 {
return errors.New(ERROR_AUTHENTICATION_FAILURE)
}

defer res.Body.Close()

data, err := io.ReadAll(res.Body)
if err != nil {
return err
}

var loginResult loginResponse
err = json.Unmarshal(data, &loginResult)
if err != nil {
return err
}

c.token = fmt.Sprintf("Bearer %s", loginResult.Token)
c.tokenExpiry = time.Now().Add(time.Second * time.Duration(loginResult.ExpireSec))

return nil
}

func (c *MeshStackProviderClient) ensureValidToken() error {
if c.token == "" || time.Now().Add(time.Second*30).After(c.tokenExpiry) {
return c.login()
}
return nil
}

// nolint: unused
func (c *MeshStackProviderClient) lookUpEndpoints() error {
if c.ensureValidToken() != nil {
return errors.New(ERROR_AUTHENTICATION_FAILURE)
}

meshObjectsPath, err := url.JoinPath(c.url.String(), apiMeshObjectsRoot)
if err != nil {
return err
}
meshObjects, _ := url.Parse(meshObjectsPath)

res, err := c.httpClient.Do(
&http.Request{
URL: meshObjects,
Method: "GET",
Header: http.Header{
"Authorization": {c.token},
},
},
)

if err != nil {
return errors.New(ERROR_GENERIC_CLIENT_ERROR)
}

defer res.Body.Close()

if res.StatusCode != 200 {
return errors.New(ERROR_AUTHENTICATION_FAILURE)
}

data, err := io.ReadAll(res.Body)
if err != nil {
return err
}

var endpoints endpoints
err = json.Unmarshal(data, &endpoints)
if err != nil {
return err
}

c.endpoints = endpoints
return nil
}

func (c *MeshStackProviderClient) doAuthenticatedRequest(req *http.Request) (*http.Response, error) {
// ensure that headeres are initialized
if req.Header == nil {
req.Header = map[string][]string{}
}
req.Header.Set("User-Agent", "meshStack Terraform Provider")

// log request before adding auth
log.Println(req)

// add authentication
if c.ensureValidToken() != nil {
return nil, errors.New(ERROR_AUTHENTICATION_FAILURE)
}
req.Header.Set("Authorization", c.token)

res, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
log.Println(res)

return res, nil
}

func (c *MeshStackProviderClient) deleteMeshObject(targetUrl url.URL, expectedStatus int) error {
req, err := http.NewRequest("DELETE", targetUrl.String(), nil)
if err != nil {
return err
}

res, err := c.doAuthenticatedRequest(req)

if err != nil {
return errors.New(ERROR_GENERIC_CLIENT_ERROR)
}

defer res.Body.Close()

data, err := io.ReadAll(res.Body)
if err != nil {
return err
}

if res.StatusCode != expectedStatus {
return fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
}

return nil
}
Loading

0 comments on commit 06e52a0

Please sign in to comment.