Skip to content
Open
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
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,57 @@ $ make testunit

If you want to go off of an example, we recommend using the [external contacts](https://github.com/MyPureCloud/terraform-provider-genesyscloud/tree/main/genesyscloud/external_contacts) package.

### Custom API Client

When a proxy function needs to call a Genesys Cloud API endpoint that doesn't have a generated SDK method, use the `custom_api_client` package (`genesyscloud/custom_api_client`) instead of calling `APIClient.CallAPI()` directly or constructing raw `http.Client` requests. This package handles authorization headers, content-type negotiation, query parameter encoding, error handling, and SDK debug logging context automatically.

```go
import customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client"
```

**Available functions:**

| Function | Use case |
|----------|----------|
| `Do[T]` | Typed JSON response — unmarshals into `T` |
| `DoNoResponse` | No response body (DELETE, PUT with no return) |
| `DoRaw` | Raw `[]byte` response for custom unmarshaling |
| `DoWithAcceptHeader` | Non-JSON responses (e.g., `text/csv`) |

**Example — adding to a proxy struct:**

```go
type myProxy struct {
clientConfig *platformclientv2.Configuration
myApi *platformclientv2.MyApi
customApiClient *customapi.Client
}

func newMyProxy(clientConfig *platformclientv2.Configuration) *myProxy {
return &myProxy{
clientConfig: clientConfig,
myApi: platformclientv2.NewMyApiWithConfig(clientConfig),
customApiClient: customapi.NewClient(clientConfig, ResourceType),
}
}
```

**Example — making a call:**

```go
// Typed response
result, resp, err := customapi.Do[platformclientv2.MyEntity](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/my/endpoint", nil, nil)

// With query params
qp := customapi.NewQueryParams(map[string]string{"pageSize": "100", "pageNumber": "1"})
result, resp, err := customapi.Do[platformclientv2.MyListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/my/endpoint", nil, qp)

// Delete with no response
resp, err := customapi.DoNoResponse(ctx, p.customApiClient, customapi.MethodDelete, "/api/v2/my/endpoint/"+id, nil, nil)
```

See the [package documentation](genesyscloud/custom_api_client/custom_api_client.go) for full details and additional examples.

### Documentation Generation

The provider documentation is automatically generated from resource schemas and example files. Understanding this process is crucial when adding new resources.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package architect_datatable

import (
"context"
"encoding/json"
"errors"
"net/http"

customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client"
"github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider"

"github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2"
Expand All @@ -23,6 +21,7 @@ type getAllArchitectDatatableFunc func(ctx context.Context, p *architectDatatabl
type architectDatatableProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
customApiClient *customapi.Client
createOrUpdateArchitectDatatableAttr createOrUpdateArchitectDatatableFunc
getArchitectDatatableAttr getArchitectDatatableFunc
getAllArchitectDatatableAttr getAllArchitectDatatableFunc
Expand All @@ -34,6 +33,7 @@ func newArchitectDatatableProxy(clientConfig *platformclientv2.Configuration) *a
return &architectDatatableProxy{
clientConfig: clientConfig,
architectApi: api,
customApiClient: customapi.NewClient(clientConfig, ResourceType),
createOrUpdateArchitectDatatableAttr: createOrUpdateArchitectDatatableFn,
getArchitectDatatableAttr: getArchitectDatatableFn,
getAllArchitectDatatableAttr: getAllArchitectDatatableFn,
Expand Down Expand Up @@ -73,78 +73,24 @@ func createOrUpdateArchitectDatatableFn(ctx context.Context, p *architectDatatab
// Set resource context for SDK debug logging
ctx = provider.EnsureResourceContext(ctx, ResourceType)

apiClient := &p.architectApi.Configuration.APIClient
action := http.MethodPost

// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables"
method := customapi.MethodPost
path := "/api/v2/flows/datatables"

if !createAction {
action = http.MethodPut
method = customapi.MethodPut
path += "/" + *datatable.Id
}

headerParams := make(map[string]string)

// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}

headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"

var successPayload *Datatable
response, err := apiClient.CallAPI(path, action, datatable, headerParams, nil, nil, "", nil, "")

if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal([]byte(response.RawBody), &successPayload)
}

return successPayload, response, err
return customapi.Do[Datatable](ctx, p.customApiClient, method, path, datatable, nil)
}

func getArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) {
// Set resource context for SDK debug logging
ctx = provider.EnsureResourceContext(ctx, ResourceType)

apiClient := &p.architectApi.Configuration.APIClient
queryParams := customapi.NewQueryParams(map[string]string{"expand": expanded})

// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId

headerParams := make(map[string]string)
queryParams := make(map[string]string)

// oauth required
if p.architectApi.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
}
// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}

queryParams["expand"] = apiClient.ParameterToString(expanded, "")

headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"

var successPayload *Datatable
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "")
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal(response.RawBody, &successPayload)
}
return successPayload, response, err
return customapi.Do[Datatable](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows/datatables/"+datatableId, nil, queryParams)
}

func deleteArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string) (*platformclientv2.APIResponse, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package architect_datatable_row

import (
"context"
"encoding/json"
"errors"
"log"
"net/http"

"github.com/mitchellh/mapstructure"
customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client"
"github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider"
rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache"

Expand All @@ -26,6 +24,7 @@ type deleteArchitectDatatableRowFunc func(ctx context.Context, p *architectDatat
type architectDatatableRowProxy struct {
clientConfig *platformclientv2.Configuration
architectApi *platformclientv2.ArchitectApi
customApiClient *customapi.Client
createArchitectDatatableRowAttr createArchitectDatatableRowFunc
getArchitectDatatableAttr getArchitectDatatableFunc
getAllArchitectDatatableAttr getAllArchitectDatatableFunc
Expand All @@ -47,6 +46,7 @@ func newArchitectDatatableRowProxy(clientConfig *platformclientv2.Configuration)
return &architectDatatableRowProxy{
clientConfig: clientConfig,
architectApi: api,
customApiClient: customapi.NewClient(clientConfig, ResourceType),
dataTableRowCache: dataTableRowCache,
dataTableCache: dataTableCache,
getArchitectDatatableAttr: getArchitectDatatableFn,
Expand Down Expand Up @@ -149,38 +149,9 @@ func getArchitectDatatableFn(ctx context.Context, p *architectDatatableRowProxy,
return eg, nil, nil
}

apiClient := &p.architectApi.Configuration.APIClient
queryParams := customapi.NewQueryParams(map[string]string{"expand": expanded})

// create path and map variables
path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId

headerParams := make(map[string]string)
queryParams := make(map[string]string)

// oauth required
if p.architectApi.Configuration.AccessToken != "" {
headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken
}
// add default headers if any
for key := range p.architectApi.Configuration.DefaultHeader {
headerParams[key] = p.architectApi.Configuration.DefaultHeader[key]
}

queryParams["expand"] = apiClient.ParameterToString(expanded, "")

headerParams["Content-Type"] = "application/json"
headerParams["Accept"] = "application/json"

var successPayload *Datatable
response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "")
if err != nil {
// Nothing special to do here, but do avoid processing the response
} else if response.Error != nil {
err = errors.New(response.ErrorMessage)
} else {
err = json.Unmarshal(response.RawBody, &successPayload)
}
return successPayload, response, err
return customapi.Do[Datatable](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows/datatables/"+datatableId, nil, queryParams)
}

func getAllArchitectDatatableRowsFn(ctx context.Context, p *architectDatatableRowProxy, tableId string) (*[]map[string]interface{}, *platformclientv2.APIResponse, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package architect_flow

import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"

customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client"
"github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider"
rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache"
"github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util"
Expand All @@ -33,8 +31,9 @@ type getExportJobStatusByIdFunc func(a *architectFlowProxy, jobId string) (*plat
type pollExportJobForDownloadUrlFunc func(a *architectFlowProxy, jobId string, timeoutInSeconds float64) (downloadUrl string, err error)

type architectFlowProxy struct {
clientConfig *platformclientv2.Configuration
api *platformclientv2.ArchitectApi
clientConfig *platformclientv2.Configuration
customApiClient *customapi.Client
api *platformclientv2.ArchitectApi

getArchitectFlowAttr getArchitectFunc
getAllArchitectFlowsAttr getAllArchitectFlowsFunc
Expand All @@ -56,8 +55,9 @@ var flowCache = rc.NewResourceCache[platformclientv2.Flow]()
func newArchitectFlowProxy(clientConfig *platformclientv2.Configuration) *architectFlowProxy {
api := platformclientv2.NewArchitectApiWithConfig(clientConfig)
return &architectFlowProxy{
clientConfig: clientConfig,
api: api,
clientConfig: clientConfig,
customApiClient: customapi.NewClient(clientConfig, ResourceType),
api: api,

getArchitectFlowAttr: getArchitectFlowFn,
getAllArchitectFlowsAttr: getAllArchitectFlowsFn,
Expand Down Expand Up @@ -322,49 +322,35 @@ func getArchitectFlowJobsFn(ctx context.Context, p *architectFlowProxy, jobId st

// getAllArchitectFlowsFn is the implementation function for GetAllFlows
func getAllArchitectFlowsFn(ctx context.Context, p *architectFlowProxy, name string, varType []string) (*[]platformclientv2.Flow, *platformclientv2.APIResponse, error) {
baseURL := p.clientConfig.BasePath + "/api/v2/flows"
ctx = provider.EnsureResourceContext(ctx, ResourceType)
var allFlows []platformclientv2.Flow

params := url.Values{}
queryParams := customapi.QueryParams{}
queryParams.Set("pageSize", "100")
queryParams.Set("pageNumber", "1")
queryParams.Set("includeSchemas", "true")
if name != "" {
params.Add("name", name)
queryParams.Set("name", name)
}
for _, t := range varType {
params.Add("type", t)
}
params.Add("includeSchemas", "true")

client := &http.Client{}
var allFlows []platformclientv2.Flow

params.Set("pageSize", "100")
params.Set("pageNumber", "1")

u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
queryParams.Add("type", t)
}
u.RawQuery = params.Encode()

flows, apiResp, err := makeFlowRequest(ctx, client, u.String(), p)
flows, apiResp, err := customapi.Do[platformclientv2.Flowentitylisting](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows", nil, queryParams)
if err != nil {
return nil, apiResp, err
}

if flows.Entities != nil {
allFlows = append(allFlows, *flows.Entities...)
}

for pageNum := 2; pageNum <= *flows.PageCount; pageNum++ {
ctx = provider.EnsureResourceContext(ctx, ResourceType)
params.Set("pageNumber", fmt.Sprintf("%d", pageNum))
u.RawQuery = params.Encode()

pageFlows, _, err := makeFlowRequest(ctx, client, u.String(), p)
queryParams.Set("pageNumber", fmt.Sprintf("%d", pageNum))
pageFlows, _, err := customapi.Do[platformclientv2.Flowentitylisting](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows", nil, queryParams)
if err != nil {
return nil, apiResp, err
}

if pageFlows.Entities != nil {
allFlows = append(allFlows, *pageFlows.Entities...)
}
Expand All @@ -373,50 +359,9 @@ func getAllArchitectFlowsFn(ctx context.Context, p *architectFlowProxy, name str
for _, flow := range allFlows {
rc.SetCache(p.flowCache, *flow.Id, flow)
}

return &allFlows, apiResp, nil
}

func makeFlowRequest(ctx context.Context, client *http.Client, url string, p *architectFlowProxy) (*platformclientv2.Flowentitylisting, *platformclientv2.APIResponse, error) {
// Set resource context for SDK debug logging before creating HTTP request
ctx = provider.EnsureResourceContext(ctx, ResourceType)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, nil, err
}

req.Header.Set("Authorization", "Bearer "+p.clientConfig.AccessToken)
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("error reading response: %v", err)
}

apiResp := &platformclientv2.APIResponse{
StatusCode: resp.StatusCode,
Response: resp,
}

if resp.StatusCode >= 400 {
return nil, apiResp, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody))
}

var flows platformclientv2.Flowentitylisting
if err := json.Unmarshal(respBody, &flows); err != nil {
return nil, apiResp, err
}

return &flows, apiResp, nil
}

// generateDownloadUrlFn is the implementation function for the generateDownloadUrl method
func generateDownloadUrlFn(a *architectFlowProxy, flowId string) (downloadUrl string, err error) {
defer func() {
Expand Down
Loading
Loading