internal/client/connections/custom-connections.go (270 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package connections
import (
"encoding/json"
"fmt"
"internal/apiclient"
"net/url"
"path"
"strconv"
"strings"
"time"
)
type customConnectorOverrides struct {
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"`
CustomConnectorType string `json:"customConnectorType,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
CustomConnectorVersion customConnectorVersionRequest `json:"customConnectorVersion,omitempty"`
}
type customConnectorVersionRequest struct {
Labels map[string]string `json:"labels,omitempty"`
ServiceAccount *string `json:"serviceAccount,omitempty"`
EnableBackendDestinationConfig bool `json:"enableBackendDestinationConfig,omitempty"`
SpecLocation string `json:"specLocation,omitempty"`
AuthConfig *authConfig `json:"authConfig,omitempty"`
DestinationConfigs []destinationConfig `json:"destinationConfigs,omitempty"`
BackendVariableTemplates []configVariableTemplate `json:"backendVariableTemplates,omitempty"`
}
type configVariableTemplate struct {
Key string `json:"key,omitempty"`
ValueType string `json:"valueType,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"`
ValidationRegex string `json:"validationRegex,omitempty"`
Required bool `json:"required,omitempty"`
IsAdvanced bool `json:"isAdvanced,omitempty"`
LocationType string `json:"locationType,omitempty"`
}
const waitTime = 1 * time.Second
// CreateCustom
func CreateCustom(name string, description string, displayName string,
connType string, labels map[string]string,
) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
q := u.Query()
q.Set("customConnectorId", name)
customConnect := []string{}
customConnect = append(customConnect, "\"displayName\":"+"\""+displayName+"\"")
customConnect = append(customConnect, "\"description\":"+"\""+description+"\"")
customConnect = append(customConnect, "\"customConnectorType\":"+"\""+connType+"\"")
if len(labels) > 0 {
l := []string{}
for key, value := range labels {
l = append(l, "\""+key+"\":\""+value+"\"")
}
labelStr := "\"labels\":{" + strings.Join(l, ",") + "}"
customConnect = append(customConnect, labelStr)
}
payload := "{" + strings.Join(customConnect, ",") + "}"
u.RawQuery = q.Encode()
respBody, err = apiclient.HttpClient(u.String(), payload)
return respBody, err
}
// DeleteCustom
func DeleteCustom(name string, force bool) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
u.Path = path.Join(u.Path, name)
q := u.Query()
q.Set("force", strconv.FormatBool(force))
u.RawQuery = q.Encode()
respBody, err = apiclient.HttpClient(u.String(), "", "DELETE")
return respBody, err
}
// GetCustom
func GetCustom(name string) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
u.Path = path.Join(u.Path, name)
respBody, err = apiclient.HttpClient(u.String())
return respBody, err
}
// ListCustom
func ListCustom(pageSize int, pageToken string, filter string) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
q := u.Query()
if pageSize != -1 {
q.Set("pageSize", strconv.Itoa(pageSize))
}
if pageToken != "" {
q.Set("pageToken", pageToken)
}
if filter != "" {
q.Set("filter", filter)
}
u.RawQuery = q.Encode()
respBody, err = apiclient.HttpClient(u.String())
return respBody, err
}
// CreateCustomVersion
func CreateCustomVersion(connName string, versionName string, content []byte,
serviceAccountName string, serviceAccountProject string,
) (respBody []byte, err error) {
c := customConnectorVersionRequest{}
if err = json.Unmarshal(content, &c); err != nil {
return nil, err
}
// service account overrides have been provided, use them
if serviceAccountName != "" {
// set the project id if one was not presented
if serviceAccountProject == "" {
serviceAccountProject = apiclient.GetProjectID()
}
serviceAccountName = fmt.Sprintf("%s@%s.iam.gserviceaccount.com", serviceAccountName, serviceAccountProject)
// create the SA if it doesn't exist
if err = apiclient.CreateServiceAccount(serviceAccountName); err != nil {
return nil, err
}
}
if c.ServiceAccount != nil && serviceAccountName != "" {
*c.ServiceAccount = serviceAccountName
}
if content, err = json.Marshal(c); err != nil {
return nil, err
}
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
u.Path = path.Join(u.Path, connName, "customConnectorVersions")
q := u.Query()
q.Set("customConnectorVersionId", versionName)
u.RawQuery = q.Encode()
respBody, err = apiclient.HttpClient(u.String(), string(content))
return respBody, err
}
func GetCustomVersion(connName string, connVersion string, overrides bool) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
u.Path = path.Join(u.Path, connName, "customConnectorVersions", connVersion)
if overrides {
apiclient.ClientPrintHttpResponse.Set(false)
c := customConnectorOverrides{}
connRespBody, err := GetCustom(connName)
if err != nil {
return nil, err
}
if err = json.Unmarshal(connRespBody, &c); err != nil {
return nil, err
}
respBody, err = apiclient.HttpClient(u.String())
if err != nil {
return nil, err
}
apiclient.ClientPrintHttpResponse.Set(apiclient.GetCmdPrintHttpResponseSetting())
cVerReq := customConnectorVersionRequest{}
if err = json.Unmarshal(respBody, &cVerReq); err != nil {
return nil, err
}
// remove the default p4s from the overrides
if cVerReq.ServiceAccount != nil && strings.Contains(*cVerReq.ServiceAccount, "-compute@developer.gserviceaccount.com") {
cVerReq.ServiceAccount = nil
}
c.CustomConnectorVersion = cVerReq
overridesResp, err := json.Marshal(c)
if err != nil {
return nil, err
}
apiclient.PrettyPrint(overridesResp)
return overridesResp, nil
}
respBody, err = apiclient.HttpClient(u.String())
return respBody, err
}
func ListCustomVersions(connName string, pageSize int, pageToken string) (respBody []byte, err error) {
u, _ := url.Parse(apiclient.GetBaseCustomConnectorURL())
u.Path = path.Join(u.Path, connName, "customConnectorVersions")
q := u.Query()
if pageSize != -1 {
q.Set("pageSize", strconv.Itoa(pageSize))
}
if pageToken != "" {
q.Set("pageToken", pageToken)
}
u.RawQuery = q.Encode()
respBody, err = apiclient.HttpClient(u.String())
return respBody, err
}
func GetCustomFromConnection(contents []byte) (respBody []byte, err error) {
c := connection{}
err = json.Unmarshal(respBody, &c)
if err != nil {
return nil, err
}
if c.ConnectorDetails.Provider != "customconnector" {
return nil, fmt.Errorf("connector is not of type customconnector")
}
respBody, err = GetCustomVersion(getConnectorName(*c.ConnectorVersion), getConnectorVersionId(*c.ConnectorVersion), false)
return respBody, err
}
func IsCustomConnector(contents []byte) bool {
c := connection{}
err := json.Unmarshal(contents, &c)
if err != nil {
return false
}
if c.ConnectorDetails.Provider != "customconnector" {
return false
}
return true
}
func CreateCustomWithVersion(name string, version string, contents []byte,
serviceAccount string, serviceAccountProject string,
) (err error) {
c := customConnectorOverrides{}
err = json.Unmarshal(contents, &c)
if err != nil {
return err
}
createCustomBody, err := CreateCustom(name, c.Description, c.DisplayName, c.CustomConnectorType, c.Labels)
if err != nil {
return err
}
var createCustomMap map[string]interface{}
err = json.Unmarshal(createCustomBody, &createCustomMap)
if err != nil {
return err
}
// wait for custom connection to be created
if len(strings.Split(fmt.Sprintf("%s", createCustomMap["name"]), "/")) > 4 {
operationName := strings.Split(fmt.Sprintf("%s", createCustomMap["name"]), "/")[5]
err = waitForCustom(operationName)
if err != nil {
return err
}
}
connectionVersionContents, err := json.Marshal(c.CustomConnectorVersion)
if err != nil {
return err
}
_, err = CreateCustomVersion(name, version, connectionVersionContents, serviceAccount, serviceAccountProject)
if err != nil {
return err
}
// wait for custom version to be created
err = waitForCustomVersion(name, version)
return err
}
func waitForCustom(operationName string) error {
var err error
var respBody []byte
var respMap map[string]interface{}
region := apiclient.GetRegion()
defer apiclient.SetRegion(region)
apiclient.SetRegion("global")
for {
if respBody, err = GetOperation(operationName); err != nil {
return err
}
if err = json.Unmarshal(respBody, &respMap); err != nil {
return err
}
done := respMap["done"].(bool)
if done {
time.Sleep(waitTime)
return nil
}
time.Sleep(waitTime)
}
}
func waitForCustomVersion(name string, version string) error {
var err error
var respBody []byte
var respMap map[string]string
for {
if respBody, err = GetCustomVersion(name, version, false); err != nil {
return err
}
if err = json.Unmarshal(respBody, &respMap); err != nil {
return err
}
if respMap["state"] == "ACTIVE" {
time.Sleep(waitTime)
return nil
}
time.Sleep(waitTime)
}
}