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

View File

@ -31,10 +31,22 @@ var stackDeployCmd = &cobra.Command{
common.CheckError(clientRetrievalErr) common.CheckError(clientRetrievalErr)
stackName := args[0] 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{ logrus.WithFields(logrus.Fields{
"stack": stackName, "stack": stackName,
}).Debug("Getting stack") }).Debug("Getting stack")
retrievedStack, stackRetrievalErr := common.GetStackByName(stackName) retrievedStack, stackRetrievalErr := common.GetStackByName(stackName, endpointSwarmClusterId, endpointId)
switch stackRetrievalErr.(type) { switch stackRetrievalErr.(type) {
case nil: case nil:
// We are updating an existing stack // We are updating an existing stack
@ -80,7 +92,7 @@ var stackDeployCmd = &cobra.Command{
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"stack": retrievedStack.Name, "stack": retrievedStack.Name,
}).Info("Updating stack") }).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) common.CheckError(err)
case *common.StackNotFoundError: case *common.StackNotFoundError:
// We are deploying a new stack // We are deploying a new stack
@ -96,19 +108,16 @@ var stackDeployCmd = &cobra.Command{
stackFileContent, loadingErr := loadStackFile(viper.GetString("stack.deploy.stack-file")) stackFileContent, loadingErr := loadStackFile(viper.GetString("stack.deploy.stack-file"))
common.CheckError(loadingErr) common.CheckError(loadingErr)
swarmClusterId, selectionErr := getSwarmClusterId() if endpointSwarmClusterId != "" {
endpointId := viper.GetString("stack.deploy.endpoint")
switch selectionErr.(type) {
case nil:
// It's a swarm cluster // It's a swarm cluster
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"stack": stackName, "stack": stackName,
"endpoint": endpointId, "endpoint": endpointId,
"cluster": swarmClusterId, "cluster": endpointSwarmClusterId,
}).Info("Creating stack") }).Info("Creating stack")
deploymentErr := portainerClient.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, swarmClusterId, endpointId) deploymentErr := portainerClient.CreateSwarmStack(stackName, loadedEnvironmentVariables, stackFileContent, endpointSwarmClusterId, endpointId)
common.CheckError(deploymentErr) common.CheckError(deploymentErr)
case *valueNotFoundError: } else {
// It's not a swarm cluster // It's not a swarm cluster
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"stack": stackName, "stack": stackName,
@ -116,9 +125,6 @@ var stackDeployCmd = &cobra.Command{
}).Info("Creating stack") }).Info("Creating stack")
deploymentErr := portainerClient.CreateComposeStack(stackName, loadedEnvironmentVariables, stackFileContent, endpointId) deploymentErr := portainerClient.CreateComposeStack(stackName, loadedEnvironmentVariables, stackFileContent, endpointId)
common.CheckError(deploymentErr) common.CheckError(deploymentErr)
default:
// Something else happened
common.CheckError(stackRetrievalErr)
} }
default: default:
// Something else happened // Something else happened
@ -131,7 +137,7 @@ func init() {
stackCmd.AddCommand(stackDeployCmd) stackCmd.AddCommand(stackDeployCmd)
stackDeployCmd.Flags().StringP("stack-file", "c", "", "Path to a file with the content of the stack.") 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().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().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).") 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")) 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) { func loadStackFile(path string) (string, error) {
loadedStackFileContentBytes, readingErr := ioutil.ReadFile(path) loadedStackFileContentBytes, readingErr := ioutil.ReadFile(path)
if readingErr != nil { if readingErr != nil {
@ -204,9 +173,3 @@ func loadEnvironmentVariablesFile(path string) ([]client.StackEnv, error) {
return variables, nil return variables, nil
} }
type valueNotFoundError struct{}
func (e *valueNotFoundError) Error() string {
return "Value not found"
}

View File

@ -5,8 +5,11 @@ import (
"os" "os"
"text/template" "text/template"
"github.com/greenled/portainer-stack-utils/common" "github.com/greenled/portainer-stack-utils/client"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/greenled/portainer-stack-utils/common"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -18,17 +21,40 @@ var stackListCmd = &cobra.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
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() portainerClient, err := common.GetClient()
common.CheckError(err) common.CheckError(err)
swarmId := viper.GetString("stack.list.swarm")
endpointId := viper.GetUint32("stack.list.endpoint") endpointId := viper.GetUint32("stack.list.endpoint")
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{ logrus.WithFields(logrus.Fields{
"swarm": swarmId,
"endpoint": endpointId, "endpoint": endpointId,
}).Debug("Getting stacks") }).Debug("Getting stacks")
stacks, err := client.GetStacks(swarmId, endpointId) stacks, err = portainerClient.GetStacks("", endpointId)
common.CheckError(err) 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") { if viper.GetBool("stack.list.quiet") {
// Print only stack names // Print only stack names
@ -79,11 +105,9 @@ var stackListCmd = &cobra.Command{
func init() { func init() {
stackCmd.AddCommand(stackListCmd) stackCmd.AddCommand(stackListCmd)
stackListCmd.Flags().String("swarm", "", "Filter by swarm ID.") stackListCmd.Flags().Uint32("endpoint", 0, "Filter by endpoint ID.")
stackListCmd.Flags().String("endpoint", "", "Filter by endpoint ID.")
stackListCmd.Flags().BoolP("quiet", "q", false, "Only display stack names.") stackListCmd.Flags().BoolP("quiet", "q", false, "Only display stack names.")
stackListCmd.Flags().String("format", "", "Format output using a Go template.") 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.endpoint", stackListCmd.Flags().Lookup("endpoint"))
viper.BindPFlag("stack.list.quiet", stackListCmd.Flags().Lookup("quiet")) viper.BindPFlag("stack.list.quiet", stackListCmd.Flags().Lookup("quiet"))
viper.BindPFlag("stack.list.format", stackListCmd.Flags().Lookup("format")) viper.BindPFlag("stack.list.format", stackListCmd.Flags().Lookup("format"))

View File

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

View File

@ -3,16 +3,18 @@ package common
import ( import (
"fmt" "fmt"
"github.com/sirupsen/logrus"
"github.com/greenled/portainer-stack-utils/client" "github.com/greenled/portainer-stack-utils/client"
) )
func GetStackByName(name string) (stack client.Stack, err error) { func GetStackByName(name string, swarmId string, endpointId uint32) (stack client.Stack, err error) {
client, err := GetClient() portainerClient, err := GetClient()
if err != nil { if err != nil {
return return
} }
stacks, err := client.GetStacks("", 0) stacks, err := portainerClient.GetStacks(swarmId, endpointId)
if err != nil { if err != nil {
return return
} }
@ -28,6 +30,46 @@ func GetStackByName(name string) (stack client.Stack, err error) {
return 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 // Custom customerrors
type StackNotFoundError struct { type StackNotFoundError struct {
StackName string StackName string
@ -36,3 +78,15 @@ type StackNotFoundError struct {
func (e *StackNotFoundError) Error() string { func (e *StackNotFoundError) Error() string {
return fmt.Sprintf("Stack %s not found", e.StackName) 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"
}