-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cloud credential validator, api for agent worker.
- Loading branch information
1 parent
6d41053
commit 6e563cd
Showing
15 changed files
with
861 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright 2018 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package credentialvalidator | ||
|
||
import ( | ||
"github.com/juju/errors" | ||
"github.com/juju/loggo" | ||
"gopkg.in/juju/names.v2" | ||
|
||
"github.com/juju/juju/api/base" | ||
apiwatcher "github.com/juju/juju/api/watcher" | ||
"github.com/juju/juju/apiserver/params" | ||
"github.com/juju/juju/watcher" | ||
) | ||
|
||
var logger = loggo.GetLogger("juju.api.credentialvalidator") | ||
|
||
// Facade provides methods that the Juju client command uses to interact | ||
// with the Juju backend. | ||
type Facade struct { | ||
facade base.FacadeCaller | ||
} | ||
|
||
// NewFacade creates a new `Facade` based on an existing authenticated API | ||
// connection. | ||
func NewFacade(caller base.APICaller) *Facade { | ||
return &Facade{base.NewFacadeCaller(caller, "CredentialValidator")} | ||
} | ||
|
||
// ModelCredential get cloud credential that a given model uses, including | ||
// useful data such as "is this credential valid"... | ||
// Some clouds do not require a credential and support the "empty" authentication | ||
// type. Models on these clouds will have no credentials set, and thus, will return | ||
// a false as 2nd argument. | ||
func (c *Facade) ModelCredential(modelUUID string) (base.StoredCredential, bool, error) { | ||
// Construct model tag from model uuid. | ||
in := params.Entities{ | ||
Entities: []params.Entity{{Tag: names.NewModelTag(modelUUID).String()}}, | ||
} | ||
|
||
// Call apiserver to get credential for this model. | ||
out := params.ModelCredentialResults{} | ||
emptyResult := base.StoredCredential{} | ||
if err := c.facade.FacadeCall("ModelCredentials", in, &out); err != nil { | ||
return emptyResult, false, errors.Trace(err) | ||
} | ||
|
||
// There should be just 1. | ||
if count := len(out.Results); count != 1 { | ||
return emptyResult, false, errors.Errorf("expected 1 model credential for model %q, got %d", modelUUID, count) | ||
} | ||
if err := out.Results[0].Error; err != nil { | ||
return emptyResult, false, errors.Trace(err) | ||
} | ||
|
||
result := out.Results[0].Result | ||
|
||
modelTag, err := names.ParseModelTag(result.Model) | ||
if err != nil { | ||
return emptyResult, false, errors.Trace(err) | ||
} | ||
if modelTag.Id() != modelUUID { | ||
return emptyResult, false, errors.Errorf("unexpected credential for model %q, expected credential for model %q", modelTag.Id(), modelUUID) | ||
} | ||
|
||
if !result.Exists { | ||
return emptyResult, false, nil | ||
} | ||
|
||
credentialTag, err := names.ParseCloudCredentialTag(result.CloudCredential) | ||
if err != nil { | ||
return emptyResult, false, errors.Trace(err) | ||
} | ||
return base.StoredCredential{ | ||
CloudCredential: credentialTag.Id(), | ||
Valid: result.Valid, | ||
}, true, nil | ||
} | ||
|
||
// WatchCredential provides notify watcher that is responsive to changes | ||
// to a given cloud credential. | ||
func (c *Facade) WatchCredential(credentialID string) (watcher.NotifyWatcher, error) { | ||
// Construct credential tag from given id. | ||
in := params.Entities{ | ||
Entities: []params.Entity{{Tag: names.NewCloudCredentialTag(credentialID).String()}}, | ||
} | ||
|
||
// Call apiserver to get the watcher for this credential. | ||
var results params.NotifyWatchResults | ||
err := c.facade.FacadeCall("WatchCredential", in, &results) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
// There should be just 1. | ||
if count := len(results.Results); count != 1 { | ||
return nil, errors.Errorf("expected 1 watcher for credential %q, got %d", credentialID, count) | ||
} | ||
|
||
if err := results.Results[0].Error; err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
w := apiwatcher.NewNotifyWatcher(c.facade.RawAPICaller(), results.Results[0]) | ||
return w, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
// Copyright 2018 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package credentialvalidator_test | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/juju/errors" | ||
jc "github.com/juju/testing/checkers" | ||
gc "gopkg.in/check.v1" | ||
names "gopkg.in/juju/names.v2" | ||
|
||
"github.com/juju/juju/api/base" | ||
"github.com/juju/juju/api/base/testing" | ||
"github.com/juju/juju/api/credentialvalidator" | ||
"github.com/juju/juju/apiserver/params" | ||
coretesting "github.com/juju/juju/testing" | ||
) | ||
|
||
var _ = gc.Suite(&CredentialValidatorSuite{}) | ||
|
||
type CredentialValidatorSuite struct { | ||
coretesting.BaseSuite | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredential(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
c.Check(objType, gc.Equals, "CredentialValidator") | ||
c.Check(request, gc.Equals, "ModelCredentials") | ||
c.Check(arg, jc.DeepEquals, params.Entities{ | ||
Entities: []params.Entity{{Tag: modelTag.String()}}, | ||
}) | ||
c.Assert(result, gc.FitsTypeOf, ¶ms.ModelCredentialResults{}) | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{ | ||
Model: modelTag.String(), | ||
CloudCredential: credentialTag.String(), | ||
Exists: true, | ||
Valid: true, | ||
}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
found, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Assert(exists, jc.IsTrue) | ||
c.Assert(found, gc.Equals, base.StoredCredential{CloudCredential: "cloud/user/credential", Valid: true}) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialIsNotNeeded(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{ | ||
Model: modelTag.String(), | ||
Exists: false, | ||
}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialManyResults(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{}}, | ||
{Result: ¶ms.ModelCredential{}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`expected 1 model credential for model %q, got 2`, modelUUID)) | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialForWrongModel(c *gc.C) { | ||
diffUUID := "d5757ef7-c86a-4835-84bc-7174af535e25" | ||
|
||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{ | ||
// different model UUID than the one supplied to the call | ||
Model: names.NewModelTag(diffUUID).String(), | ||
}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`unexpected credential for model %q, expected credential for model %q`, diffUUID, modelUUID)) | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialInvalidModelTag(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{ | ||
Model: "some-invalid-string-for-uuid", | ||
}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, `"some-invalid-string-for-uuid" is not a valid tag`) | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialInvalidCredentialTag(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Result: ¶ms.ModelCredential{ | ||
Model: modelTag.String(), | ||
Exists: true, | ||
CloudCredential: "some-invalid-cloud-credential-tag-as-string", | ||
}}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, `"some-invalid-cloud-credential-tag-as-string" is not a valid tag`) | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialError(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.ModelCredentialResults)) = params.ModelCredentialResults{ | ||
Results: []params.ModelCredentialResult{ | ||
{Error: ¶ms.Error{Message: "foo"}}}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, exists, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, "foo") | ||
c.Assert(exists, jc.IsFalse) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestModelCredentialCallError(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
return errors.New("foo") | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, _, err := client.ModelCredential(modelUUID) | ||
c.Assert(err, gc.ErrorMatches, "foo") | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestWatchCredential(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
c.Check(objType, gc.Equals, "CredentialValidator") | ||
c.Check(request, gc.Equals, "WatchCredential") | ||
c.Check(arg, jc.DeepEquals, params.Entities{ | ||
Entities: []params.Entity{{Tag: credentialTag.String()}}, | ||
}) | ||
c.Assert(result, gc.FitsTypeOf, ¶ms.NotifyWatchResults{}) | ||
*(result.(*params.NotifyWatchResults)) = params.NotifyWatchResults{ | ||
Results: []params.NotifyWatchResult{ | ||
{NotifyWatcherId: "notify-watcher-id"}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
found, err := client.WatchCredential(credentialID) | ||
c.Assert(err, jc.ErrorIsNil) | ||
c.Assert(found, gc.NotNil) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestWatchCredentialTooMany(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.NotifyWatchResults)) = params.NotifyWatchResults{ | ||
Results: []params.NotifyWatchResult{ | ||
{}, | ||
{}, | ||
}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, err := client.WatchCredential(credentialID) | ||
c.Assert(err, gc.ErrorMatches, fmt.Sprintf("expected 1 watcher for credential %q, got 2", credentialID)) | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestWatchCredentialError(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
*(result.(*params.NotifyWatchResults)) = params.NotifyWatchResults{ | ||
Results: []params.NotifyWatchResult{ | ||
{Error: ¶ms.Error{Message: "foo"}}}, | ||
} | ||
return nil | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, err := client.WatchCredential(credentialID) | ||
c.Assert(err, gc.ErrorMatches, "foo") | ||
} | ||
|
||
func (s *CredentialValidatorSuite) TestWatchCredentialCallError(c *gc.C) { | ||
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { | ||
return errors.New("foo") | ||
}) | ||
|
||
client := credentialvalidator.NewFacade(apiCaller) | ||
_, err := client.WatchCredential(credentialID) | ||
c.Assert(err, gc.ErrorMatches, "foo") | ||
} | ||
|
||
var ( | ||
modelUUID = "e5757df7-c86a-4835-84bc-7174af535d25" | ||
modelTag = names.NewModelTag(modelUUID) | ||
|
||
credentialID = "cloud/user/credential" | ||
credentialTag = names.NewCloudCredentialTag(credentialID) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2018 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package credentialvalidator_test | ||
|
||
import ( | ||
"testing" | ||
|
||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
func TestAll(t *testing.T) { | ||
gc.TestingT(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.