Skip to content

Commit

Permalink
Merge pull request #174 from squarescale/edge-configs
Browse files Browse the repository at this point in the history
Edge configs
  • Loading branch information
obourdon authored Oct 17, 2023
2 parents 155e5c9 + e6296d2 commit 80f9fdd
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 1 deletion.
1 change: 1 addition & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
builds:
-
binary: sqsc
ldflags: -s -w -X main.Version={{.Version}} -X main.GitBranch={{.Branch}} -X main.GitCommit={{.ShortCommit}} -X main.BuildDate={{.Date}}
goos:
- linux
Expand Down
80 changes: 80 additions & 0 deletions command/external_node-download-config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package command

import (
"errors"
"flag"
"fmt"
"strings"

"github.com/squarescale/squarescale-cli/squarescale"
)

// ExternalNodeDownloadConfigCommand is a cli.Command implementation for getting OpenVPN, Consul & Nomad configuration for an external node.
type ExternalNodeDownloadConfigCommand struct {
Meta
flagSet *flag.FlagSet
}

func (b *ExternalNodeDownloadConfigCommand) Run(args []string) int {
b.flagSet = newFlagSet(b, b.Ui)
endpoint := endpointFlag(b.flagSet)
projectUUID := projectUUIDFlag(b.flagSet)
projectName := projectNameFlag(b.flagSet)
configName := externalNodeConfigNameFlag(b.flagSet)

if err := b.flagSet.Parse(args); err != nil {
return 1
}

if *projectUUID == "" && *projectName == "" {
return b.errorWithUsage(errors.New("Project name or uuid is mandatory"))
}

if *configName == "" || (*configName != "all" && *configName != "openvpn" && *configName != "consul" && *configName != "nomad") {
return b.errorWithUsage(errors.New(fmt.Sprintf("Invalid service configuration name: %s", *configName)))
}

externalNodeName, err := externalNodeNameArg(b.flagSet, 0)
if err != nil {
return b.errorWithUsage(err)
}

if b.flagSet.NArg() > 1 {
return b.errorWithUsage(fmt.Errorf("Unparsed arguments on the command line: %v", b.flagSet.Args()))
}

return b.runWithSpinner("downloading external node service configuration file(s)", endpoint.String(), func(client *squarescale.Client) (string, error) {
var UUID string
var err error
if *projectUUID == "" {
UUID, err = client.ProjectByName(*projectName)
if err != nil {
return "", err
}
} else {
UUID = *projectUUID
}

err = client.DownloadConfigExternalNode(UUID, externalNodeName, *configName)
if err != nil {
return "", err
}

return "All done", nil
})
}

// Synopsis is part of cli.Command implementation.
func (b *ExternalNodeDownloadConfigCommand) Synopsis() string {
return "Download service configuration file(s) for external node of project"
}

// Help is part of cli.Command implementation.
func (b *ExternalNodeDownloadConfigCommand) Help() string {
helpText := `
usage: sqsc external-node download-config [options] <external_node_name>
Download service configuration file(s) for external node of project.
`
return strings.TrimSpace(helpText + optionsFromFlags(b.flagSet))
}
4 changes: 4 additions & 0 deletions command/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ func externalNodeNameArg(f *flag.FlagSet, arg int) (string, error) {
}
}

func externalNodeConfigNameFlag(f *flag.FlagSet) *string {
return f.String("config-name", "all", "Configuration service name (all/openvpn/consul/nomad [default to all])")
}

func serviceNameArg(f *flag.FlagSet, arg int) (string, error) {
value := f.Arg(arg)
if value == "" {
Expand Down
6 changes: 5 additions & 1 deletion commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,16 @@ func Commands(meta *command.Meta) map[string]cli.CommandFactory {
Meta: *meta,
}, nil
},

"external-node get": func() (cli.Command, error) {
return &command.ExternalNodeGetCommand{
Meta: *meta,
}, nil
},
"external-node download-config": func() (cli.Command, error) {
return &command.ExternalNodeDownloadConfigCommand{
Meta: *meta,
}, nil
},
"service": func() (cli.Command, error) {
return &command.ServiceCommand{}, nil
},
Expand Down
31 changes: 31 additions & 0 deletions squarescale/external_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,34 @@ func (c *Client) WaitExternalNode(projectUUID string, name string, timeToWait in

return externalNode, err
}

// DownloadConfigExternalNode download service configuration file(s) for a external node
func (c *Client) DownloadConfigExternalNode(projectUUID, name, configName string) (error) {
externalNodes, err := c.GetExternalNodes(projectUUID)
if err != nil {
return err
}

for _, externalNode := range externalNodes {
if externalNode.Name == name {
for _, cfg := range []string{"openvpn", "consul", "nomad"} {
if configName == "all" || configName == cfg {
code, err := c.download(fmt.Sprintf("/projects/%s/external_nodes/%d/%s_client", projectUUID, externalNode.ID, cfg), name)
if err != nil {
return err
}

switch code {
case http.StatusOK:
case http.StatusNotFound:
return fmt.Errorf("Error retrieving service configuration %s for external node %s in project '%s'", configName, name, projectUUID)
default:
return unexpectedHTTPError(code, []byte{})
}
}
}
return nil
}
}
return fmt.Errorf("Unable to find external node '%s' in project '%s'", name, projectUUID)
}
72 changes: 72 additions & 0 deletions squarescale/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"io/ioutil"
"net/http"
"net/http/httputil"
"os"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -100,6 +102,76 @@ func (c *Client) put(path string, payload interface{}) (int, []byte, error) {
return code, response, err
}

func (c *Client) download(path, nodeName string) (int, error) {
var bodyReader io.Reader
req, err := http.NewRequest("GET", c.endpoint+path, bodyReader)
if err != nil {
return 0, err
}

req.Header.Set("Authorization", "bearer "+c.token)
req.Header.Set("API-Version", supportedAPI)

reqDump, err := httputil.DumpRequestOut(req, true)
if err != nil {
logger.Error.Println("Error dumping HTTP request:", err)
} else {
logger.Trace.Printf("REQUEST: %s", string(reqDump))
}

res, err := c.httpClient.Do(req)
if err != nil {
return 0, err
}
fName := ""
val, ok := res.Header["Content-Disposition"]
if ok {
if len(val) != 1 {
return 0, fmt.Errorf("Content-Disposition header value: %+v", val)
}
re := regexp.MustCompile(`.*filename="([^"]*)".*`)
fName = re.ReplaceAllString(val[0], "$1")
}

defer res.Body.Close()
// Check server response
if res.StatusCode != http.StatusOK {
var reason map[string]interface{}
rbytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return 0, fmt.Errorf("bad status: %s unabled to read body error: %+v", res.Status, err)
}
err1 := json.Unmarshal(rbytes, &reason)
if err1 != nil {
return 0, fmt.Errorf("bad status: %s unabled to decode error: %+v", res.Status, err1)
}
var val1 map[string]interface{}
val1, ok := reason["errors"].(map[string]interface{})
if ok {
val, ok := val1["Error"]
if ok {
return 0, fmt.Errorf("Error: %s", val.(string))
}
}
return 0, fmt.Errorf("bad status: %s", res.Status)
}

// Create the file
out, err := os.Create(fmt.Sprintf("%s_%s", nodeName, fName))
if err != nil {
return 0, err
}
defer out.Close()

// Writer the body to file
_, err = io.Copy(out, res.Body)
if err != nil {
return 0, err
}

return res.StatusCode, nil
}

func (c *Client) request(method, path string, payload interface{}) (int, []byte, error) {
var bodyReader io.Reader
if payload != nil {
Expand Down

0 comments on commit 80f9fdd

Please sign in to comment.