Use go-getter to get jobfile by URL

This commit is contained in:
Kenjiro Nakayama 2016-08-10 23:30:19 +09:00
parent 410e0f7fe3
commit 7fb866ae4c
8 changed files with 133 additions and 223 deletions

View File

@ -4,10 +4,16 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"time"
gg "github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/ryanuber/columnize"
)
@ -222,3 +228,63 @@ READ:
// Just stream from the underlying reader now
return l.ReadCloser.Read(p)
}
type Helper struct {
// The fields below can be overwritten for tests
testStdin io.Reader
}
// StructJob returns the Job struct from jobfile.
func (h *Helper) StructJob(jpath string) (*structs.Job, error) {
var jobfile io.Reader
switch jpath {
case "-":
if h.testStdin != nil {
jobfile = h.testStdin
} else {
jobfile = os.Stdin
}
default:
if len(jpath) == 0 {
return nil, fmt.Errorf("Error jobfile path has to be specified.")
}
job, err := ioutil.TempFile("", "jobfile")
if err != nil {
return nil, err
}
defer os.Remove(job.Name())
// Get the pwd
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
client := &gg.Client{
Src: jpath,
Pwd: pwd,
Dst: job.Name(),
}
if err := client.Get(); err != nil {
return nil, fmt.Errorf("Error getting jobfile from %q: %v", jpath, err)
} else {
file, err := os.Open(job.Name())
defer file.Close()
if err != nil {
return nil, fmt.Errorf("Error opening file %q: %v", jpath, err)
}
jobfile = file
}
}
// Parse the JobFile
jobStruct, err := jobspec.Parse(jobfile)
if err != nil {
fmt.Errorf("Error parsing job file from %s: %v", jpath, err)
return nil, err
}
return jobStruct, nil
}

View File

@ -3,6 +3,7 @@ package command
import (
"io"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
@ -185,3 +186,42 @@ func TestHelpers_LineLimitReader_TimeLimit(t *testing.T) {
t.Fatalf("did not exit by time limit")
}
}
func TestStructJob(t *testing.T) {
fh, err := ioutil.TempFile("", "nomad")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(fh.Name())
_, err = fh.WriteString(`
job "job1" {
type = "service"
datacenters = [ "dc1" ]
group "group1" {
count = 1
task "task1" {
driver = "exec"
resources = {}
}
restart{
attempts = 10
mode = "delay"
}
}
}`)
if err != nil {
t.Fatalf("err: %s", err)
}
h := &Helper{}
sj, err := h.StructJob(fh.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
err = sj.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
}

View File

@ -1,17 +1,12 @@
package command
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/scheduler"
"github.com/mitchellh/colorstring"
@ -30,10 +25,8 @@ potentially invalid.`
type PlanCommand struct {
Meta
Helper
color *colorstring.Colorize
// The fields below can be overwritten for tests
testStdin io.Reader
}
func (c *PlanCommand) Help() string {
@ -76,9 +69,6 @@ Plan Options:
-verbose
Increase diff verbosity.
-k
Allow insecure SSL connections to access jobfile.
`
return strings.TrimSpace(helpText)
}
@ -88,13 +78,12 @@ func (c *PlanCommand) Synopsis() string {
}
func (c *PlanCommand) Run(args []string) int {
var diff, verbose, insecure bool
var diff, verbose bool
flags := c.Meta.FlagSet("plan", FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&diff, "diff", true, "")
flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&insecure, "k", false, "")
if err := flags.Parse(args); err != nil {
return 255
@ -107,66 +96,12 @@ func (c *PlanCommand) Run(args []string) int {
return 255
}
var path, url string
// If prefix has http(s)://, read the Jobfile from the URL
if strings.Index(args[0], "http://") == 0 || strings.Index(args[0], "https://") == 0 {
path = "_url"
url = args[0]
} else {
// Read the Jobfile
path = args[0]
}
var f io.Reader
switch path {
case "-":
if c.testStdin != nil {
f = c.testStdin
} else {
f = os.Stdin
}
path = "stdin"
case "_url":
if len(url) == 0 {
c.Ui.Error(fmt.Sprintf("Error invalid Jobfile name"))
}
var resp *http.Response
var err error
if insecure == true {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err = client.Get(url)
} else {
resp, err = http.Get(url)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error accessing URL %s: %v", url, err))
return 1
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
c.Ui.Error(fmt.Sprintf("Error reading URL (%d) : %s", resp.StatusCode, resp.Status))
return 1
}
f = resp.Body
path = url
default:
file, err := os.Open(path)
defer file.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error opening file %q: %v", path, err))
return 255
}
f = file
}
// Parse the JobFile
job, err := jobspec.Parse(f)
path := args[0]
// Get Job struct from Jobfile
job, err := c.Helper.StructJob(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %v", path, err))
return 255
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 1
}
// Initialize any fields that need to be.

View File

@ -110,8 +110,8 @@ func TestPlanCommand_From_STDIN(t *testing.T) {
ui := new(cli.MockUi)
cmd := &PlanCommand{
Meta: Meta{Ui: ui},
testStdin: stdinR,
Meta: Meta{Ui: ui},
Helper: Helper{testStdin: stdinR},
}
go func() {

View File

@ -2,20 +2,15 @@ package command
import (
"bytes"
"crypto/tls"
"encoding/gob"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs"
)
@ -26,9 +21,7 @@ var (
type RunCommand struct {
Meta
// The fields below can be overwritten for tests
testStdin io.Reader
Helper
}
func (c *RunCommand) Help() string {
@ -82,10 +75,6 @@ Run Options:
-output
Output the JSON that would be submitted to the HTTP API without submitting
the job.
-k
Allow insecure SSL connections to access jobfile.
`
return strings.TrimSpace(helpText)
}
@ -95,7 +84,7 @@ func (c *RunCommand) Synopsis() string {
}
func (c *RunCommand) Run(args []string) int {
var detach, verbose, output, insecure bool
var detach, verbose, output bool
var checkIndexStr string
flags := c.Meta.FlagSet("run", FlagSetClient)
@ -103,7 +92,6 @@ func (c *RunCommand) Run(args []string) int {
flags.BoolVar(&detach, "detach", false, "")
flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&output, "output", false, "")
flags.BoolVar(&insecure, "k", false, "")
flags.StringVar(&checkIndexStr, "check-index", "", "")
if err := flags.Parse(args); err != nil {
@ -123,65 +111,17 @@ func (c *RunCommand) Run(args []string) int {
return 1
}
var path, url string
// If prefix has http(s)://, read the Jobfile from the URL
if strings.Index(args[0], "http://") == 0 || strings.Index(args[0], "https://") == 0 {
path = "_url"
url = args[0]
} else {
// Read the Jobfile
path = args[0]
// Check that we got exactly one node
args = flags.Args()
if len(args) != 1 {
c.Ui.Error(c.Help())
return 1
}
var f io.Reader
switch path {
case "-":
if c.testStdin != nil {
f = c.testStdin
} else {
f = os.Stdin
}
path = "stdin"
case "_url":
if len(url) == 0 {
c.Ui.Error(fmt.Sprintf("Error invalid Jobfile name"))
}
var resp *http.Response
var err error
if insecure == true {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err = client.Get(url)
} else {
resp, err = http.Get(url)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error accessing URL %s: %v", url, err))
return 1
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
c.Ui.Error(fmt.Sprintf("Error reading URL (%d) : %s", resp.StatusCode, resp.Status))
return 1
}
f = resp.Body
path = url
default:
file, err := os.Open(path)
defer file.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error opening file %q: %v", path, err))
return 1
}
f = file
}
// Parse the JobFile
job, err := jobspec.Parse(f)
// Get Job struct from Jobfile
job, err := c.Helper.StructJob(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing job file from %s: %v", path, err))
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 1
}

View File

@ -156,8 +156,8 @@ func TestRunCommand_From_STDIN(t *testing.T) {
ui := new(cli.MockUi)
cmd := &RunCommand{
Meta: Meta{Ui: ui},
testStdin: stdinR,
Meta: Meta{Ui: ui},
Helper: Helper{testStdin: stdinR},
}
go func() {

View File

@ -1,21 +1,13 @@
package command
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/hashicorp/nomad/jobspec"
)
type ValidateCommand struct {
Meta
// The fields below can be overwritten for tests
testStdin io.Reader
Helper
}
func (c *ValidateCommand) Help() string {
@ -27,10 +19,6 @@ Usage: nomad validate [options] <file>
If the supplied path is "-", the jobfile is read from stdin. Otherwise
it is read from the file at the supplied path.
-k
Allow insecure SSL connections to access jobfile.
`
return strings.TrimSpace(helpText)
}
@ -40,10 +28,7 @@ func (c *ValidateCommand) Synopsis() string {
}
func (c *ValidateCommand) Run(args []string) int {
var insecure bool
flags := c.Meta.FlagSet("validate", FlagSetNone)
flags.BoolVar(&insecure, "k", false, "")
flags.Usage = func() { c.Ui.Output(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
@ -56,66 +41,10 @@ func (c *ValidateCommand) Run(args []string) int {
return 1
}
// Read the Jobfile
var path, url string
// If prefix has http(s)://, read the Jobfile from the URL
if strings.Index(args[0], "http://") == 0 || strings.Index(args[0], "https://") == 0 {
path = "_url"
url = args[0]
} else {
// Read the Jobfile
path = args[0]
}
var f io.Reader
switch path {
case "-":
if c.testStdin != nil {
f = c.testStdin
} else {
f = os.Stdin
}
path = "stdin"
case "_url":
if len(url) == 0 {
c.Ui.Error(fmt.Sprintf("Error invalid Jobfile name"))
}
var resp *http.Response
var err error
if insecure == true {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err = client.Get(url)
} else {
resp, err = http.Get(url)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error accessing URL %s: %v", url, err))
return 1
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
c.Ui.Error(fmt.Sprintf("Error reading URL (%d) : %s", resp.StatusCode, resp.Status))
return 1
}
f = resp.Body
path = url
default:
file, err := os.Open(path)
defer file.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error opening file %q: %v", path, err))
return 1
}
f = file
}
// Parse the JobFile
job, err := jobspec.Parse(f)
// Get Job struct from Jobfile
job, err := c.Helper.StructJob(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing job file from %s: %v", path, err))
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 1
}

View File

@ -111,8 +111,8 @@ func TestValidateCommand_From_STDIN(t *testing.T) {
ui := new(cli.MockUi)
cmd := &ValidateCommand{
Meta: Meta{Ui: ui},
testStdin: stdinR,
Meta: Meta{Ui: ui},
Helper: Helper{testStdin: stdinR},
}
go func() {