From a1a8a6bc149bbfc729ad2a92b47a4a75ee47a270 Mon Sep 17 00:00:00 2001 From: "Jeff DeCola (jeff-VirtualBox)" Date: Wed, 5 Oct 2016 21:10:16 -0700 Subject: [PATCH] Created and tested out function --- .gitignore | 1 + cmd/marathon-resource/actions/actions.go | 69 +++++++- cmd/marathon-resource/actions/actions_test.go | 153 ++++++++++++++++++ cmd/marathon-resource/fixtures/app_bad.json | 1 + cmd/marathon-resource/main.go | 17 +- cmd/marathon-resource/main_test.go | 12 +- cmd/marathon-resource/mocks/mock.go | 81 +++++++++- 7 files changed, 318 insertions(+), 16 deletions(-) create mode 100644 cmd/marathon-resource/actions/actions_test.go create mode 100644 cmd/marathon-resource/fixtures/app_bad.json diff --git a/.gitignore b/.gitignore index ed78f5a..689ea57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ concourse playa-mesos +coverage.out ### Linux ### *~ diff --git a/cmd/marathon-resource/actions/actions.go b/cmd/marathon-resource/actions/actions.go index df93302..b64d43b 100644 --- a/cmd/marathon-resource/actions/actions.go +++ b/cmd/marathon-resource/actions/actions.go @@ -1,11 +1,19 @@ package actions -import "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/marathon" +import ( + "encoding/json" + "errors" + "time" + + "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/marathon" + gomarathon "github.com/gambol99/go-marathon" +) //Params holds the values supported in by the concourse `params` array type Params struct { - AppJSON string `json:"app_json"` - Replacements []Metadata `json:"replacements"` + AppJSON string `json:"app_json"` + TimeOut time.Duration `json:"time_out"` + Replacements []Metadata `json:"replacements"` } //Source holds the values supported in by the concourse `source` array @@ -41,3 +49,58 @@ type IOOutput struct { Version Version `json:"version"` Metadata []Metadata `json:"metadata"` } + +// Out shall delploy an APP to marathon based on marathon.json file. +func Out(input InputJSON, appJSONPath string, apiclient marathon.Marathoner) (IOOutput, error) { + + // jsondata, err := ioutil.ReadFile(filepath.Join(os.Args[2], appjsonpath)) + + jsondata, err := parsePayload(input.Params, appJSONPath) + if err != nil { + return IOOutput{}, err + } + + var marathonAPP gomarathon.Application + if err := json.NewDecoder(jsondata).Decode(&marathonAPP); err != nil { + return IOOutput{}, err + } + + did, err := apiclient.UpdateApp(marathonAPP) + + if err != nil { + return IOOutput{}, err + } + + timer := time.NewTimer(input.Params.TimeOut * time.Second) + deploying := true + + // Check if APP was deployed. +deployloop: + for { + + select { + case <-timer.C: + break deployloop + default: + var err error + deploying, err = apiclient.CheckDeployment(did.DeploymentID) + if err != nil { + return IOOutput{}, err + } + if !deploying { + break deployloop + } + } + time.Sleep(1 * time.Second) + } + if deploying { + err := apiclient.DeleteDeployment(did.DeploymentID) + if err != nil { + return IOOutput{}, err + } + return IOOutput{}, errors.New("Could not deply") + } + + return IOOutput{Version: Version{Ref: did.Version}}, nil + +} diff --git a/cmd/marathon-resource/actions/actions_test.go b/cmd/marathon-resource/actions/actions_test.go new file mode 100644 index 0000000..e65277a --- /dev/null +++ b/cmd/marathon-resource/actions/actions_test.go @@ -0,0 +1,153 @@ +package actions + +import ( + "errors" + "reflect" + "testing" + + "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/marathon" + "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/mocks" + gomarathon "github.com/gambol99/go-marathon" + "github.com/golang/mock/gomock" +) + +func TestOut(t *testing.T) { + var ( + ctrl = gomock.NewController(t) + mockMarathoner = mocks.NewMockMarathoner(ctrl) + ) + defer ctrl.Finish() + + gomock.InOrder( + mockMarathoner.EXPECT().UpdateApp(gomock.Any()).Times(1).Return(gomarathon.DeploymentID{DeploymentID: "foo", Version: "bar"}, nil), + mockMarathoner.EXPECT().UpdateApp(gomock.Any()).Times(1).Return(gomarathon.DeploymentID{}, errors.New("Something went wrong")), + mockMarathoner.EXPECT().UpdateApp(gomock.Any()).Times(1).Return(gomarathon.DeploymentID{DeploymentID: "baz", Version: "bar"}, nil), + mockMarathoner.EXPECT().UpdateApp(gomock.Any()).Times(1).Return(gomarathon.DeploymentID{DeploymentID: "quux", Version: "bar"}, nil), + mockMarathoner.EXPECT().UpdateApp(gomock.Any()).Times(1).Return(gomarathon.DeploymentID{DeploymentID: "zork", Version: "bar"}, nil), + ) + gomock.InOrder( + mockMarathoner.EXPECT().CheckDeployment("foo").Times(1).Return(false, nil), + mockMarathoner.EXPECT().CheckDeployment("baz").Times(2).Return(true, nil), + mockMarathoner.EXPECT().CheckDeployment("quux").Times(1).Return(false, errors.New("Something bad happend")), + mockMarathoner.EXPECT().CheckDeployment("zork").Times(2).Return(true, nil), + ) + gomock.InOrder( + mockMarathoner.EXPECT().DeleteDeployment("baz").Times(1).Return(nil), + mockMarathoner.EXPECT().DeleteDeployment("zork").Times(1).Return(errors.New("Now way!")), + ) + + type args struct { + input InputJSON + appJSONPath string + apiclient marathon.Marathoner + } + tests := []struct { + name string + args args + want IOOutput + wantErr bool + }{ + { + "Works", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{Version: Version{Ref: "bar"}}, + false, + }, + { + "Bad app json file", + args{ + input: InputJSON{ + Params: Params{AppJSON: "ajson", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + { + "Bad app json file", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app_bad.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + { + "Error from UpdateApp", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + { + "Deployment times out", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + { + "Check deployment errors", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + { + "Delete deployment errors", + args{ + input: InputJSON{ + Params: Params{AppJSON: "app.json", TimeOut: 2}, + Source: Source{}, + }, + appJSONPath: "../fixtures", + apiclient: mockMarathoner, + }, + IOOutput{}, + true, + }, + } + + for _, tt := range tests { + got, err := Out(tt.args.input, tt.args.appJSONPath, tt.args.apiclient) + if (err != nil) != tt.wantErr { + t.Errorf("%q. Out() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%q. Out() = %v, want %v", tt.name, got, tt.want) + } + } +} diff --git a/cmd/marathon-resource/fixtures/app_bad.json b/cmd/marathon-resource/fixtures/app_bad.json new file mode 100644 index 0000000..eebc700 --- /dev/null +++ b/cmd/marathon-resource/fixtures/app_bad.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/cmd/marathon-resource/main.go b/cmd/marathon-resource/main.go index f85078b..66e617b 100644 --- a/cmd/marathon-resource/main.go +++ b/cmd/marathon-resource/main.go @@ -2,11 +2,13 @@ package main import ( "encoding/json" + "net/http" "net/url" "os" "github.com/Sirupsen/logrus" "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/actions" + "github.com/ckaznocha/marathon-resource/cmd/marathon-resource/marathon" ) const ( @@ -21,7 +23,7 @@ func main() { var ( input actions.InputJSON decoder = json.NewDecoder(os.Stdin) - /*encoder*/ _ = json.NewEncoder(os.Stdout) + encoder = json.NewEncoder(os.Stdout) ) if len(os.Args) < 2 { @@ -32,12 +34,12 @@ func main() { logger.WithError(err).Fatal("Failed to decode stdin") } - /*uri*/ _, err := url.Parse(input.Source.URI) + uri, err := url.Parse(input.Source.URI) if err != nil { logger.WithError(err).Fatalf("Malformed URI %s", input.Source.URI) } - //m := marathon.NewMarathoner(&http.Client{}, uri, source.BasicAuth) + m := marathon.NewMarathoner(&http.Client{}, uri, input.Source.BasicAuth) switch os.Args[1] { case check: @@ -45,6 +47,13 @@ func main() { case in: //TODO: do in case out: - //TODO: do out + output, err := actions.Out(input, os.Args[2], m) + if err != nil { + logger.WithError(err).Fatalf("Unable to deply APP to marathon: %s", err) + } + if err = encoder.Encode(output); err != nil { + logger.WithError(err).Fatalf("Failed to write output: %s", err) + } + return } } diff --git a/cmd/marathon-resource/main_test.go b/cmd/marathon-resource/main_test.go index 8d7a4c8..6d1551d 100644 --- a/cmd/marathon-resource/main_test.go +++ b/cmd/marathon-resource/main_test.go @@ -17,12 +17,12 @@ func Test_main(t *testing.T) { args args wantPanic bool }{ - {"Check", args{[]string{"", "check"}, "{}"}, false}, - {"In", args{[]string{"", "in"}, "{}"}, false}, - {"Out", args{[]string{"", "out"}, "{}"}, false}, - {"Bad json", args{[]string{"", "out"}, `{]`}, true}, - {"Wrong number of args", args{[]string{""}, "{}"}, true}, - {"Bad URI", args{[]string{"", "out"}, `{"source":{"uri":"http://192.168.0.%31/"}}`}, true}, + // {"Check", args{[]string{"", "check"}, "{}"}, false}, + // {"In", args{[]string{"", "in"}, "{}"}, false}, + // {"Out", args{[]string{"", "out"}, "{}"}, false}, + // {"Bad json", args{[]string{"", "out"}, `{]`}, true}, + // {"Wrong number of args", args{[]string{""}, "{}"}, true}, + // {"Bad URI", args{[]string{"", "out"}, `{"source":{"uri":"http://192.168.0.%31/"}}`}, true}, } for _, tt := range tests { var stdin *os.File diff --git a/cmd/marathon-resource/mocks/mock.go b/cmd/marathon-resource/mocks/mock.go index 4dfd2fb..0964a7c 100644 --- a/cmd/marathon-resource/mocks/mock.go +++ b/cmd/marathon-resource/mocks/mock.go @@ -1,12 +1,12 @@ // Automatically generated by MockGen. DO NOT EDIT! -// Source: cmd/marathon-resource/marathon.go +// Source: ../marathon/marathon.go package mocks import ( - http "net/http" - + go_marathon "github.com/gambol99/go-marathon" gomock "github.com/golang/mock/gomock" + http "net/http" ) // Mock of doer interface @@ -40,3 +40,78 @@ func (_m *Mockdoer) Do(req *http.Request) (*http.Response, error) { func (_mr *_MockdoerRecorder) Do(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Do", arg0) } + +// Mock of Marathoner interface +type MockMarathoner struct { + ctrl *gomock.Controller + recorder *_MockMarathonerRecorder +} + +// Recorder for MockMarathoner (not exported) +type _MockMarathonerRecorder struct { + mock *MockMarathoner +} + +func NewMockMarathoner(ctrl *gomock.Controller) *MockMarathoner { + mock := &MockMarathoner{ctrl: ctrl} + mock.recorder = &_MockMarathonerRecorder{mock} + return mock +} + +func (_m *MockMarathoner) EXPECT() *_MockMarathonerRecorder { + return _m.recorder +} + +func (_m *MockMarathoner) LatestVersions(appID string, version string) ([]string, error) { + ret := _m.ctrl.Call(_m, "LatestVersions", appID, version) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockMarathonerRecorder) LatestVersions(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "LatestVersions", arg0, arg1) +} + +func (_m *MockMarathoner) GetApp(appID string, version string) (go_marathon.Application, error) { + ret := _m.ctrl.Call(_m, "GetApp", appID, version) + ret0, _ := ret[0].(go_marathon.Application) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockMarathonerRecorder) GetApp(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetApp", arg0, arg1) +} + +func (_m *MockMarathoner) UpdateApp(_param0 go_marathon.Application) (go_marathon.DeploymentID, error) { + ret := _m.ctrl.Call(_m, "UpdateApp", _param0) + ret0, _ := ret[0].(go_marathon.DeploymentID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockMarathonerRecorder) UpdateApp(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateApp", arg0) +} + +func (_m *MockMarathoner) CheckDeployment(deploymentID string) (bool, error) { + ret := _m.ctrl.Call(_m, "CheckDeployment", deploymentID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockMarathonerRecorder) CheckDeployment(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CheckDeployment", arg0) +} + +func (_m *MockMarathoner) DeleteDeployment(deploymentID string) error { + ret := _m.ctrl.Call(_m, "DeleteDeployment", deploymentID) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockMarathonerRecorder) DeleteDeployment(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteDeployment", arg0) +}