feat(client): add basic client, model
This commit is contained in:
commit
4429f3fa18
3 changed files with 428 additions and 0 deletions
0
Dockerfile
Normal file
0
Dockerfile
Normal file
303
client/client.go
Normal file
303
client/client.go
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrResourceNotFound = fmt.Errorf("resource not found")
|
||||||
|
|
||||||
|
type EdgeConnect struct {
|
||||||
|
BaseURL string
|
||||||
|
HttpClient *http.Client
|
||||||
|
Credentials Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
type Credentials struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) RetrieveToken(ctx context.Context) (string, error) {
|
||||||
|
json_data, err := json.Marshal(map[string]string{
|
||||||
|
"username": e.Credentials.Username,
|
||||||
|
"password": e.Credentials.Password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, "POST", e.BaseURL+"/api/v1/login", bytes.NewBuffer(json_data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := e.HttpClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var respData struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&respData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return respData.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) CreateApp(ctx context.Context, input NewAppInput) error {
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := call[App](ctx, e, "/api/v1/auth/ctrl/CreateApp", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) ShowApp(ctx context.Context, appkey AppKey, region string) (App, error) {
|
||||||
|
input := struct {
|
||||||
|
App App `json:"App"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
App: App{Key: appkey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return App{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[App](ctx, e, "/api/v1/auth/ctrl/ShowApp", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return App{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if responses.StatusCode == http.StatusNotFound {
|
||||||
|
return App{}, fmt.Errorf("Error retrieving App: %w", ErrResourceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responses.IsSuccessful() {
|
||||||
|
return App{}, responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
apps := responses.GetData()
|
||||||
|
if len(apps) > 0 {
|
||||||
|
return apps[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return App{}, fmt.Errorf("could not find app with region/key: %s/%v: %w", region, appkey, ErrResourceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) ShowApps(ctx context.Context, appkey AppKey, region string) ([]App, error) {
|
||||||
|
input := struct {
|
||||||
|
App App `json:"App"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
App: App{Key: appkey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[App](ctx, e, "/api/v1/auth/ctrl/ShowApp", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responses.IsSuccessful() && responses.StatusCode != http.StatusNotFound {
|
||||||
|
return nil, responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses.GetData(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) DeleteApp(ctx context.Context, appkey AppKey, region string) error {
|
||||||
|
input := struct {
|
||||||
|
App App `json:"App"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
App: App{Key: appkey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := call[App](ctx, e, "/api/v1/auth/ctrl/DeleteApp", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.IsSuccessful() && response.StatusCode != 404 {
|
||||||
|
return response.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) CreateAppInstance(ctx context.Context, input NewAppInstanceInput) error {
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal NewAppInstanceInput %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[AppInstance](ctx, e, "/api/v1/auth/ctrl/CreateAppInst", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) ShowAppInstance(ctx context.Context, appinstkey AppInstanceKey, region string) (AppInstance, error) {
|
||||||
|
input := struct {
|
||||||
|
App AppInstance `json:"appinst"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
App: AppInstance{Key: appinstkey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return AppInstance{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[AppInstance](ctx, e, "/api/v1/auth/ctrl/ShowAppInst", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return AppInstance{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if responses.StatusCode == http.StatusNotFound {
|
||||||
|
return AppInstance{}, fmt.Errorf("Error retrieving AppInstance: %w", ErrResourceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responses.IsSuccessful() {
|
||||||
|
return AppInstance{}, responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := responses.GetData()
|
||||||
|
if len(data) > 0 {
|
||||||
|
return data[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppInstance{}, fmt.Errorf("could not find app instance: %v: %w", responses, ErrResourceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) ShowAppInstances(ctx context.Context, appinstkey AppInstanceKey, region string) ([]AppInstance, error) {
|
||||||
|
input := struct {
|
||||||
|
App AppInstance `json:"appinst"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
App: AppInstance{Key: appinstkey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[AppInstance](ctx, e, "/api/v1/auth/ctrl/ShowAppInst", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responses.IsSuccessful() && responses.StatusCode != http.StatusNotFound {
|
||||||
|
return nil, responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses.GetData(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EdgeConnect) DeleteAppInstance(ctx context.Context, appinstancekey AppInstanceKey, region string) error {
|
||||||
|
input := struct {
|
||||||
|
AppInstance AppInstance `json:"appinst"`
|
||||||
|
Region string `json:"Region"`
|
||||||
|
}{
|
||||||
|
AppInstance: AppInstance{Key: appinstancekey},
|
||||||
|
Region: region,
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := call[AppInstance](ctx, e, "/api/v1/auth/ctrl/DeleteAppInst", json_data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func call[T Message](ctx context.Context, client *EdgeConnect, path string, body []byte) (Responses[T], error) {
|
||||||
|
token, err := client.RetrieveToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return Responses[T]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", client.BaseURL, path), bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return Responses[T]{}, err
|
||||||
|
}
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
|
||||||
|
resp, err := client.HttpClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return Responses[T]{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responses := Responses[T]{}
|
||||||
|
responses.StatusCode = resp.StatusCode
|
||||||
|
|
||||||
|
if responses.StatusCode == http.StatusNotFound {
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
for {
|
||||||
|
var d Response[T]
|
||||||
|
if err := decoder.Decode(&d); err != nil {
|
||||||
|
if err.Error() == "EOF" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("Error in call %s: %v", path, err)
|
||||||
|
return Responses[T]{}, fmt.Errorf("Error in call %s: %w", path, err)
|
||||||
|
}
|
||||||
|
responses.Responses = append(responses.Responses, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("call(): %s resulting in http status %v and %v responses\n", path, resp.StatusCode, len(responses.GetMessages()))
|
||||||
|
for i, v := range responses.GetMessages() {
|
||||||
|
log.Printf("call(): response[%v]: %s\n", i, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
125
client/models.go
Normal file
125
client/models.go
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Responses[T Message] struct {
|
||||||
|
Responses []Response[T]
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message interface {
|
||||||
|
GetMessage() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Responses[T]) GetData() []T {
|
||||||
|
var data []T
|
||||||
|
for _, v := range r.Responses {
|
||||||
|
if v.HasData() {
|
||||||
|
data = append(data, v.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Responses[T]) GetMessages() []string {
|
||||||
|
var messages []string
|
||||||
|
for _, v := range r.Responses {
|
||||||
|
if v.IsMessage() {
|
||||||
|
messages = append(messages, v.Data.GetMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Responses[T]) IsSuccessful() bool {
|
||||||
|
return r.StatusCode < 400 && r.StatusCode > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Responses[T]) Error() error {
|
||||||
|
if r.IsSuccessful() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error with status code %v and messages %v", r.StatusCode, r.GetMessages())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response[T Message] struct {
|
||||||
|
Data T `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *Response[T]) HasData() bool {
|
||||||
|
return !res.IsMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *Response[T]) IsMessage() bool {
|
||||||
|
return res.Data.GetMessage() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewAppInstanceInput struct {
|
||||||
|
Region string `json:"region"`
|
||||||
|
AppInst AppInstance `json:"appinst"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type msg struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg msg) GetMessage() string {
|
||||||
|
return msg.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppInstance struct {
|
||||||
|
msg `json:",inline"`
|
||||||
|
Key AppInstanceKey `json:"key"`
|
||||||
|
AppKey AppKey `json:"app_key,omitzero"`
|
||||||
|
Flavor Flavor `json:"flavor,omitzero"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
PowerState string `json:"power_state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppInstanceKey struct {
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CloudletKey CloudletKey `json:"cloudlet_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudletKey struct {
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppKey struct {
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flavor struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewAppInput struct {
|
||||||
|
Region string `json:"region"`
|
||||||
|
App App `json:"app"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecurityRule struct {
|
||||||
|
PortRangeMax int `json:"port_range_max"`
|
||||||
|
PortRangeMin int `json:"port_range_min"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
RemoteCIDR string `json:"remote_cidr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
msg `json:",inline"`
|
||||||
|
Key AppKey `json:"key"`
|
||||||
|
Deployment string `json:"deployment,omitempty"`
|
||||||
|
ImageType string `json:"image_type,omitempty"`
|
||||||
|
ImagePath string `json:"image_path,omitempty"`
|
||||||
|
AllowServerless bool `json:"allow_serverless,omitempty"`
|
||||||
|
DefaultFlavor Flavor `json:"defaultFlavor,omitempty"`
|
||||||
|
ServerlessConfig any `json:"serverless_config,omitempty"`
|
||||||
|
DeploymentGenerator string `json:"deployment_generator,omitempty"`
|
||||||
|
DeploymentManifest string `json:"deployment_manifest,omitempty"`
|
||||||
|
RequiredOutboundConnections []SecurityRule `json:"required_outbound_connections"`
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue