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" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"strconv" "strconv"
"time" "time"
gg "github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/ryanuber/columnize" "github.com/ryanuber/columnize"
) )
@ -222,3 +228,63 @@ READ:
// Just stream from the underlying reader now // Just stream from the underlying reader now
return l.ReadCloser.Read(p) 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 ( import (
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -185,3 +186,42 @@ func TestHelpers_LineLimitReader_TimeLimit(t *testing.T) {
t.Fatalf("did not exit by time limit") 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 package command
import ( import (
"crypto/tls"
"fmt" "fmt"
"io"
"net/http"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/scheduler" "github.com/hashicorp/nomad/scheduler"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
@ -30,10 +25,8 @@ potentially invalid.`
type PlanCommand struct { type PlanCommand struct {
Meta Meta
Helper
color *colorstring.Colorize color *colorstring.Colorize
// The fields below can be overwritten for tests
testStdin io.Reader
} }
func (c *PlanCommand) Help() string { func (c *PlanCommand) Help() string {
@ -76,9 +69,6 @@ Plan Options:
-verbose -verbose
Increase diff verbosity. Increase diff verbosity.
-k
Allow insecure SSL connections to access jobfile.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -88,13 +78,12 @@ func (c *PlanCommand) Synopsis() string {
} }
func (c *PlanCommand) Run(args []string) int { func (c *PlanCommand) Run(args []string) int {
var diff, verbose, insecure bool var diff, verbose bool
flags := c.Meta.FlagSet("plan", FlagSetClient) flags := c.Meta.FlagSet("plan", FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) } flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&diff, "diff", true, "") flags.BoolVar(&diff, "diff", true, "")
flags.BoolVar(&verbose, "verbose", false, "") flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&insecure, "k", false, "")
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
return 255 return 255
@ -107,66 +96,12 @@ func (c *PlanCommand) Run(args []string) int {
return 255 return 255
} }
var path, url string path := args[0]
// If prefix has http(s)://, read the Jobfile from the URL // Get Job struct from Jobfile
if strings.Index(args[0], "http://") == 0 || strings.Index(args[0], "https://") == 0 { job, err := c.Helper.StructJob(args[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)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %v", path, err)) c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 255 return 1
} }
// Initialize any fields that need to be. // Initialize any fields that need to be.

View File

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

View File

@ -2,20 +2,15 @@ package command
import ( import (
"bytes" "bytes"
"crypto/tls"
"encoding/gob" "encoding/gob"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs"
) )
@ -26,9 +21,7 @@ var (
type RunCommand struct { type RunCommand struct {
Meta Meta
Helper
// The fields below can be overwritten for tests
testStdin io.Reader
} }
func (c *RunCommand) Help() string { func (c *RunCommand) Help() string {
@ -82,10 +75,6 @@ Run Options:
-output -output
Output the JSON that would be submitted to the HTTP API without submitting Output the JSON that would be submitted to the HTTP API without submitting
the job. the job.
-k
Allow insecure SSL connections to access jobfile.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -95,7 +84,7 @@ func (c *RunCommand) Synopsis() string {
} }
func (c *RunCommand) Run(args []string) int { func (c *RunCommand) Run(args []string) int {
var detach, verbose, output, insecure bool var detach, verbose, output bool
var checkIndexStr string var checkIndexStr string
flags := c.Meta.FlagSet("run", FlagSetClient) flags := c.Meta.FlagSet("run", FlagSetClient)
@ -103,7 +92,6 @@ func (c *RunCommand) Run(args []string) int {
flags.BoolVar(&detach, "detach", false, "") flags.BoolVar(&detach, "detach", false, "")
flags.BoolVar(&verbose, "verbose", false, "") flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&output, "output", false, "") flags.BoolVar(&output, "output", false, "")
flags.BoolVar(&insecure, "k", false, "")
flags.StringVar(&checkIndexStr, "check-index", "", "") flags.StringVar(&checkIndexStr, "check-index", "", "")
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
@ -123,65 +111,17 @@ func (c *RunCommand) Run(args []string) int {
return 1 return 1
} }
var path, url string // Check that we got exactly one node
// If prefix has http(s)://, read the Jobfile from the URL args = flags.Args()
if strings.Index(args[0], "http://") == 0 || strings.Index(args[0], "https://") == 0 { if len(args) != 1 {
path = "_url" c.Ui.Error(c.Help())
url = args[0] return 1
} else {
// Read the Jobfile
path = args[0]
} }
var f io.Reader // Get Job struct from Jobfile
switch path { job, err := c.Helper.StructJob(args[0])
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)
if err != nil { 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 return 1
} }

View File

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

View File

@ -1,21 +1,13 @@
package command package command
import ( import (
"crypto/tls"
"fmt" "fmt"
"io"
"net/http"
"os"
"strings" "strings"
"github.com/hashicorp/nomad/jobspec"
) )
type ValidateCommand struct { type ValidateCommand struct {
Meta Meta
Helper
// The fields below can be overwritten for tests
testStdin io.Reader
} }
func (c *ValidateCommand) Help() string { 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 If the supplied path is "-", the jobfile is read from stdin. Otherwise
it is read from the file at the supplied path. it is read from the file at the supplied path.
-k
Allow insecure SSL connections to access jobfile.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
@ -40,10 +28,7 @@ func (c *ValidateCommand) Synopsis() string {
} }
func (c *ValidateCommand) Run(args []string) int { func (c *ValidateCommand) Run(args []string) int {
var insecure bool
flags := c.Meta.FlagSet("validate", FlagSetNone) flags := c.Meta.FlagSet("validate", FlagSetNone)
flags.BoolVar(&insecure, "k", false, "")
flags.Usage = func() { c.Ui.Output(c.Help()) } flags.Usage = func() { c.Ui.Output(c.Help()) }
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
return 1 return 1
@ -56,66 +41,10 @@ func (c *ValidateCommand) Run(args []string) int {
return 1 return 1
} }
// Read the Jobfile // Get Job struct from Jobfile
var path, url string job, err := c.Helper.StructJob(args[0])
// 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)
if err != nil { 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 return 1
} }

View File

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