mirror of
https://gitlab.com/psuapp/psu.git
synced 2024-08-30 18:12:34 +00:00
Split common package into common, client, and util packages
This commit is contained in:
297
common/client.go
297
common/client.go
@ -1,299 +1,18 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/client"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cachedClient PortainerClient
|
||||
|
||||
type ClientConfig struct {
|
||||
Url string
|
||||
User string
|
||||
Password string
|
||||
Token string
|
||||
DoNotUseToken bool
|
||||
}
|
||||
|
||||
type PortainerClient interface {
|
||||
Authenticate() (token string, err error)
|
||||
GetEndpoints() ([]EndpointSubset, error)
|
||||
GetStacks(swarmId string, endpointId uint32) ([]Stack, error)
|
||||
CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId string) error
|
||||
CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId string) error
|
||||
UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId string) error
|
||||
DeleteStack(stackId uint32) error
|
||||
GetStackFileContent(stackId uint32) (content string, err error)
|
||||
GetEndpointDockerInfo(endpointId string) (info map[string]interface{}, err error)
|
||||
GetStatus() (Status, error)
|
||||
}
|
||||
|
||||
type PortainerClientImp struct {
|
||||
httpClient *http.Client
|
||||
url *url.URL
|
||||
user string
|
||||
password string
|
||||
token string
|
||||
doNotUseToken bool
|
||||
}
|
||||
|
||||
// Check if an http.Response object has errors
|
||||
func checkResponseForErrors(resp *http.Response) error {
|
||||
if 300 <= resp.StatusCode {
|
||||
// Guess it's a GenericError
|
||||
respBody := GenericError{}
|
||||
err := json.NewDecoder(resp.Body).Decode(&respBody)
|
||||
if err != nil {
|
||||
// It's not a GenericError
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return errors.New(string(bodyBytes))
|
||||
}
|
||||
return &respBody
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do an http request
|
||||
func (n *PortainerClientImp) do(uri, method string, request io.Reader, requestType string, headers http.Header) (resp *http.Response, err error) {
|
||||
requestUrl, err := n.url.Parse(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, requestUrl.String(), request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
req.Header = headers
|
||||
}
|
||||
|
||||
if request != nil {
|
||||
req.Header.Set("Content-Type", requestType)
|
||||
}
|
||||
|
||||
if !n.doNotUseToken {
|
||||
if n.token == "" {
|
||||
n.token, err = n.Authenticate()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
PrintDebug(fmt.Sprintf("Auth token: %s", n.token))
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+n.token)
|
||||
}
|
||||
|
||||
PrintDebugRequest("Request", req)
|
||||
|
||||
resp, err = n.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = checkResponseForErrors(resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
PrintDebugResponse("Response", resp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Do a JSON http request
|
||||
func (n *PortainerClientImp) doJSON(uri, method string, request interface{}, response interface{}) error {
|
||||
var body io.Reader
|
||||
|
||||
if request != nil {
|
||||
reqBodyBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = bytes.NewReader(reqBodyBytes)
|
||||
}
|
||||
|
||||
resp, err := n.do(uri, method, body, "application/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
d := json.NewDecoder(resp.Body)
|
||||
err := d.Decode(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate a user to get an auth token
|
||||
func (n *PortainerClientImp) Authenticate() (token string, err error) {
|
||||
PrintVerbose("Getting auth token...")
|
||||
|
||||
reqBody := AuthenticateUserRequest{
|
||||
Username: n.user,
|
||||
Password: n.password,
|
||||
}
|
||||
|
||||
respBody := AuthenticateUserResponse{}
|
||||
|
||||
previousDoNotUseTokenValue := n.doNotUseToken
|
||||
n.doNotUseToken = true
|
||||
|
||||
err = n.doJSON("auth", http.MethodPost, &reqBody, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.doNotUseToken = previousDoNotUseTokenValue
|
||||
|
||||
token = respBody.Jwt
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get endpoints
|
||||
func (n *PortainerClientImp) GetEndpoints() (endpoints []EndpointSubset, err error) {
|
||||
PrintVerbose("Getting endpoints...")
|
||||
err = n.doJSON("endpoints", http.MethodGet, nil, &endpoints)
|
||||
return
|
||||
}
|
||||
|
||||
// Get stacks, optionally filtered by swarmId and endpointId
|
||||
func (n *PortainerClientImp) GetStacks(swarmId string, endpointId uint32) (stacks []Stack, err error) {
|
||||
PrintVerbose("Getting stacks...")
|
||||
|
||||
filter := StackListFilter{
|
||||
SwarmId: swarmId,
|
||||
EndpointId: endpointId,
|
||||
}
|
||||
|
||||
filterJsonBytes, _ := json.Marshal(filter)
|
||||
filterJsonString := string(filterJsonBytes)
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks?filters=%s", filterJsonString), http.MethodGet, nil, &stacks)
|
||||
return
|
||||
}
|
||||
|
||||
// Create swarm stack
|
||||
func (n *PortainerClientImp) CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId string) (err error) {
|
||||
PrintVerbose("Deploying stack...")
|
||||
|
||||
reqBody := StackCreateRequest{
|
||||
Name: stackName,
|
||||
Env: environmentVariables,
|
||||
SwarmID: swarmClusterId,
|
||||
StackFileContent: stackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%s", 1, "string", endpointId), http.MethodPost, &reqBody, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Create compose stack
|
||||
func (n *PortainerClientImp) CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId string) (err error) {
|
||||
PrintVerbose("Deploying stack...")
|
||||
|
||||
reqBody := StackCreateRequest{
|
||||
Name: stackName,
|
||||
Env: environmentVariables,
|
||||
StackFileContent: stackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%s", 2, "string", endpointId), http.MethodPost, &reqBody, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Update stack
|
||||
func (n *PortainerClientImp) UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId string) (err error) {
|
||||
PrintVerbose("Updating stack...")
|
||||
|
||||
reqBody := StackUpdateRequest{
|
||||
Env: environmentVariables,
|
||||
StackFileContent: stackFileContent,
|
||||
Prune: prune,
|
||||
}
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks/%v?endpointId=%s", stack.Id, endpointId), http.MethodPut, &reqBody, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete stack
|
||||
func (n *PortainerClientImp) DeleteStack(stackId uint32) (err error) {
|
||||
PrintVerbose("Deleting stack...")
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks/%d", stackId), http.MethodDelete, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get stack file content
|
||||
func (n *PortainerClientImp) GetStackFileContent(stackId uint32) (content string, err error) {
|
||||
PrintVerbose("Getting stack file content...")
|
||||
|
||||
var respBody StackFileInspectResponse
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("stacks/%v/file", stackId), http.MethodGet, nil, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content = respBody.StackFileContent
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get endpoint Docker info
|
||||
func (n *PortainerClientImp) GetEndpointDockerInfo(endpointId string) (info map[string]interface{}, err error) {
|
||||
PrintVerbose("Getting endpoint Docker info...")
|
||||
|
||||
err = n.doJSON(fmt.Sprintf("endpoints/%v/docker/info", endpointId), http.MethodGet, nil, &info)
|
||||
return
|
||||
}
|
||||
|
||||
// Get Portainer status info
|
||||
func (n *PortainerClientImp) GetStatus() (status Status, err error) {
|
||||
err = n.doJSON("status", http.MethodGet, nil, &status)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new client
|
||||
func NewClient(httpClient *http.Client, config ClientConfig) (c PortainerClient, err error) {
|
||||
apiUrl, err := url.Parse(strings.TrimRight(config.Url, "/") + "/api/")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c = &PortainerClientImp{
|
||||
httpClient: httpClient,
|
||||
url: apiUrl,
|
||||
user: config.User,
|
||||
password: config.Password,
|
||||
token: config.Token,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
var cachedClient client.PortainerClient
|
||||
|
||||
// Get the cached client or a new one
|
||||
func GetClient() (c PortainerClient, err error) {
|
||||
func GetClient() (c client.PortainerClient, err error) {
|
||||
if cachedClient == nil {
|
||||
cachedClient, err = GetDefaultClient()
|
||||
if err != nil {
|
||||
@ -304,13 +23,13 @@ func GetClient() (c PortainerClient, err error) {
|
||||
}
|
||||
|
||||
// Get the default client
|
||||
func GetDefaultClient() (c PortainerClient, err error) {
|
||||
return NewClient(GetDefaultHttpClient(), GetDefaultClientConfig())
|
||||
func GetDefaultClient() (c client.PortainerClient, err error) {
|
||||
return client.NewClient(GetDefaultHttpClient(), GetDefaultClientConfig())
|
||||
}
|
||||
|
||||
// Get the default config for a client
|
||||
func GetDefaultClientConfig() ClientConfig {
|
||||
return ClientConfig{
|
||||
func GetDefaultClientConfig() client.ClientConfig {
|
||||
return client.ClientConfig{
|
||||
Url: viper.GetString("url"),
|
||||
User: viper.GetString("user"),
|
||||
Password: viper.GetString("password"),
|
||||
|
@ -1,13 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// CheckError checks if an error occurred (it's not nil)
|
||||
func CheckError(err error) {
|
||||
if err != nil {
|
||||
log.Fatalln(fmt.Sprintf("Error: %s", err.Error()))
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func PrintVerbose(a ...interface{}) {
|
||||
if viper.GetBool("verbose") {
|
||||
log.Println(a)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintDebug(a ...interface{}) {
|
||||
if viper.GetBool("debug") {
|
||||
log.Println(a)
|
||||
}
|
||||
}
|
||||
|
||||
func NewTabWriter(headers []string) (*tabwriter.Writer, error) {
|
||||
writer := tabwriter.NewWriter(os.Stdout, 20, 2, 3, ' ', 0)
|
||||
_, err := fmt.Fprintln(writer, strings.Join(headers, "\t"))
|
||||
if err != nil {
|
||||
return &tabwriter.Writer{}, err
|
||||
}
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
func PrintDebugRequest(title string, req *http.Request) error {
|
||||
if viper.GetBool("debug") {
|
||||
var bodyString string
|
||||
if req.Body != nil {
|
||||
bodyBytes, err := ioutil.ReadAll(req.Body)
|
||||
defer req.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyString = string(bodyBytes)
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
}
|
||||
|
||||
PrintDebug(fmt.Sprintf(`%s
|
||||
---
|
||||
Method: %s
|
||||
URL: %s
|
||||
Body:
|
||||
%s
|
||||
---`, title, req.Method, req.URL.String(), string(bodyString)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintDebugResponse(title string, resp *http.Response) error {
|
||||
if viper.GetBool("debug") {
|
||||
var bodyString string
|
||||
if resp.Body != nil {
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyString = string(bodyBytes)
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
}
|
||||
|
||||
PrintDebug(fmt.Sprintf(`%s
|
||||
---
|
||||
Status: %s
|
||||
Body:
|
||||
%s
|
||||
---`, title, resp.Status, bodyString))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package common
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Stack struct {
|
||||
// In the API documentation this field is a String,
|
||||
// but it's returned as a number
|
||||
Id uint32
|
||||
Name string
|
||||
Type uint8 // 1 for a Swarm stack, 2 for a Compose stack
|
||||
EndpointID uint
|
||||
EntryPoint string
|
||||
SwarmID string
|
||||
ProjectPath string
|
||||
Env []StackEnv
|
||||
}
|
||||
|
||||
func (s *Stack) GetTranslatedStackType() string {
|
||||
switch s.Type {
|
||||
case 1:
|
||||
return "swarm"
|
||||
case 2:
|
||||
return "compose"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type StackEnv struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type EndpointSubset struct {
|
||||
Id uint32
|
||||
Name string
|
||||
Type uint8
|
||||
URL string
|
||||
PublicURL string
|
||||
GroupID uint32
|
||||
}
|
||||
|
||||
type StackCreateRequest struct {
|
||||
Name string
|
||||
SwarmID string
|
||||
StackFileContent string
|
||||
Env []StackEnv `json:",omitempty"`
|
||||
}
|
||||
|
||||
type StackUpdateRequest struct {
|
||||
StackFileContent string
|
||||
Env []StackEnv `json:",omitempty"`
|
||||
Prune bool
|
||||
}
|
||||
|
||||
type StackFileInspectResponse struct {
|
||||
StackFileContent string
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Authentication bool
|
||||
EndpointManagement bool
|
||||
Analytics bool
|
||||
Version string
|
||||
}
|
||||
|
||||
type GenericError struct {
|
||||
Err string
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e *GenericError) Error() string {
|
||||
if e.Details != "" {
|
||||
return fmt.Sprintf("%s: %s", e.Err, e.Details)
|
||||
} else {
|
||||
return fmt.Sprintf("%s", e.Err)
|
||||
}
|
||||
}
|
||||
|
||||
type AuthenticateUserRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type AuthenticateUserResponse struct {
|
||||
Jwt string
|
||||
}
|
@ -2,9 +2,13 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/util"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/client"
|
||||
)
|
||||
|
||||
func GetStackByName(name string) (stack Stack, err error) {
|
||||
func GetStackByName(name string) (stack client.Stack, err error) {
|
||||
client, err := GetClient()
|
||||
if err != nil {
|
||||
return
|
||||
@ -15,7 +19,7 @@ func GetStackByName(name string) (stack Stack, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
PrintVerbose(fmt.Sprintf("Getting stack %s...", name))
|
||||
util.PrintVerbose(fmt.Sprintf("Getting stack %s...", name))
|
||||
for _, stack := range stacks {
|
||||
if stack.Name == name {
|
||||
return stack, nil
|
||||
@ -27,11 +31,6 @@ func GetStackByName(name string) (stack Stack, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type StackListFilter struct {
|
||||
SwarmId string `json:",omitempty"`
|
||||
EndpointId uint32 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Custom customerrors
|
||||
type StackNotFoundError struct {
|
||||
StackName string
|
||||
|
Reference in New Issue
Block a user