diff --git a/apiserver/facades/client/modelgeneration/mocks/package_mock.go b/apiserver/facades/client/modelgeneration/mocks/package_mock.go index 34e35ff80b80..83f285248bf0 100644 --- a/apiserver/facades/client/modelgeneration/mocks/package_mock.go +++ b/apiserver/facades/client/modelgeneration/mocks/package_mock.go @@ -314,6 +314,34 @@ func (mr *MockGenerationMockRecorder) Commit(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockGeneration)(nil).Commit), arg0) } +// Completed mocks base method +func (m *MockGeneration) Completed() int64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Completed") + ret0, _ := ret[0].(int64) + return ret0 +} + +// Completed indicates an expected call of Completed +func (mr *MockGenerationMockRecorder) Completed() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Completed", reflect.TypeOf((*MockGeneration)(nil).Completed)) +} + +// CompletedBy mocks base method +func (m *MockGeneration) CompletedBy() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CompletedBy") + ret0, _ := ret[0].(string) + return ret0 +} + +// CompletedBy indicates an expected call of CompletedBy +func (mr *MockGenerationMockRecorder) CompletedBy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompletedBy", reflect.TypeOf((*MockGeneration)(nil).CompletedBy)) +} + // Config mocks base method func (m *MockGeneration) Config() map[string]settings.ItemChanges { m.ctrl.T.Helper() diff --git a/apiserver/facades/client/modelgeneration/modelgeneration.go b/apiserver/facades/client/modelgeneration/modelgeneration.go index 72d5dc5a8d95..480ebdf56d14 100644 --- a/apiserver/facades/client/modelgeneration/modelgeneration.go +++ b/apiserver/facades/client/modelgeneration/modelgeneration.go @@ -290,7 +290,8 @@ func (api *API) BranchInfo( } // ShowCommit will return details a commit given by its generationId -// An error is returned if no commit can be found corresponding to a branch. +// An error is returned if either no branch can be found corresponding to the generation id. +// Or the generation id given is below 1. func (api *APIV3) ShowCommit(arg params.GenerationId) (params.GenerationCommitResult, error) { result := params.GenerationCommitResult{} @@ -312,13 +313,12 @@ func (api *APIV3) ShowCommit(arg params.GenerationId) (params.GenerationCommitRe return result, nil } - //TODO: convert them - _, err = api.oneBranchInfo(branch, true) + generationCommit, err := api.getGenerationCommit(branch) if err != nil { return generationCommitResultError(err) } - //result.GenerationCommit. = generation.Applications + result.GenerationCommit = generationCommit return result, nil } @@ -403,6 +403,23 @@ func (api *API) oneBranchInfo(branch Generation, detailed bool) (params.Generati }, nil } +func (api *APIV3) getGenerationCommit(branch Generation) (params.GenerationCommit, error) { + + generation, err := api.oneBranchInfo(branch, true) + if err != nil { + return params.GenerationCommit{}, errors.Trace(err) + } + return params.GenerationCommit{ + BranchName: branch.BranchName(), + Completed: branch.Completed(), + CompletedBy: branch.CompletedBy(), + GenerationId: branch.GenerationId(), + Created: branch.Created(), + CreatedBy: branch.CreatedBy(), + Applications: generation.Applications, + }, nil +} + // HasActiveBranch returns a true result if the input model has an "in-flight" // branch matching the input name. func (api *API) HasActiveBranch(arg params.BranchArg) (params.BoolResult, error) { @@ -427,10 +444,6 @@ func (api *API) HasActiveBranch(arg params.BranchArg) (params.BoolResult, error) return result, nil } -func convertGenerationsToGenerationsCommit(generation Generation) params.GenerationCommit { - return params.GenerationCommit{} -} - func generationResultsError(err error) (params.GenerationResults, error) { return params.GenerationResults{Error: common.ServerError(err)}, nil } diff --git a/apiserver/facades/schema.json b/apiserver/facades/schema.json index 02ede8ce804a..e5667500c18e 100644 --- a/apiserver/facades/schema.json +++ b/apiserver/facades/schema.json @@ -23096,25 +23096,6 @@ } } }, - "ListCommits": { - "type": "object", - "properties": { - "Result": { - "$ref": "#/definitions/GenerationCommitResults" - } - } - }, - "ShowCommit": { - "type": "object", - "properties": { - "Params": { - "$ref": "#/definitions/GenerationId" - }, - "Result": { - "$ref": "#/definitions/GenerationCommitResult" - } - } - }, "TrackBranch": { "type": "object", "properties": { @@ -23280,7 +23261,8 @@ "required": [ "branch", "created", - "created-by" + "created-by", + "applications" ] }, "GenerationApplication": { @@ -23321,87 +23303,6 @@ "config" ] }, - "GenerationCommit": { - "type": "object", - "properties": { - "applications": { - "type": "array", - "items": { - "$ref": "#/definitions/GenerationApplication" - } - }, - "branch": { - "type": "string" - }, - "completed": { - "type": "integer" - }, - "completed-by": { - "type": "string" - }, - "created": { - "type": "integer" - }, - "created-by": { - "type": "string" - }, - "generation-id": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "branch", - "completed", - "completed-by", - "generation-id" - ] - }, - "GenerationCommitResult": { - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Error" - }, - "generation-commit": { - "$ref": "#/definitions/GenerationCommit" - } - }, - "additionalProperties": false, - "required": [ - "generation-commit" - ] - }, - "GenerationCommitResults": { - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Error" - }, - "generation-commit": { - "type": "array", - "items": { - "$ref": "#/definitions/GenerationCommit" - } - } - }, - "additionalProperties": false, - "required": [ - "generation-commit" - ] - }, - "GenerationId": { - "type": "object", - "properties": { - "generation-id": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "generation-id" - ] - }, "GenerationResults": { "type": "object", "properties": { diff --git a/cmd/juju/commands/main.go b/cmd/juju/commands/main.go index 19016c2860f3..ab560ca87dfd 100644 --- a/cmd/juju/commands/main.go +++ b/cmd/juju/commands/main.go @@ -373,6 +373,7 @@ func registerCommands(r commandRegistry, ctx *cmd.Context) { r.Register(model.NewDiffCommand()) r.Register(model.NewAbortCommand()) r.Register(model.NewCommitsCommand()) + r.Register(model.NewShowCommitCommand()) } r.Register(newMigrateCommand()) diff --git a/cmd/juju/model/export_test.go b/cmd/juju/model/export_test.go index 8c5791664c9b..ed462660026f 100644 --- a/cmd/juju/model/export_test.go +++ b/cmd/juju/model/export_test.go @@ -236,3 +236,11 @@ func NewListCommitsCommandForTest(api CommitsCommandAPI, store jujuclient.Client cmd.SetClientStore(store) return modelcmd.Wrap(cmd) } + +func NewShowCommitCommandForTest(api ShowCommitCommandAPI, store jujuclient.ClientStore) cmd.Command { + cmd := &ShowCommitCommand{ + api: api, + } + cmd.SetClientStore(store) + return modelcmd.Wrap(cmd) +} diff --git a/cmd/juju/model/showcommit.go b/cmd/juju/model/showcommit.go index bcb0037c3173..37f3c9cd86fb 100644 --- a/cmd/juju/model/showcommit.go +++ b/cmd/juju/model/showcommit.go @@ -65,10 +65,7 @@ type ShowCommitCommand struct { type ShowCommitCommandAPI interface { Close() error - // ListCommitsBranch commits the branch with the input name to the model, - // effectively completing it and applying - // all branch changes across the model. - // The new generation ID of the model is returned. + // ShowCommit shows the branches which were committed ShowCommit(func(time.Time) string) (model.GenerationCommit, error) } @@ -148,5 +145,30 @@ func (c *ShowCommitCommand) Run(ctx *cmd.Context) error { if err != nil { return err } - return c.out.Write(ctx, cmt) + return errors.Trace(c.out.Write(ctx, c.getFormattedOutput(cmt))) +} + +// Run implements the meaty part of the cmd.Command interface. +func (c *ShowCommitCommand) getFormattedOutput(gcm model.GenerationCommit) formattedShowCommit { + applications := map[string]formattedShowCommitApplications{gcm.BranchName: {gcm.Applications}} + commit := formattedShowCommit{ + Branch: applications, + CommittedAt: gcm.Completed, + CommittedBy: gcm.CompletedBy, + Created: gcm.Created, + CreatedBy: gcm.CreatedBy, + } + return commit +} + +type formattedShowCommit struct { + Branch map[string]formattedShowCommitApplications `json:"branch" yaml:"branch"` + CommittedAt string `json:"committed-at" yaml:"committed-at"` + CommittedBy string `json:"committed-by" yaml:"committed-by"` + Created string `json:"created" yaml:"created"` + CreatedBy string `json:"created-by" yaml:"created-by"` +} + +type formattedShowCommitApplications struct { + Applications []model.GenerationApplication `json:"applications" yaml:"applications"` } diff --git a/cmd/juju/model/showcommit_test.go b/cmd/juju/model/showcommit_test.go new file mode 100644 index 000000000000..c9fdc66fc148 --- /dev/null +++ b/cmd/juju/model/showcommit_test.go @@ -0,0 +1,162 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package model_test + +import ( + "fmt" + "github.com/golang/mock/gomock" + "github.com/juju/cmd" + "github.com/juju/cmd/cmdtesting" + jc "github.com/juju/testing/checkers" + "github.com/pkg/errors" + gc "gopkg.in/check.v1" + "regexp" + + "github.com/juju/juju/cmd/juju/model" + "github.com/juju/juju/cmd/juju/model/mocks" + coremodel "github.com/juju/juju/core/model" +) + +type showCommitsSuite struct { + generationBaseSuite + + api *mocks.MockShowCommitCommandAPI +} + +var _ = gc.Suite(&showCommitsSuite{}) + +func (s *showCommitsSuite) TestInitNoArg(c *gc.C) { + err := s.runInit() + c.Assert(err, gc.ErrorMatches, "expected exactly 1 commit id, got 0 arguments") +} + +func (s *showCommitsSuite) TestInitOneArg(c *gc.C) { + err := s.runInit("1") + c.Assert(err, jc.ErrorIsNil) +} + +func (s *showCommitsSuite) TestInitNotInt(c *gc.C) { + err := s.runInit("something") + c.Assert(err, gc.ErrorMatches, `encountered problem trying to parse "something" into an int`) +} + +func (s *showCommitsSuite) TestInitMoreArgs(c *gc.C) { + args := []string{"1", "2", "3"} + err := s.runInit(args...) + c.Assert(err, gc.ErrorMatches, "expected exactly 1 commit id, got 3 arguments") +} +func (s *showCommitsSuite) getMockValues() coremodel.GenerationCommit { + values := coremodel.GenerationCommit{ + Completed: "0001-01-01", + CompletedBy: "test-user", + Created: "0001-01-00", + CreatedBy: "test-user", + GenerationId: 1, + BranchName: "bla", + Applications: []coremodel.GenerationApplication{{ + ApplicationName: "redis", + UnitProgress: "1/2", + UnitDetail: &coremodel.GenerationUnits{ + UnitsTracking: []string{"redis/0"}, + UnitsPending: []string{"redis/1"}, + }, + ConfigChanges: map[string]interface{}{"databases": 8}, + }}, + } + return values +} + +func (s *showCommitsSuite) TestRunCommandJsonOutput(c *gc.C) { + defer s.setup(c).Finish() + result := s.getMockValues() + unwrap := regexp.MustCompile(`[\s+\n]`) + expected := unwrap.ReplaceAllLiteralString(` +{ + "branch": { + "bla": { + "applications": [ + { + "ApplicationName": "redis", + "UnitProgress": "1/2", + "UnitDetail": { + "UnitsTracking": [ + "redis/0" + ], + "UnitsPending": [ + "redis/1" + ] + }, + "ConfigChanges": { + "databases": 8 + } + } + ] + } + }, + "committed-at": "0001-01-01", + "committed-by": "test-user", + "created": "0001-01-00", + "created-by": "test-user" +} +`, "") + expected = expected + "\n" + s.api.EXPECT().ShowCommit(gomock.Any()).Return(result, nil) + ctx, err := s.runCommand(c, "1", "--format=json") + c.Assert(err, jc.ErrorIsNil) + output := cmdtesting.Stdout(ctx) + fmt.Println(output) + c.Assert(output, gc.Equals, expected) +} + +func (s *showCommitsSuite) TestRunCommandYamlOutput(c *gc.C) { + defer s.setup(c).Finish() + result := s.getMockValues() + expected := ` +branch: + bla: + applications: + - application: redis + progress: 1/2 + units: + tracking: + - redis/0 + incomplete: + - redis/1 + config: + databases: 8 +committed-at: "0001-01-01" +committed-by: test-user +created: 0001-01-00 +created-by: test-user +`[1:] + s.api.EXPECT().ShowCommit(gomock.Any()).Return(result, nil) + + ctx, err := s.runCommand(c, "1", "--format=yaml") + c.Assert(err, jc.ErrorIsNil) + c.Assert(cmdtesting.Stdout(ctx), gc.Matches, expected) +} + +func (s *showCommitsSuite) TestRunCommandAPIError(c *gc.C) { + defer s.setup(c).Finish() + + s.api.EXPECT().ShowCommit(gomock.Any()).Return(nil, errors.New("boom")) + + _, err := s.runCommand(c) + c.Assert(err, gc.ErrorMatches, "boom") +} + +func (s *showCommitsSuite) runInit(args ...string) error { + return cmdtesting.InitCommand(model.NewShowCommitCommandForTest(nil, s.store), args) +} + +func (s *showCommitsSuite) runCommand(c *gc.C, args ...string) (*cmd.Context, error) { + return cmdtesting.RunCommand(c, model.NewShowCommitCommandForTest(s.api, s.store), args...) +} + +func (s *showCommitsSuite) setup(c *gc.C) *gomock.Controller { + ctrl := gomock.NewController(c) + s.api = mocks.NewMockShowCommitCommandAPI(ctrl) + s.api.EXPECT().Close() + return ctrl +}