Fix inconsistencies with EndpointID and SwarmID filtering

This commit is contained in:
Juan Carlos Mejías Rodríguez 2019-08-06 23:08:59 -04:00
parent 0c3e255bf2
commit b3f1989a25
5 changed files with 124 additions and 83 deletions

View File

@ -36,13 +36,13 @@ type PortainerClient interface {
GetStacks(swarmId string, endpointId uint32) ([]Stack, error)
// Create swarm stack
CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId string) error
CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId uint32) error
// Create compose stack
CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId string) error
CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId uint32) error
// Update stack
UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId string) error
UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId uint32) error
// Delete stack
DeleteStack(stackId uint32) error
@ -51,7 +51,7 @@ type PortainerClient interface {
GetStackFileContent(stackId uint32) (content string, err error)
// Get endpoint Docker info
GetEndpointDockerInfo(endpointId string) (info map[string]interface{}, err error)
GetEndpointDockerInfo(endpointId uint32) (info map[string]interface{}, err error)
// Get Portainer status info
GetStatus() (Status, error)
@ -231,7 +231,7 @@ func (n *portainerClientImp) GetStacks(swarmId string, endpointId uint32) (stack
return
}
func (n *portainerClientImp) CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId string) (err error) {
func (n *portainerClientImp) CreateSwarmStack(stackName string, environmentVariables []StackEnv, stackFileContent string, swarmClusterId string, endpointId uint32) (err error) {
reqBody := StackCreateRequest{
Name: stackName,
Env: environmentVariables,
@ -239,29 +239,29 @@ func (n *portainerClientImp) CreateSwarmStack(stackName string, environmentVaria
StackFileContent: stackFileContent,
}
err = n.doJSON(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%s", 1, "string", endpointId), http.MethodPost, &reqBody, nil)
err = n.doJSON(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 1, "string", endpointId), http.MethodPost, &reqBody, nil)
return
}
func (n *portainerClientImp) CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId string) (err error) {
func (n *portainerClientImp) CreateComposeStack(stackName string, environmentVariables []StackEnv, stackFileContent string, endpointId uint32) (err error) {
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)
err = n.doJSON(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 2, "string", endpointId), http.MethodPost, &reqBody, nil)
return
}
func (n *portainerClientImp) UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId string) (err error) {
func (n *portainerClientImp) UpdateStack(stack Stack, environmentVariables []StackEnv, stackFileContent string, prune bool, endpointId uint32) (err error) {
reqBody := StackUpdateRequest{
Env: environmentVariables,
StackFileContent: stackFileContent,
Prune: prune,
}
err = n.doJSON(fmt.Sprintf("stacks/%v?endpointId=%s", stack.Id, endpointId), http.MethodPut, &reqBody, nil)
err = n.doJSON(fmt.Sprintf("stacks/%v?endpointId=%v", stack.Id, endpointId), http.MethodPut, &reqBody, nil)
return
}
@ -283,7 +283,7 @@ func (n *portainerClientImp) GetStackFileContent(stackId uint32) (content string
return
}
func (n *portainerClientImp) GetEndpointDockerInfo(endpointId string) (info map[string]interface{}, err error) {
func (n *portainerClientImp) GetEndpointDockerInfo(endpointId uint32) (info map[string]interface{}, err error) {
err = n.doJSON(fmt.Sprintf("endpoints/%v/docker/info", endpointId), http.MethodGet, nil, &info)
return
}

View File

@ -31,10 +31,22 @@ var stackDeployCmd = &cobra.Command{
common.CheckError(clientRetrievalErr)
stackName := args[0]
endpointId := viper.GetUint32("stack.deploy.endpoint")
endpointSwarmClusterId, selectionErr := common.GetEndpointSwarmClusterId(endpointId)
switch selectionErr.(type) {
case nil:
// It's a swarm cluster
case *common.StackClusterNotFoundError:
// It's not a swarm cluster
default:
// Something else happened
common.CheckError(selectionErr)
}
logrus.WithFields(logrus.Fields{
"stack": stackName,
}).Debug("Getting stack")
retrievedStack, stackRetrievalErr := common.GetStackByName(stackName)
retrievedStack, stackRetrievalErr := common.GetStackByName(stackName, endpointSwarmClusterId, endpointId)
switch stackRetrievalErr.(type) {
case nil:
// We are updating an existing stack
@ -80,7 +92,7 @@ var stackDeployCmd = &cobra.Command{
logrus.WithFields(logrus.Fields{
"stack": retrievedStack.Name,
}).Info("Updating stack")
err := portainerClient.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"), endpointId)
common.CheckError(err)
case *common.StackNotFoundError:
// We are deploying a new stack
@ -96,19 +108,16 @@ var stackDeployCmd = &cobra.Command{
stackFileContent, loadingErr := loadStackFile(viper.GetString("stack.deploy.stack-file"))
common.CheckError(loadingErr)
swarmClusterId, selectionErr := getSwarmClusterId()
endpointId := viper.GetString("stack.deploy.endpoint")
switch selectionErr.(type) {
case nil:
if endpointSwarmClusterId != "" {
// It's a swarm cluster
logrus.WithFields(logrus.Fields{
"stack": stackName,
"endpoint": endpointId,
"cluster": swarmClusterId,
"cluster": endpointSwarmClusterId,
}).Info("Creating stack")
deploymentErr := portainerClient.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, swarmClusterId, endpointId)
deploymentErr := portainerClient.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, endpointSwarmClusterId, endpointId)
common.CheckError(deploymentErr)
case *valueNotFoundError:
} else {
// It's not a swarm cluster
logrus.WithFields(logrus.Fields{
"stack": stackName,
@ -116,9 +125,6 @@ var stackDeployCmd = &cobra.Command{
}).Info("Creating stack")
deploymentErr := portainerClient.CreateComposeStack(stackName, loadedEnvironmentVariables, stackFileContent, endpointId)
common.CheckError(deploymentErr)
default:
// Something else happened
common.CheckError(stackRetrievalErr)
}
default:
// Something else happened
@ -131,7 +137,7 @@ func init() {
stackCmd.AddCommand(stackDeployCmd)
stackDeployCmd.Flags().StringP("stack-file", "c", "", "Path to a file with the content of the stack.")
stackDeployCmd.Flags().String("endpoint", "1", "Endpoint ID.")
stackDeployCmd.Flags().Uint32("endpoint", 1, "Endpoint ID.")
stackDeployCmd.Flags().StringP("env-file", "e", "", "Path to a file with environment variables used during stack deployment.")
stackDeployCmd.Flags().Bool("replace-env", false, "Replace environment variables instead of merging them.")
stackDeployCmd.Flags().BoolP("prune", "r", false, "Prune services that are no longer referenced (only available for Swarm stacks).")
@ -142,43 +148,6 @@ func init() {
viper.BindPFlag("stack.deploy.prune", stackDeployCmd.Flags().Lookup("prune"))
}
func getSwarmClusterId() (id string, err error) {
// Get docker information for endpoint
client, err := common.GetClient()
if err != nil {
return
}
endpointId := viper.GetString("stack.deploy.endpoint")
logrus.WithFields(logrus.Fields{
"endpoint": endpointId,
}).Debug("Getting endpoint's Docker info")
result, err := client.GetEndpointDockerInfo(endpointId)
if err != nil {
return
}
// Get swarm (if any) information for endpoint
swarmClusterId, err := selectValue(result, []string{"Swarm", "Cluster", "ID"})
if err != nil {
return
}
id = swarmClusterId.(string)
return
}
func selectValue(jsonMap map[string]interface{}, jsonPath []string) (interface{}, error) {
value := jsonMap[jsonPath[0]]
if value == nil {
return nil, &valueNotFoundError{}
} else if len(jsonPath) > 1 {
return selectValue(value.(map[string]interface{}), jsonPath[1:])
} else {
return value, nil
}
}
func loadStackFile(path string) (string, error) {
loadedStackFileContentBytes, readingErr := ioutil.ReadFile(path)
if readingErr != nil {
@ -204,9 +173,3 @@ func loadEnvironmentVariablesFile(path string) ([]client.StackEnv, error) {
return variables, nil
}
type valueNotFoundError struct{}
func (e *valueNotFoundError) Error() string {
return "Value not found"
}

View File

@ -5,8 +5,11 @@ import (
"os"
"text/template"
"github.com/greenled/portainer-stack-utils/common"
"github.com/greenled/portainer-stack-utils/client"
"github.com/sirupsen/logrus"
"github.com/greenled/portainer-stack-utils/common"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -18,17 +21,40 @@ var stackListCmd = &cobra.Command{
Aliases: []string{"ls"},
Example: "psu stack list --endpoint 1",
Run: func(cmd *cobra.Command, args []string) {
client, err := common.GetClient()
portainerClient, err := common.GetClient()
common.CheckError(err)
swarmId := viper.GetString("stack.list.swarm")
endpointId := viper.GetUint32("stack.list.endpoint")
logrus.WithFields(logrus.Fields{
"swarm": swarmId,
"endpoint": endpointId,
}).Debug("Getting stacks")
stacks, err := client.GetStacks(swarmId, endpointId)
common.CheckError(err)
var endpointSwarmClusterId string
var stacks []client.Stack
if endpointId != 0 {
var selectionErr error
endpointSwarmClusterId, selectionErr = common.GetEndpointSwarmClusterId(endpointId)
switch selectionErr.(type) {
case nil:
// It's a swarm cluster
logrus.WithFields(logrus.Fields{
"endpoint": endpointId,
"swarm": endpointSwarmClusterId,
}).Debug("Getting stacks")
stacks, err = portainerClient.GetStacks(endpointSwarmClusterId, endpointId)
common.CheckError(err)
case *common.StackClusterNotFoundError:
// It's not a swarm cluster
logrus.WithFields(logrus.Fields{
"endpoint": endpointId,
}).Debug("Getting stacks")
stacks, err = portainerClient.GetStacks("", endpointId)
common.CheckError(err)
default:
// Something else happened
common.CheckError(selectionErr)
}
} else {
logrus.Debug("Getting stacks")
stacks, err = portainerClient.GetStacks("", 0)
common.CheckError(err)
}
if viper.GetBool("stack.list.quiet") {
// Print only stack names
@ -79,11 +105,9 @@ var stackListCmd = &cobra.Command{
func init() {
stackCmd.AddCommand(stackListCmd)
stackListCmd.Flags().String("swarm", "", "Filter by swarm ID.")
stackListCmd.Flags().String("endpoint", "", "Filter by endpoint ID.")
stackListCmd.Flags().Uint32("endpoint", 0, "Filter by endpoint ID.")
stackListCmd.Flags().BoolP("quiet", "q", false, "Only display stack names.")
stackListCmd.Flags().String("format", "", "Format output using a Go template.")
viper.BindPFlag("stack.list.swarm", stackListCmd.Flags().Lookup("swarm"))
viper.BindPFlag("stack.list.endpoint", stackListCmd.Flags().Lookup("endpoint"))
viper.BindPFlag("stack.list.quiet", stackListCmd.Flags().Lookup("quiet"))
viper.BindPFlag("stack.list.format", stackListCmd.Flags().Lookup("format"))

View File

@ -16,7 +16,7 @@ var stackRemoveCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
stackName := args[0]
stack, err := common.GetStackByName(stackName)
stack, err := common.GetStackByName(stackName, "", 0)
switch err.(type) {
case nil:

View File

@ -3,16 +3,18 @@ package common
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/greenled/portainer-stack-utils/client"
)
func GetStackByName(name string) (stack client.Stack, err error) {
client, err := GetClient()
func GetStackByName(name string, swarmId string, endpointId uint32) (stack client.Stack, err error) {
portainerClient, err := GetClient()
if err != nil {
return
}
stacks, err := client.GetStacks("", 0)
stacks, err := portainerClient.GetStacks(swarmId, endpointId)
if err != nil {
return
}
@ -28,6 +30,46 @@ func GetStackByName(name string) (stack client.Stack, err error) {
return
}
func GetEndpointSwarmClusterId(endpointId uint32) (endpointSwarmClusterId string, err error) {
// Get docker information for endpoint
portainerClient, err := GetClient()
if err != nil {
return
}
logrus.WithFields(logrus.Fields{
"endpoint": endpointId,
}).Debug("Getting endpoint's Docker info")
result, err := portainerClient.GetEndpointDockerInfo(endpointId)
if err != nil {
return
}
// Get swarm (if any) information for endpoint
id, selectionErr := selectValue(result, []string{"Swarm", "Cluster", "ID"})
switch selectionErr.(type) {
case nil:
endpointSwarmClusterId = id.(string)
case *valueNotFoundError:
err = &StackClusterNotFoundError{}
default:
err = selectionErr
}
return
}
func selectValue(jsonMap map[string]interface{}, jsonPath []string) (interface{}, error) {
value := jsonMap[jsonPath[0]]
if value == nil {
return nil, &valueNotFoundError{}
} else if len(jsonPath) > 1 {
return selectValue(value.(map[string]interface{}), jsonPath[1:])
} else {
return value, nil
}
}
// Custom customerrors
type StackNotFoundError struct {
StackName string
@ -36,3 +78,15 @@ type StackNotFoundError struct {
func (e *StackNotFoundError) Error() string {
return fmt.Sprintf("Stack %s not found", e.StackName)
}
type valueNotFoundError struct{}
func (e *valueNotFoundError) Error() string {
return "Value not found"
}
type StackClusterNotFoundError struct{}
func (e *StackClusterNotFoundError) Error() string {
return "Stack cluster not found"
}