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:
parent
a3b58499b8
commit
9c79379191
295
client/client.go
Normal file
295
client/client.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StackListFilter struct {
|
||||||
|
SwarmId string `json:",omitempty"`
|
||||||
|
EndpointId uint32 `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
util.PrintDebug(fmt.Sprintf("Auth token: %s", n.token))
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+n.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.PrintDebugRequest("Request", req)
|
||||||
|
|
||||||
|
resp, err = n.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkResponseForErrors(resp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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) {
|
||||||
|
util.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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package common
|
package client
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
@ -2,12 +2,13 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// configCmd represents the config command
|
// configCmd represents the config command
|
||||||
@ -32,12 +33,12 @@ var configCmd = &cobra.Command{
|
|||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
// Get config
|
// Get config
|
||||||
value, configGettingErr := getConfig(args[0])
|
value, configGettingErr := getConfig(args[0])
|
||||||
common.CheckError(configGettingErr)
|
util.CheckError(configGettingErr)
|
||||||
fmt.Println(value)
|
fmt.Println(value)
|
||||||
} else {
|
} else {
|
||||||
// Set config
|
// Set config
|
||||||
configSettingErr := setConfig(args[0], args[1])
|
configSettingErr := setConfig(args[0], args[1])
|
||||||
common.CheckError(configSettingErr)
|
util.CheckError(configSettingErr)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -67,7 +68,7 @@ func loadCofig() (*viper.Viper, error) {
|
|||||||
|
|
||||||
// Read config from file
|
// Read config from file
|
||||||
if configReadingErr := newViper.ReadInConfig(); configReadingErr != nil {
|
if configReadingErr := newViper.ReadInConfig(); configReadingErr != nil {
|
||||||
common.PrintVerbose(fmt.Sprintf("Could not read configuration from \"%s\". Expect all configuration values to be unset.", configFile))
|
util.PrintVerbose(fmt.Sprintf("Could not read configuration from \"%s\". Expect all configuration values to be unset.", configFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
return newViper, nil
|
return newViper, nil
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
@ -34,23 +36,23 @@ var endpointListCmd = &cobra.Command{
|
|||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
client, err := common.GetClient()
|
client, err := common.GetClient()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
endpoints, err := client.GetEndpoints()
|
endpoints, err := client.GetEndpoints()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
if viper.GetString("endpoint.list.format") != "" {
|
if viper.GetString("endpoint.list.format") != "" {
|
||||||
// Print endpoint fields formatted
|
// Print endpoint fields formatted
|
||||||
template, templateParsingErr := template.New("endpointTpl").Parse(viper.GetString("endpoint.list.format"))
|
template, templateParsingErr := template.New("endpointTpl").Parse(viper.GetString("endpoint.list.format"))
|
||||||
common.CheckError(templateParsingErr)
|
util.CheckError(templateParsingErr)
|
||||||
for _, e := range endpoints {
|
for _, e := range endpoints {
|
||||||
templateExecutionErr := template.Execute(os.Stdout, e)
|
templateExecutionErr := template.Execute(os.Stdout, e)
|
||||||
common.CheckError(templateExecutionErr)
|
util.CheckError(templateExecutionErr)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Print all endpoint fields as a table
|
// Print all endpoint fields as a table
|
||||||
writer, err := common.NewTabWriter([]string{
|
writer, err := util.NewTabWriter([]string{
|
||||||
"ENDPOINT ID",
|
"ENDPOINT ID",
|
||||||
"NAME",
|
"NAME",
|
||||||
"TYPE",
|
"TYPE",
|
||||||
@ -58,7 +60,7 @@ var endpointListCmd = &cobra.Command{
|
|||||||
"PUBLIC URL",
|
"PUBLIC URL",
|
||||||
"GROUP ID",
|
"GROUP ID",
|
||||||
})
|
})
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
for _, e := range endpoints {
|
for _, e := range endpoints {
|
||||||
var endpointType string
|
var endpointType string
|
||||||
if e.Type == 1 {
|
if e.Type == 1 {
|
||||||
@ -75,10 +77,10 @@ var endpointListCmd = &cobra.Command{
|
|||||||
e.PublicURL,
|
e.PublicURL,
|
||||||
e.GroupID,
|
e.GroupID,
|
||||||
))
|
))
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
}
|
}
|
||||||
flushErr := writer.Flush()
|
flushErr := writer.Flush()
|
||||||
common.CheckError(flushErr)
|
util.CheckError(flushErr)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -15,10 +17,10 @@ var loginCmd = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Get auth token
|
// Get auth token
|
||||||
client, err := common.GetDefaultClient()
|
client, err := common.GetDefaultClient()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
authToken, err := client.Authenticate()
|
authToken, err := client.Authenticate()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
if viper.GetBool("login.print") {
|
if viper.GetBool("login.print") {
|
||||||
fmt.Println(authToken)
|
fmt.Println(authToken)
|
||||||
@ -26,7 +28,7 @@ var loginCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Save auth token
|
// Save auth token
|
||||||
configSettingErr := setConfig("auth-token", authToken)
|
configSettingErr := setConfig("auth-token", authToken)
|
||||||
common.CheckError(configSettingErr)
|
util.CheckError(configSettingErr)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,12 +2,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
var cfgFile string
|
||||||
@ -83,6 +85,6 @@ func initConfig() {
|
|||||||
|
|
||||||
// If a config file is found, read it in.
|
// If a config file is found, read it in.
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
common.PrintVerbose("Using config file:", viper.ConfigFileUsed())
|
util.PrintVerbose("Using config file:", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/client"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -19,35 +23,35 @@ var stackDeployCmd = &cobra.Command{
|
|||||||
Example: "psu stack deploy mystack --stack-file mystack.yml",
|
Example: "psu stack deploy mystack --stack-file mystack.yml",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var loadedEnvironmentVariables []common.StackEnv
|
var loadedEnvironmentVariables []client.StackEnv
|
||||||
if viper.GetString("stack.deploy.env-file") != "" {
|
if viper.GetString("stack.deploy.env-file") != "" {
|
||||||
var loadingErr error
|
var loadingErr error
|
||||||
loadedEnvironmentVariables, loadingErr = loadEnvironmentVariablesFile(viper.GetString("stack.deploy.env-file"))
|
loadedEnvironmentVariables, loadingErr = loadEnvironmentVariablesFile(viper.GetString("stack.deploy.env-file"))
|
||||||
common.CheckError(loadingErr)
|
util.CheckError(loadingErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, clientRetrievalErr := common.GetClient()
|
portainerClient, clientRetrievalErr := common.GetClient()
|
||||||
common.CheckError(clientRetrievalErr)
|
util.CheckError(clientRetrievalErr)
|
||||||
|
|
||||||
stackName := args[0]
|
stackName := args[0]
|
||||||
retrievedStack, stackRetrievalErr := common.GetStackByName(stackName)
|
retrievedStack, stackRetrievalErr := common.GetStackByName(stackName)
|
||||||
switch stackRetrievalErr.(type) {
|
switch stackRetrievalErr.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// We are updating an existing stack
|
// We are updating an existing stack
|
||||||
common.PrintVerbose(fmt.Sprintf("Stack %s found. Updating...", retrievedStack.Name))
|
util.PrintVerbose(fmt.Sprintf("Stack %s found. Updating...", retrievedStack.Name))
|
||||||
|
|
||||||
var stackFileContent string
|
var stackFileContent string
|
||||||
if viper.GetString("stack.deploy.stack-file") != "" {
|
if viper.GetString("stack.deploy.stack-file") != "" {
|
||||||
var loadingErr error
|
var loadingErr error
|
||||||
stackFileContent, loadingErr = loadStackFile(viper.GetString("stack.deploy.stack-file"))
|
stackFileContent, loadingErr = loadStackFile(viper.GetString("stack.deploy.stack-file"))
|
||||||
common.CheckError(loadingErr)
|
util.CheckError(loadingErr)
|
||||||
} else {
|
} else {
|
||||||
var stackFileContentRetrievalErr error
|
var stackFileContentRetrievalErr error
|
||||||
stackFileContent, stackFileContentRetrievalErr = client.GetStackFileContent(retrievedStack.Id)
|
stackFileContent, stackFileContentRetrievalErr = portainerClient.GetStackFileContent(retrievedStack.Id)
|
||||||
common.CheckError(stackFileContentRetrievalErr)
|
util.CheckError(stackFileContentRetrievalErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var newEnvironmentVariables []common.StackEnv
|
var newEnvironmentVariables []client.StackEnv
|
||||||
if viper.GetBool("stack.deploy.replace-env") {
|
if viper.GetBool("stack.deploy.replace-env") {
|
||||||
newEnvironmentVariables = loadedEnvironmentVariables
|
newEnvironmentVariables = loadedEnvironmentVariables
|
||||||
} else {
|
} else {
|
||||||
@ -61,44 +65,44 @@ var stackDeployCmd = &cobra.Command{
|
|||||||
continue LoadedVariablesLoop
|
continue LoadedVariablesLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newEnvironmentVariables = append(newEnvironmentVariables, common.StackEnv{
|
newEnvironmentVariables = append(newEnvironmentVariables, client.StackEnv{
|
||||||
Name: loadedEnvironmentVariable.Name,
|
Name: loadedEnvironmentVariable.Name,
|
||||||
Value: loadedEnvironmentVariable.Value,
|
Value: loadedEnvironmentVariable.Value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.UpdateStack(retrievedStack, newEnvironmentVariables, stackFileContent, viper.GetBool("stack.deploy.prune"), viper.GetString("stack.deploy.endpoint"))
|
err := portainerClient.UpdateStack(retrievedStack, newEnvironmentVariables, stackFileContent, viper.GetBool("stack.deploy.prune"), viper.GetString("stack.deploy.endpoint"))
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
case *common.StackNotFoundError:
|
case *common.StackNotFoundError:
|
||||||
// We are deploying a new stack
|
// We are deploying a new stack
|
||||||
common.PrintVerbose(fmt.Sprintf("Stack %s not found. Deploying...", stackName))
|
util.PrintVerbose(fmt.Sprintf("Stack %s not found. Deploying...", stackName))
|
||||||
|
|
||||||
if viper.GetString("stack.deploy.stack-file") == "" {
|
if viper.GetString("stack.deploy.stack-file") == "" {
|
||||||
log.Fatalln("Specify a docker-compose file with --stack-file")
|
log.Fatalln("Specify a docker-compose file with --stack-file")
|
||||||
}
|
}
|
||||||
stackFileContent, loadingErr := loadStackFile(viper.GetString("stack.deploy.stack-file"))
|
stackFileContent, loadingErr := loadStackFile(viper.GetString("stack.deploy.stack-file"))
|
||||||
common.CheckError(loadingErr)
|
util.CheckError(loadingErr)
|
||||||
|
|
||||||
swarmClusterId, selectionErr := getSwarmClusterId()
|
swarmClusterId, selectionErr := getSwarmClusterId()
|
||||||
switch selectionErr.(type) {
|
switch selectionErr.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// It's a swarm cluster
|
// It's a swarm cluster
|
||||||
common.PrintVerbose(fmt.Sprintf("Swarm cluster found with id %s", swarmClusterId))
|
util.PrintVerbose(fmt.Sprintf("Swarm cluster found with id %s", swarmClusterId))
|
||||||
deploymentErr := client.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, swarmClusterId, viper.GetString("stack.deploy.endpoint"))
|
deploymentErr := portainerClient.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, swarmClusterId, viper.GetString("stack.deploy.endpoint"))
|
||||||
common.CheckError(deploymentErr)
|
util.CheckError(deploymentErr)
|
||||||
case *valueNotFoundError:
|
case *valueNotFoundError:
|
||||||
// It's not a swarm cluster
|
// It's not a swarm cluster
|
||||||
common.PrintVerbose("Swarm cluster not found")
|
util.PrintVerbose("Swarm cluster not found")
|
||||||
deploymentErr := client.CreateComposeStack(stackName, loadedEnvironmentVariables, stackFileContent, viper.GetString("stack.deploy.endpoint"))
|
deploymentErr := portainerClient.CreateComposeStack(stackName, loadedEnvironmentVariables, stackFileContent, viper.GetString("stack.deploy.endpoint"))
|
||||||
common.CheckError(deploymentErr)
|
util.CheckError(deploymentErr)
|
||||||
default:
|
default:
|
||||||
// Something else happened
|
// Something else happened
|
||||||
common.CheckError(stackRetrievalErr)
|
util.CheckError(stackRetrievalErr)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Something else happened
|
// Something else happened
|
||||||
common.CheckError(stackRetrievalErr)
|
util.CheckError(stackRetrievalErr)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -160,15 +164,15 @@ func loadStackFile(path string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
func loadEnvironmentVariablesFile(path string) ([]common.StackEnv, error) {
|
func loadEnvironmentVariablesFile(path string) ([]client.StackEnv, error) {
|
||||||
var variables []common.StackEnv
|
var variables []client.StackEnv
|
||||||
variablesMap, readingErr := godotenv.Read(path)
|
variablesMap, readingErr := godotenv.Read(path)
|
||||||
if readingErr != nil {
|
if readingErr != nil {
|
||||||
return []common.StackEnv{}, readingErr
|
return []client.StackEnv{}, readingErr
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range variablesMap {
|
for key, value := range variablesMap {
|
||||||
variables = append(variables, common.StackEnv{
|
variables = append(variables, client.StackEnv{
|
||||||
Name: key,
|
Name: key,
|
||||||
Value: value,
|
Value: value,
|
||||||
})
|
})
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -18,29 +20,29 @@ var stackListCmd = &cobra.Command{
|
|||||||
Example: "psu stack list --endpoint 1",
|
Example: "psu stack list --endpoint 1",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
client, err := common.GetClient()
|
client, err := common.GetClient()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
stacks, err := client.GetStacks(viper.GetString("stack.list.swarm"), viper.GetUint32("stack.list.endpoint"))
|
stacks, err := client.GetStacks(viper.GetString("stack.list.swarm"), viper.GetUint32("stack.list.endpoint"))
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
if viper.GetBool("stack.list.quiet") {
|
if viper.GetBool("stack.list.quiet") {
|
||||||
// Print only stack names
|
// Print only stack names
|
||||||
for _, s := range stacks {
|
for _, s := range stacks {
|
||||||
_, err := fmt.Println(s.Name)
|
_, err := fmt.Println(s.Name)
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
}
|
}
|
||||||
} else if viper.GetString("stack.list.format") != "" {
|
} else if viper.GetString("stack.list.format") != "" {
|
||||||
// Print stack fields formatted
|
// Print stack fields formatted
|
||||||
template, templateParsingErr := template.New("stackTpl").Parse(viper.GetString("stack.list.format"))
|
template, templateParsingErr := template.New("stackTpl").Parse(viper.GetString("stack.list.format"))
|
||||||
common.CheckError(templateParsingErr)
|
util.CheckError(templateParsingErr)
|
||||||
for _, s := range stacks {
|
for _, s := range stacks {
|
||||||
templateExecutionErr := template.Execute(os.Stdout, s)
|
templateExecutionErr := template.Execute(os.Stdout, s)
|
||||||
common.CheckError(templateExecutionErr)
|
util.CheckError(templateExecutionErr)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Print all stack fields as a table
|
// Print all stack fields as a table
|
||||||
writer, err := common.NewTabWriter([]string{
|
writer, err := util.NewTabWriter([]string{
|
||||||
"STACK ID",
|
"STACK ID",
|
||||||
"NAME",
|
"NAME",
|
||||||
"TYPE",
|
"TYPE",
|
||||||
@ -49,7 +51,7 @@ var stackListCmd = &cobra.Command{
|
|||||||
"ENDPOINT ID",
|
"ENDPOINT ID",
|
||||||
"SWARM ID",
|
"SWARM ID",
|
||||||
})
|
})
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
for _, s := range stacks {
|
for _, s := range stacks {
|
||||||
_, err := fmt.Fprintln(writer, fmt.Sprintf(
|
_, err := fmt.Fprintln(writer, fmt.Sprintf(
|
||||||
"%v\t%s\t%v\t%s\t%s\t%v\t%s",
|
"%v\t%s\t%v\t%s\t%s\t%v\t%s",
|
||||||
@ -61,10 +63,10 @@ var stackListCmd = &cobra.Command{
|
|||||||
s.EndpointID,
|
s.EndpointID,
|
||||||
s.SwarmID,
|
s.SwarmID,
|
||||||
))
|
))
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
}
|
}
|
||||||
flushErr := writer.Flush()
|
flushErr := writer.Flush()
|
||||||
common.CheckError(flushErr)
|
util.CheckError(flushErr)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -23,26 +25,26 @@ var stackRemoveCmd = &cobra.Command{
|
|||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// The stack exists
|
// The stack exists
|
||||||
common.PrintVerbose(fmt.Sprintf("Stack %s exists.", stackName))
|
util.PrintVerbose(fmt.Sprintf("Stack %s exists.", stackName))
|
||||||
|
|
||||||
stackId := stack.Id
|
stackId := stack.Id
|
||||||
|
|
||||||
common.PrintVerbose(fmt.Sprintf("Removing stack %s...", stackName))
|
util.PrintVerbose(fmt.Sprintf("Removing stack %s...", stackName))
|
||||||
|
|
||||||
client, err := common.GetClient()
|
client, err := common.GetClient()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
err = client.DeleteStack(stackId)
|
err = client.DeleteStack(stackId)
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
case *common.StackNotFoundError:
|
case *common.StackNotFoundError:
|
||||||
// The stack does not exist
|
// The stack does not exist
|
||||||
common.PrintVerbose(fmt.Sprintf("Stack %s does not exist.", stackName))
|
util.PrintVerbose(fmt.Sprintf("Stack %s does not exist.", stackName))
|
||||||
if viper.GetBool("stack.remove.strict") {
|
if viper.GetBool("stack.remove.strict") {
|
||||||
log.Fatalln(fmt.Sprintf("Stack %s does not exist.", stackName))
|
log.Fatalln(fmt.Sprintf("Stack %s does not exist.", stackName))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Something else happened
|
// Something else happened
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/greenled/portainer-stack-utils/util"
|
||||||
|
|
||||||
"github.com/greenled/portainer-stack-utils/common"
|
"github.com/greenled/portainer-stack-utils/common"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -16,27 +18,27 @@ var statusCmd = &cobra.Command{
|
|||||||
Short: "Check Portainer status",
|
Short: "Check Portainer status",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
client, err := common.GetClient()
|
client, err := common.GetClient()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
respBody, err := client.GetStatus()
|
respBody, err := client.GetStatus()
|
||||||
common.CheckError(err)
|
util.CheckError(err)
|
||||||
|
|
||||||
if viper.GetString("status.format") != "" {
|
if viper.GetString("status.format") != "" {
|
||||||
// Print stack fields formatted
|
// Print stack fields formatted
|
||||||
template, templateParsingErr := template.New("statusTpl").Parse(viper.GetString("status.format"))
|
template, templateParsingErr := template.New("statusTpl").Parse(viper.GetString("status.format"))
|
||||||
common.CheckError(templateParsingErr)
|
util.CheckError(templateParsingErr)
|
||||||
templateExecutionErr := template.Execute(os.Stdout, respBody)
|
templateExecutionErr := template.Execute(os.Stdout, respBody)
|
||||||
common.CheckError(templateExecutionErr)
|
util.CheckError(templateExecutionErr)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
} else {
|
} else {
|
||||||
// Print status fields as a table
|
// Print status fields as a table
|
||||||
writer, newTabWriterErr := common.NewTabWriter([]string{
|
writer, newTabWriterErr := util.NewTabWriter([]string{
|
||||||
"VERSION",
|
"VERSION",
|
||||||
"AUTHENTICATION",
|
"AUTHENTICATION",
|
||||||
"ENDPOINT MANAGEMENT",
|
"ENDPOINT MANAGEMENT",
|
||||||
"ANALYTICS",
|
"ANALYTICS",
|
||||||
})
|
})
|
||||||
common.CheckError(newTabWriterErr)
|
util.CheckError(newTabWriterErr)
|
||||||
|
|
||||||
_, printingErr := fmt.Fprintln(writer, fmt.Sprintf(
|
_, printingErr := fmt.Fprintln(writer, fmt.Sprintf(
|
||||||
"%s\t%v\t%v\t%v",
|
"%s\t%v\t%v\t%v",
|
||||||
@ -45,10 +47,10 @@ var statusCmd = &cobra.Command{
|
|||||||
respBody.EndpointManagement,
|
respBody.EndpointManagement,
|
||||||
respBody.Analytics,
|
respBody.Analytics,
|
||||||
))
|
))
|
||||||
common.CheckError(printingErr)
|
util.CheckError(printingErr)
|
||||||
|
|
||||||
flushErr := writer.Flush()
|
flushErr := writer.Flush()
|
||||||
common.CheckError(flushErr)
|
util.CheckError(flushErr)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
297
common/client.go
297
common/client.go
@ -1,299 +1,18 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"github.com/greenled/portainer-stack-utils/client"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cachedClient PortainerClient
|
var cachedClient client.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the cached client or a new one
|
// Get the cached client or a new one
|
||||||
func GetClient() (c PortainerClient, err error) {
|
func GetClient() (c client.PortainerClient, err error) {
|
||||||
if cachedClient == nil {
|
if cachedClient == nil {
|
||||||
cachedClient, err = GetDefaultClient()
|
cachedClient, err = GetDefaultClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -304,13 +23,13 @@ func GetClient() (c PortainerClient, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the default client
|
// Get the default client
|
||||||
func GetDefaultClient() (c PortainerClient, err error) {
|
func GetDefaultClient() (c client.PortainerClient, err error) {
|
||||||
return NewClient(GetDefaultHttpClient(), GetDefaultClientConfig())
|
return client.NewClient(GetDefaultHttpClient(), GetDefaultClientConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the default config for a client
|
// Get the default config for a client
|
||||||
func GetDefaultClientConfig() ClientConfig {
|
func GetDefaultClientConfig() client.ClientConfig {
|
||||||
return ClientConfig{
|
return client.ClientConfig{
|
||||||
Url: viper.GetString("url"),
|
Url: viper.GetString("url"),
|
||||||
User: viper.GetString("user"),
|
User: viper.GetString("user"),
|
||||||
Password: viper.GetString("password"),
|
Password: viper.GetString("password"),
|
||||||
|
@ -2,9 +2,13 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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()
|
client, err := GetClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -15,7 +19,7 @@ func GetStackByName(name string) (stack Stack, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintVerbose(fmt.Sprintf("Getting stack %s...", name))
|
util.PrintVerbose(fmt.Sprintf("Getting stack %s...", name))
|
||||||
for _, stack := range stacks {
|
for _, stack := range stacks {
|
||||||
if stack.Name == name {
|
if stack.Name == name {
|
||||||
return stack, nil
|
return stack, nil
|
||||||
@ -27,11 +31,6 @@ func GetStackByName(name string) (stack Stack, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type StackListFilter struct {
|
|
||||||
SwarmId string `json:",omitempty"`
|
|
||||||
EndpointId uint32 `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom customerrors
|
// Custom customerrors
|
||||||
type StackNotFoundError struct {
|
type StackNotFoundError struct {
|
||||||
StackName string
|
StackName string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package common
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,15 +1,16 @@
|
|||||||
package common
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrintVerbose(a ...interface{}) {
|
func PrintVerbose(a ...interface{}) {
|
Loading…
Reference in New Issue
Block a user