Authenticate Handler API and SDK, and much more (#183)

This commit is contained in:
Todd Knight authored and GitHub committed 2020-07-14 17:32:47 -04:00
1 parent acce195dcb
commit 28dbcd84bd
89 files changed
+5296 -1943

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
// Code generated by "make api"; DO NOT EDIT.
package authtokens
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/internal/strutil"
)
type AuthToken struct {
Client *api.Client `json:"-"`
defaultFields []string
// The ID of the AuthToken
// Output only.
Id string `json:"id,omitempty"`
// The token. This will only be populated after login and is only ever visible to the end user whose login
// request resulted in this auth token being created.
// Output only.
Token string `json:"token,omitempty"`
// The id of the user of this AuthToken.
// Output only.
UserId string `json:"user_id,omitempty"`
// The id of the user of this AuthToken.
// Output only.
AuthMethodId string `json:"auth_method_id,omitempty"`
// The time this resource was created
// Output only.
CreatedTime time.Time `json:"created_time,omitempty"`
// The time this resource was last updated.
// Output only.
UpdatedTime time.Time `json:"updated_time,omitempty"`
// The approximate time this AuthToken was last used.
// Output only.
ApproximateLastUsedTime time.Time `json:"approximate_last_used_time,omitempty"`
// The time this AuthToken expires.
// Output only.
ExpirationTime time.Time `json:"expiration_time,omitempty"`
}
func (s *AuthToken) SetDefault(key string) {
lowerKey := strings.ToLower(key)
validMap := map[string]string{"approximatelastusedtime": "approximate_last_used_time", "authmethodid": "auth_method_id", "createdtime": "created_time", "expirationtime": "expiration_time", "id": "id", "token": "token", "updatedtime": "updated_time", "userid": "user_id"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *AuthToken) UnsetDefault(key string) {
lowerKey := strings.ToLower(key)
validMap := map[string]string{"approximatelastusedtime": "approximate_last_used_time", "authmethodid": "auth_method_id", "createdtime": "created_time", "expirationtime": "expiration_time", "id": "id", "token": "token", "updatedtime": "updated_time", "userid": "user_id"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s AuthToken) MarshalJSON() ([]byte, error) {
m := structs.Map(s)
if m == nil {
m = make(map[string]interface{})
}
for _, k := range s.defaultFields {
m[k] = nil
}
return json.Marshal(m)
}
+39 -7
View File
@@ -143,6 +143,7 @@ func DefaultConfig() (*Config, error) {
Addr: "https://127.0.0.1:9200",
HttpClient: cleanhttp.DefaultPooledClient(),
Timeout: time.Second * 60,
TLSConfig: &TLSConfig{},
}
// We read the environment now; after DefaultClient returns we can override
@@ -217,10 +218,10 @@ func (c *Config) ConfigureTLS() error {
return nil
}
// setAddress parses a given string and looks for org and project info, setting
// the actual address to the base. Note that if a very malformed URL is passed
// in, this may not return what one expects. For now this is on purpose to
// avoid requiring error handling.
// setAddr parses a given string and looks for org and project info, setting the
// actual address to the base. Note that if a very malformed URL is passed in,
// this may not return what one expects. For now this is on purpose to avoid
// requiring error handling.
//
// This also removes any trailing "/v1"; we'll use that in our commands so we
// don't require it from users.
@@ -449,6 +450,14 @@ func (c *Client) SetAddr(addr string) error {
return c.config.setAddr(addr)
}
// Org fetches the org the client will use by default
func (c *Client) Org() string {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
return c.config.Org
}
// SetOrg sets the org the client will use by default
func (c *Client) SetOrg(org string) {
c.modifyLock.Lock()
@@ -457,6 +466,14 @@ func (c *Client) SetOrg(org string) {
c.config.Org = org
}
// Project fetches the project the client will use by default
func (c *Client) Project() string {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
return c.config.Project
}
// SetProject sets the project the client will use by default
func (c *Client) SetProject(project string) {
c.modifyLock.Lock()
@@ -519,6 +536,14 @@ func (c *Client) SetOutputCurlString(curl bool) {
c.config.OutputCurlString = curl
}
// Token gets the configured token.
func (c *Client) Token() string {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
return c.config.Token
}
// SetToken sets the token directly. This won't perform any auth
// verification, it simply sets the token properly for future requests.
func (c *Client) SetToken(token string) {
@@ -700,18 +725,25 @@ func (c *Client) NewRequest(ctx context.Context, method, requestPath string, bod
orgProjPath = path.Join(orgProjPath, "projects", project)
}
p := path.Join(u.Path, "/v1", orgProjPath)
if strings.HasPrefix(requestPath, ":") {
p = p + requestPath
} else {
p = path.Join(p, requestPath)
}
req := &http.Request{
Method: method,
URL: &url.URL{
User: u.User,
Scheme: u.Scheme,
Host: host,
Path: path.Join(u.Path, "/v1", orgProjPath, requestPath),
Path: p,
},
Host: u.Host,
}
req.Header = headers
req.Header.Add("authorization", "bearer: "+token)
req.Header.Add("authorization", "Bearer "+token)
req.Header.Set("content-type", "application/json")
if ctx != nil {
req = req.WithContext(ctx)
@@ -786,7 +818,7 @@ func (c *Client) Do(r *retryablehttp.Request) (*Response, error) {
}
result, err := client.Do(r)
if result != nil && err == nil && result.StatusCode == 307 {
if result != nil && err == nil && result.StatusCode == http.StatusTemporaryRedirect {
loc, err := result.Location()
if err != nil {
return nil, fmt.Errorf("error getting new location during redirect: %w", err)
+20 -5
View File
@@ -3,6 +3,7 @@ package api
import (
"encoding/json"
"strings"
"github.com/fatih/structs"
@@ -13,22 +14,36 @@ type Error struct {
defaultFields []string
// The HTTP Status code applicable to this error
Status *int32 `json:"status,omitempty"`
Status int32 `json:"status,omitempty"`
// An application-specific error string
Code *string `json:"code,omitempty"`
Code string `json:"code,omitempty"`
// A human readable explanation specific to this occurrence of the error
Message *string `json:"message,omitempty"`
Message string `json:"message,omitempty"`
// Additional metadata regarding the error. Depending on the error
// different fields will be populated.
Details *ErrorDetails `json:"details,omitempty"`
}
func (s *Error) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"code": "code", "details": "details", "message": "message", "status": "status"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Error) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"code": "code", "details": "details", "message": "message", "status": "status"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Error) MarshalJSON() ([]byte, error) {
+19 -4
View File
@@ -3,6 +3,7 @@ package api
import (
"encoding/json"
"strings"
"github.com/fatih/structs"
@@ -12,17 +13,31 @@ import (
type ErrorDetails struct {
defaultFields []string
TraceId *string `json:"TraceId,omitempty"`
RequestId *string `json:"request_id,omitempty"`
TraceId string `json:"TraceId,omitempty"`
RequestId string `json:"request_id,omitempty"`
RequestFields []*FieldError `json:"request_fields,omitempty"`
}
func (s *ErrorDetails) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"requestfields": "request_fields", "requestid": "request_id", "traceid": "TraceId"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *ErrorDetails) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"requestfields": "request_fields", "requestid": "request_id", "traceid": "TraceId"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s ErrorDetails) MarshalJSON() ([]byte, error) {
+19 -4
View File
@@ -3,6 +3,7 @@ package api
import (
"encoding/json"
"strings"
"github.com/fatih/structs"
@@ -12,16 +13,30 @@ import (
type FieldError struct {
defaultFields []string
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}
func (s *FieldError) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"description": "description", "name": "name"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *FieldError) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"description": "description", "name": "name"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s FieldError) MarshalJSON() ([]byte, error) {
+17 -2
View File
@@ -3,6 +3,7 @@ package groups
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -34,11 +35,25 @@ type Group struct {
}
func (s *Group) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Group) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Group) MarshalJSON() ([]byte, error) {
+5 -5
View File
@@ -116,8 +116,8 @@ func TestGroup_Crud(t *testing.T) {
checkGroup := func(step string, g *groups.Group, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
}
assert.NotNil(g, "returned no resource", step)
gotName := ""
@@ -216,17 +216,17 @@ func TestGroup_Errors(t *testing.T) {
_, apiErr, err = tt.scope.ReadGroup(ctx, &groups.Group{Id: iam.GroupPrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
assert.EqualValues(apiErr.Status, http.StatusNotFound)
_, apiErr, err = tt.scope.ReadGroup(ctx, &groups.Group{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
_, apiErr, err = tt.scope.UpdateGroup(ctx, &groups.Group{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
})
}
}
+17 -2
View File
@@ -3,6 +3,7 @@ package hosts
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -38,11 +39,25 @@ type Host struct {
}
func (s *Host) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"address": "address", "createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Host) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"address": "address", "createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Host) MarshalJSON() ([]byte, error) {
+17 -2
View File
@@ -3,6 +3,7 @@ package hosts
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -39,11 +40,25 @@ type HostCatalog struct {
}
func (s *HostCatalog) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"attributes": "attributes", "createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *HostCatalog) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"attributes": "attributes", "createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s HostCatalog) MarshalJSON() ([]byte, error) {
+5 -5
View File
@@ -29,8 +29,8 @@ func TestCatalogs_Crud(t *testing.T) {
checkCatalog := func(step string, hc *hosts.HostCatalog, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
}
assert.NotNil(hc, "returned no resource", step)
gotName := ""
@@ -94,15 +94,15 @@ func TestCatalogs_Errors(t *testing.T) {
assert.NoError(err)
// TODO: Should this be nil instead of just a catalog that has no values set
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
assert.EqualValues(apiErr.Status, http.StatusNotFound)
_, apiErr, err = p.ReadHostCatalog(ctx, &hosts.HostCatalog{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
_, apiErr, err = p.UpdateHostCatalog(ctx, &hosts.HostCatalog{Id: hc.Id, Type: api.String("Cant Update")})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
}
+17 -2
View File
@@ -3,6 +3,7 @@ package hosts
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -44,11 +45,25 @@ type HostSet struct {
}
func (s *HostSet) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "hosts": "hosts", "id": "id", "name": "name", "size": "size", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *HostSet) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "hosts": "hosts", "id": "id", "name": "name", "size": "size", "type": "type", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s HostSet) MarshalJSON() ([]byte, error) {
+86 -117
View File
@@ -12,150 +12,119 @@ type structInfo struct {
parentName string
detailName string
templateType templateType
nameJsonMap map[string]string
}
var inputStructs = []*structInfo{
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
"Error",
os.Getenv("GEN_BASEPATH") + "/api/error.gen.go",
"Error",
"api",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
inName: "Error",
outFile: os.Getenv("GEN_BASEPATH") + "/api/error.gen.go",
outName: "Error",
outPkg: "api",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
"ErrorDetails",
os.Getenv("GEN_BASEPATH") + "/api/error_details.gen.go",
"ErrorDetails",
"api",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
inName: "ErrorDetails",
outFile: os.Getenv("GEN_BASEPATH") + "/api/error_details.gen.go",
outName: "ErrorDetails",
outPkg: "api",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
"FieldError",
os.Getenv("GEN_BASEPATH") + "/api/field_error.gen.go",
"FieldError",
"api",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go",
inName: "FieldError",
outFile: os.Getenv("GEN_BASEPATH") + "/api/field_error.gen.go",
outName: "FieldError",
outPkg: "api",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host.pb.go",
"Host",
os.Getenv("GEN_BASEPATH") + "/api/hosts/host.gen.go",
"Host",
"hosts",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host.pb.go",
inName: "Host",
outFile: os.Getenv("GEN_BASEPATH") + "/api/hosts/host.gen.go",
outName: "Host",
outPkg: "hosts",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_set.pb.go",
"HostSet",
os.Getenv("GEN_BASEPATH") + "/api/hosts/host_set.gen.go",
"HostSet",
"hosts",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_set.pb.go",
inName: "HostSet",
outFile: os.Getenv("GEN_BASEPATH") + "/api/hosts/host_set.gen.go",
outName: "HostSet",
outPkg: "hosts",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
"HostCatalog",
os.Getenv("GEN_BASEPATH") + "/api/hosts/host_catalog.gen.go",
"HostCatalog",
"hosts",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
inName: "HostCatalog",
outFile: os.Getenv("GEN_BASEPATH") + "/api/hosts/host_catalog.gen.go",
outName: "HostCatalog",
outPkg: "hosts",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/scopes/org.pb.go",
"Org",
os.Getenv("GEN_BASEPATH") + "/api/scopes/org.gen.go",
"Org",
"scopes",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/scopes/org.pb.go",
inName: "Org",
outFile: os.Getenv("GEN_BASEPATH") + "/api/scopes/org.gen.go",
outName: "Org",
outPkg: "scopes",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/scopes/project.pb.go",
"Project",
os.Getenv("GEN_BASEPATH") + "/api/scopes/project.gen.go",
"Project",
"scopes",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/authtokens/authtoken.pb.go",
inName: "AuthToken",
outFile: os.Getenv("GEN_BASEPATH") + "/api/authtokens/authtoken.gen.go",
outName: "AuthToken",
outPkg: "authtokens",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/users/user.pb.go",
"User",
os.Getenv("GEN_BASEPATH") + "/api/users/user.gen.go",
"User",
"users",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/scopes/project.pb.go",
inName: "Project",
outFile: os.Getenv("GEN_BASEPATH") + "/api/scopes/project.gen.go",
outName: "Project",
outPkg: "scopes",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/groups/group.pb.go",
"Group",
os.Getenv("GEN_BASEPATH") + "/api/groups/group.gen.go",
"Group",
"groups",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/users/user.pb.go",
inName: "User",
outFile: os.Getenv("GEN_BASEPATH") + "/api/users/user.gen.go",
outName: "User",
outPkg: "users",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/roles/role.pb.go",
"Role",
os.Getenv("GEN_BASEPATH") + "/api/roles/role.gen.go",
"Role",
"roles",
"",
"",
"",
templateTypeResource,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/groups/group.pb.go",
inName: "Group",
outFile: os.Getenv("GEN_BASEPATH") + "/api/groups/group.gen.go",
outName: "Group",
outPkg: "groups",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
"StaticHostCatalogDetails",
os.Getenv("GEN_BASEPATH") + "/api/hosts/static_host_catalog.gen.go",
"StaticHostCatalogDetails",
"hosts",
"",
"HostCatalog",
"StaticHostCatalog",
templateTypeDetail,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/roles/role.pb.go",
inName: "Role",
outFile: os.Getenv("GEN_BASEPATH") + "/api/roles/role.gen.go",
outName: "Role",
outPkg: "roles",
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
"AwsEc2HostCatalogDetails",
os.Getenv("GEN_BASEPATH") + "/api/hosts/awsec2_host_catalog.gen.go",
"AwsEc2HostCatalogDetails",
"hosts",
"",
"HostCatalog",
"AwsEc2HostCatalog",
templateTypeDetail,
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/roles/role.pb.go",
inName: "Principal",
outFile: os.Getenv("GEN_BASEPATH") + "/api/roles/principal.gen.go",
outName: "Principal",
outPkg: "roles",
},
{
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
inName: "StaticHostCatalogDetails",
outFile: os.Getenv("GEN_BASEPATH") + "/api/hosts/static_host_catalog.gen.go",
outName: "StaticHostCatalogDetails",
outPkg: "hosts",
parentName: "HostCatalog",
detailName: "StaticHostCatalog",
templateType: templateTypeDetail,
},
{
inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
inName: "AwsEc2HostCatalogDetails",
outFile: os.Getenv("GEN_BASEPATH") + "/api/hosts/awsec2_host_catalog.gen.go",
outName: "AwsEc2HostCatalogDetails",
outPkg: "hosts",
parentName: "HostCatalog",
detailName: "AwsEc2HostCatalog",
templateType: templateTypeDetail,
},
}
+5 -6
View File
@@ -38,6 +38,7 @@ func parsePBs() {
os.Exit(1)
}
inputStruct.nameJsonMap = make(map[string]string)
ast.Walk(visitFn(func(n ast.Node) {
spec, ok := n.(*ast.TypeSpec)
if !ok {
@@ -114,11 +115,6 @@ func parsePBs() {
var selectorExpr *ast.SelectorExpr
switch typ := field.Type.(type) {
case *ast.Ident:
// Id values are immutable and should always exist, so
// don't make it a pointer
if field.Names[0].Name != "Id" {
typ.Name = "*" + typ.Name
}
goto TAGMODIFY
case *ast.ArrayType:
goto TAGMODIFY
@@ -222,7 +218,10 @@ func parsePBs() {
}
TAGMODIFY:
st.Fields.List[i].Tag.Value = "`" + regex.FindString(st.Fields.List[i].Tag.Value) + "`"
tagString := regex.FindString(st.Fields.List[i].Tag.Value)
st.Fields.List[i].Tag.Value = "`" + tagString + "`"
tagString = strings.TrimSuffix(strings.TrimPrefix(tagString, `json:"`), `,omitempty"`)
inputStruct.nameJsonMap[strings.ToLower(field.Names[0].Name)] = tagString
}
}), inAst)
+5 -5
View File
@@ -37,11 +37,6 @@ var readFuncs = map[string][]*readInfo{
targetType: "users.User",
path: "users",
},
{
baseType: "Project",
targetType: "hosts.HostCatalog",
path: "host-catalogs",
},
{
baseType: "Project",
targetType: "groups.Group",
@@ -52,6 +47,11 @@ var readFuncs = map[string][]*readInfo{
targetType: "roles.Role",
path: "roles",
},
{
baseType: "Project",
targetType: "hosts.HostCatalog",
path: "host-catalogs",
},
},
}
+20 -3
View File
@@ -25,10 +25,12 @@ func writeStructTemplates() {
Name string
Package string
StructFields string
NameJsonMap map[string]string
}{
Name: inputStruct.outName,
Package: inputStruct.outPkg,
StructFields: inputStruct.structFields,
NameJsonMap: inputStruct.nameJsonMap,
})
case templateTypeDetail:
@@ -61,11 +63,12 @@ func writeStructTemplates() {
// TODO: Add documentation around SetDefault and how it behaves when the field corresponding to the provided key is already set.
var structTemplate = template.Must(template.New("").Parse(
`// Code generated by "make api"; DO NOT EDIT.
package {{ .Package }}
package {{ .Package }}
import (
"context"
"encoding/json"
"strings"
"github.com/fatih/structs"
@@ -82,11 +85,25 @@ type {{ .Name }} struct {
}
func (s *{{ .Name }}) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := {{ printf "%#v" .NameJsonMap }}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *{{ .Name }}) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := {{ printf "%#v" .NameJsonMap }}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s {{ .Name }}) MarshalJSON() ([]byte, error) {
+26 -11
View File
@@ -32,25 +32,40 @@ func (r *Response) Decode(inStruct interface{}) (*Error, error) {
return nil, nil
}
// TODO Remove as we'll use the common err format for this
if r.resp.StatusCode == 403 {
// Nothing to be done
return &Error{
Status: http.StatusForbidden,
Message: "Forbidden",
}, nil
}
if inStruct == nil {
return nil, fmt.Errorf("nil value given to decode into and not a 204 response")
}
r.Body = new(bytes.Buffer)
if _, err := r.Body.ReadFrom(r.resp.Body); err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
apiErr := &Error{
Status: int32(r.resp.StatusCode),
}
if r.resp.Body != nil {
r.Body = new(bytes.Buffer)
if _, err := r.Body.ReadFrom(r.resp.Body); err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
dec := json.NewDecoder(bytes.NewReader(r.Body.Bytes()))
var apiErr Error
if r.resp.StatusCode >= 400 {
inStruct = &apiErr
}
if err := dec.Decode(inStruct); err != nil {
return nil, fmt.Errorf("error decoding response: %w; response was %s", err, r.Body.String())
if r.Body.Len() != 0 {
dec := json.NewDecoder(bytes.NewReader(r.Body.Bytes()))
if r.resp.StatusCode >= 400 {
inStruct = apiErr
}
if err := dec.Decode(inStruct); err != nil {
return nil, fmt.Errorf("error decoding response: %w; response was %s", err, r.Body.String())
}
}
}
if r.resp.StatusCode >= 400 {
return &apiErr, nil
return apiErr, nil
}
return nil, nil
+29 -15
View File
@@ -7,16 +7,17 @@ import (
"github.com/hashicorp/watchtower/api"
)
func (s Role) AddPrincipals(ctx context.Context, groups, users []string) (*Role, *api.Error, error) {
func (s Role) AddPrincipals(ctx context.Context, principalIds []string) (*Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in AddPrincipals request")
}
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"group_ids": groups,
"user_ids": users,
"version": s.Version,
"version": s.Version,
}
if len(principalIds) > 0 {
body["principal_ids"] = principalIds
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-principals", s.Id), body)
@@ -40,16 +41,20 @@ func (s Role) AddPrincipals(ctx context.Context, groups, users []string) (*Role,
return target, apiErr, nil
}
func (s Role) SetPrincipals(ctx context.Context, groups, users []string) (*Role, *api.Error, error) {
func (s Role) SetPrincipals(ctx context.Context, principalIds []string) (*Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in SetPrincipals request")
}
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"group_ids": groups,
"user_ids": users,
"version": s.Version,
"version": s.Version,
}
if len(principalIds) > 0 {
body["principal_ids"] = principalIds
} else if principalIds != nil {
// In this function, a non-nil but empty list means clear out
body["principal_ids"] = nil
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-principals", s.Id), body)
@@ -73,16 +78,17 @@ func (s Role) SetPrincipals(ctx context.Context, groups, users []string) (*Role,
return target, apiErr, nil
}
func (s Role) RemovePrincipals(ctx context.Context, groups, users []string) (*Role, *api.Error, error) {
func (s Role) RemovePrincipals(ctx context.Context, principalIds []string) (*Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in RemovePrincipals request")
}
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"group_ids": groups,
"user_ids": users,
"version": s.Version,
"version": s.Version,
}
if len(principalIds) > 0 {
body["principal_ids"] = principalIds
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-principals", s.Id), body)
@@ -113,9 +119,11 @@ func (s Role) AddGrants(ctx context.Context, grants []string) (*Role, *api.Error
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"grants": grants,
"version": s.Version,
}
if len(grants) > 0 {
body["grants"] = grants
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grants", s.Id), body)
if err != nil {
@@ -145,9 +153,13 @@ func (s Role) SetGrants(ctx context.Context, grants []string) (*Role, *api.Error
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"grants": grants,
"version": s.Version,
}
if len(grants) > 0 {
body["grants"] = grants
} else if grants != nil {
body["grants"] = nil
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grants", s.Id), body)
if err != nil {
@@ -177,9 +189,11 @@ func (s Role) RemoveGrants(ctx context.Context, grants []string) (*Role, *api.Er
// We assume that the client provided has the org and optionally the project id of the request.
body := map[string]interface{}{
"grants": grants,
"version": s.Version,
}
if len(grants) > 0 {
body["grants"] = grants
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grants", s.Id), body)
if err != nil {
+61
View File
@@ -0,0 +1,61 @@
// Code generated by "make api"; DO NOT EDIT.
package roles
import (
"encoding/json"
"strings"
"github.com/fatih/structs"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/internal/strutil"
)
type Principal struct {
Client *api.Client `json:"-"`
defaultFields []string
// The ID of the principal.
// Output only.
Id string `json:"id,omitempty"`
// The type of the principal.
// Output only.
Type string `json:"type,omitempty"`
// The scope ID of the principal.
// Output only.
ScopeId string `json:"scope_id,omitempty"`
}
func (s *Principal) SetDefault(key string) {
lowerKey := strings.ToLower(key)
validMap := map[string]string{"id": "id", "scopeid": "scope_id", "type": "type"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Principal) UnsetDefault(key string) {
lowerKey := strings.ToLower(key)
validMap := map[string]string{"id": "id", "scopeid": "scope_id", "type": "type"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Principal) MarshalJSON() ([]byte, error) {
m := structs.Map(s)
if m == nil {
m = make(map[string]interface{})
}
for _, k := range s.defaultFields {
m[k] = nil
}
return json.Marshal(m)
}
+23 -7
View File
@@ -3,6 +3,7 @@ package roles
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -40,13 +41,14 @@ type Role struct {
// The version can be used in subsiquent write requests to ensure this resource
// has not changed and to fail the write if it has.
// Output only.
Version *uint32 `json:"version,omitempty"`
// The principals that are assigned this role.
Version uint32 `json:"version,omitempty"`
// The IDs (only) of principals that are assigned to this role.
// Output only.
UserIds []string `json:"user_ids,omitempty"`
PrincipalIds []string `json:"principal_ids,omitempty"`
// The principals that are assigned to this role.
// Output only.
GroupIds []string `json:"group_ids,omitempty"`
// The grants that this role provides for it's principals.
Principals []*Principal `json:"principals,omitempty"`
// The grants that this role provides for its principals.
// Output only.
Grants []string `json:"grants,omitempty"`
// The canonical version of the grants in the grants field with the same index.
@@ -58,11 +60,25 @@ type Role struct {
}
func (s *Role) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "grants": "grants", "grantscanonical": "grants_canonical", "grantscopeid": "grant_scope_id", "grantsjson": "grants_json", "id": "id", "name": "name", "principalids": "principal_ids", "principals": "principals", "updatedtime": "updated_time", "version": "version"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Role) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "grants": "grants", "grantscanonical": "grants_canonical", "grantscopeid": "grant_scope_id", "grantsjson": "grants_json", "id": "id", "name": "name", "principalids": "principal_ids", "principals": "principals", "updatedtime": "updated_time", "version": "version"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Role) MarshalJSON() ([]byte, error) {
+86 -56
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"testing"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/roles"
@@ -27,7 +28,7 @@ type roleCrud interface {
}
func TestCustom(t *testing.T) {
tc := controller.NewTestController(t, nil)
tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true})
defer tc.Shutdown()
client := tc.Client()
@@ -56,6 +57,22 @@ func TestCustom(t *testing.T) {
},
}
hasPrincipal := func(role *roles.Role, principalId string) bool {
var foundInPrincipals bool
var foundInPrincipalIds bool
for _, v := range role.Principals {
if v.Id == principalId {
foundInPrincipals = true
}
}
for _, v := range role.PrincipalIds {
if v == principalId {
foundInPrincipalIds = true
}
}
return foundInPrincipals && foundInPrincipalIds
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
@@ -70,63 +87,52 @@ func TestCustom(t *testing.T) {
require.Nil(apiErr)
require.NotNil(r)
updatedRole, apiErr, err := r.AddPrincipals(ctx, []string{g.Id}, nil)
updatedRole, apiErr, err := r.AddPrincipals(ctx, []string{g.Id})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
assert.Contains(t, updatedRole.GroupIds, g.Id)
assert.Empty(t, updatedRole.UserIds)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.True(t, hasPrincipal(updatedRole, g.Id))
r = updatedRole
updatedRole, apiErr, err = r.SetPrincipals(ctx, nil, []string{user.Id})
updatedRole, apiErr, err = r.SetPrincipals(ctx, []string{user.Id})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
assert.Empty(t, updatedRole.GroupIds)
assert.Contains(t, updatedRole.UserIds, user.Id)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.True(t, hasPrincipal(updatedRole, user.Id))
r = updatedRole
updatedRole, apiErr, err = r.RemovePrincipals(ctx, nil, []string{user.Id})
updatedRole, apiErr, err = r.RemovePrincipals(ctx, []string{user.Id})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
assert.Empty(t, updatedRole.GroupIds)
assert.Empty(t, updatedRole.UserIds)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.Empty(t, updatedRole.Principals)
r = updatedRole
updatedRole, apiErr, err = r.AddGrants(ctx, []string{"id=*;actions=read"})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.Contains(t, updatedRole.Grants, "id=*;actions=read")
r = updatedRole
updatedRole, apiErr, err = r.SetGrants(ctx, []string{"id=*;actions=*"})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.Contains(t, updatedRole.Grants, "id=*;actions=*")
r = updatedRole
updatedRole, apiErr, err = r.RemoveGrants(ctx, []string{"id=*;actions=*"})
require.NoError(err)
require.Nil(apiErr, "Got error ", errorMessage(apiErr))
assert.Equal(t, *updatedRole.Version, (*r.Version)+1)
require.Nil(apiErr, "Got error ", apiErr)
assert.Equal(t, updatedRole.Version, r.Version+1)
assert.Empty(t, updatedRole.Grants)
})
}
}
func errorMessage(in *api.Error) string {
if in == nil {
return ""
}
return *in.Message
}
func TestRole_List(t *testing.T) {
assert := assert.New(t)
tc := controller.NewTestController(t, nil)
tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true})
defer tc.Shutdown()
client := tc.Client()
@@ -153,12 +159,19 @@ func TestRole_List(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
pl, apiErr, err := tc.scope.ListRoles(ctx)
assert.NoError(err)
p1, apiErr, err := tc.scope.ListRoles(ctx)
require.NoError(err)
assert.Nil(apiErr)
assert.Empty(pl)
var defaultRoleIds []string
if tc.name == "org" {
require.True(2 == len(p1))
defaultRoleIds = []string{p1[0].Id, p1[1].Id}
} else {
require.Empty(p1)
}
var expected []*roles.Role
for i := 0; i < 10; i++ {
@@ -166,21 +179,35 @@ func TestRole_List(t *testing.T) {
}
expected[0], apiErr, err = tc.scope.CreateRole(ctx, expected[0])
assert.NoError(err)
require.NoError(err)
assert.Nil(apiErr)
pl, apiErr, err = tc.scope.ListRoles(ctx)
p2, apiErr, err := tc.scope.ListRoles(ctx)
assert.NoError(err)
assert.Nil(apiErr)
assert.ElementsMatch(comparableSlice(expected[:1]), comparableSlice(pl))
var p2ForComparison []*roles.Role
for _, v := range p2 {
if !strutil.StrListContains(defaultRoleIds, v.Id) {
p2ForComparison = append(p2ForComparison, v)
}
}
assert.ElementsMatch(comparableSlice(expected[:1]), comparableSlice(p2ForComparison))
for i := 1; i < 10; i++ {
expected[i], apiErr, err = tc.scope.CreateRole(ctx, expected[i])
assert.NoError(err)
assert.Nil(apiErr)
}
pl, apiErr, err = tc.scope.ListRoles(ctx)
assert.ElementsMatch(comparableSlice(expected), comparableSlice(pl))
p3, apiErr, err := tc.scope.ListRoles(ctx)
require.NoError(err)
assert.Nil(apiErr)
var p3ForComparison []*roles.Role
for _, v := range p3 {
if !strutil.StrListContains(defaultRoleIds, v.Id) {
p3ForComparison = append(p3ForComparison, v)
}
}
assert.ElementsMatch(comparableSlice(expected), comparableSlice(p3ForComparison))
})
}
}
@@ -202,7 +229,7 @@ func comparableSlice(in []*roles.Role) []roles.Role {
}
func TestRole_Crud(t *testing.T) {
tc := controller.NewTestController(t, nil)
tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true})
defer tc.Shutdown()
client := tc.Client()
@@ -217,8 +244,8 @@ func TestRole_Crud(t *testing.T) {
checkRole := func(step string, g *roles.Role, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
}
assert.NotNil(g, "returned no resource", step)
gotName := ""
@@ -244,6 +271,7 @@ func TestRole_Crud(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
g, apiErr, err := tt.scope.CreateRole(tc.Context(), &roles.Role{Name: api.String("foo")})
checkRole("create", g, apiErr, err, "foo")
@@ -261,19 +289,20 @@ func TestRole_Crud(t *testing.T) {
checkRole("update", g, apiErr, err, "")
existed, apiErr, err := tt.scope.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
require.NoError(err)
assert.Nil(apiErr)
assert.True(existed, "Expected existing user when deleted, but it wasn't.")
existed, apiErr, err = tt.scope.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.False(t, existed, "Expected user to not exist when deleted, but it did.")
require.NoError(err)
assert.Nil(apiErr)
assert.False(existed, "Expected user to not exist when deleted, but it did.")
})
}
}
func TestRole_Errors(t *testing.T) {
assert := assert.New(t)
tc := controller.NewTestController(t, nil)
tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true})
defer tc.Shutdown()
ctx := tc.Context()
@@ -302,30 +331,31 @@ func TestRole_Errors(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
u, apiErr, err := tt.scope.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
require.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
// Create another resource with the same name.
_, apiErr, err = tt.scope.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
require.NoError(err)
assert.NotNil(apiErr)
_, apiErr, err = tt.scope.ReadRole(ctx, &roles.Role{Id: iam.RolePrefix + "_doesntexis"})
assert.NoError(err)
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
assert.EqualValues(http.StatusNotFound, apiErr.Status)
_, apiErr, err = tt.scope.ReadRole(ctx, &roles.Role{Id: "invalid id"})
assert.NoError(err)
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(http.StatusBadRequest, apiErr.Status)
_, apiErr, err = tt.scope.UpdateRole(ctx, &roles.Role{Id: u.Id})
assert.NoError(err)
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(http.StatusBadRequest, apiErr.Status)
})
}
}
+54
View File
@@ -0,0 +1,54 @@
package scopes
import (
"context"
"fmt"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/authtokens"
)
// TODO: This will need to be changed when we add Auth Method API to watchtower. We'll also need a better
// way to handle different auth method types.
func (s Org) Authenticate(ctx context.Context, authMethodId, name, password string) (*authtokens.AuthToken, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in Authenticate request")
}
if s.Id == "" {
// Assume the client has been configured with org already and
// move on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "org", s.Id)
}
reqBody := map[string]interface{}{
"credentials": map[string]string{
"name": name,
"password": password,
},
}
req, err := s.Client.NewRequest(ctx, "POST", fmt.Sprintf("auth-methods/%s:authenticate", authMethodId), reqBody)
if err != nil {
return nil, nil, fmt.Errorf("error creating Authenticate request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Authenticate call: %w", err)
}
target := new(authtokens.AuthToken)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Authenticate response: %w", err)
}
if target.Token != "" {
s.Client.SetToken(target.Token)
}
return target, apiErr, nil
}
+34
View File
@@ -0,0 +1,34 @@
package scopes_test
import (
"context"
"net/http"
"testing"
"github.com/hashicorp/watchtower/api/scopes"
"github.com/hashicorp/watchtower/internal/servers/controller"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAuthenticate(t *testing.T) {
assert := assert.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
org := &scopes.Org{
Client: client,
}
ctx := context.Background()
tok, apiErr, err := org.Authenticate(ctx, "am_anything", "admin", "hunter2")
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(tok)
_, apiErr, err = org.Authenticate(ctx, "am_anything", "wrong", "wrong")
assert.NoError(err)
require.NotNil(t, apiErr)
assert.EqualValuesf(http.StatusUnauthorized, apiErr.Status, "Expected unauthenticated, got %q", apiErr.Message)
}
+17 -2
View File
@@ -3,6 +3,7 @@ package scopes
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -32,11 +33,25 @@ type Org struct {
}
func (s *Org) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Org) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Org) MarshalJSON() ([]byte, error) {
+17 -2
View File
@@ -3,6 +3,7 @@ package scopes
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -34,11 +35,25 @@ type Project struct {
}
func (s *Project) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *Project) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s Project) MarshalJSON() ([]byte, error) {
+2 -2
View File
@@ -133,10 +133,10 @@ func TestProject_Errors(t *testing.T) {
assert.NoError(err)
// TODO: Should this be nil instead of just a Project that has no values set
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
assert.EqualValues(apiErr.Status, http.StatusNotFound)
_, apiErr, err = org.ReadProject(ctx, &scopes.Project{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
}
+41 -41
View File
@@ -177,47 +177,6 @@ func (s Org) ReadUser(ctx context.Context, r *users.User) (*users.User, *api.Err
return target, apiErr, nil
}
func (s Project) ReadHostCatalog(ctx context.Context, r *hosts.HostCatalog) (*hosts.HostCatalog, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadHostCatalog request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
if r.Id == "" {
return nil, nil, fmt.Errorf("empty hosts.HostCatalog ID field in ReadHostCatalog request")
}
req, err := s.Client.NewRequest(ctx, "GET", fmt.Sprintf("%s/%s", "host-catalogs", r.Id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating ReadHostCatalog request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during ReadHostCatalog call: %w", err)
}
target := new(hosts.HostCatalog)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding ReadHostCatalog repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
func (s Project) ReadGroup(ctx context.Context, r *groups.Group) (*groups.Group, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadGroup request")
@@ -299,3 +258,44 @@ func (s Project) ReadRole(ctx context.Context, r *roles.Role) (*roles.Role, *api
return target, apiErr, nil
}
func (s Project) ReadHostCatalog(ctx context.Context, r *hosts.HostCatalog) (*hosts.HostCatalog, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadHostCatalog request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
if r.Id == "" {
return nil, nil, fmt.Errorf("empty hosts.HostCatalog ID field in ReadHostCatalog request")
}
req, err := s.Client.NewRequest(ctx, "GET", fmt.Sprintf("%s/%s", "host-catalogs", r.Id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating ReadHostCatalog request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during ReadHostCatalog call: %w", err)
}
target := new(hosts.HostCatalog)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding ReadHostCatalog repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
+17 -2
View File
@@ -3,6 +3,7 @@ package users
import (
"encoding/json"
"strings"
"time"
"github.com/fatih/structs"
@@ -34,11 +35,25 @@ type User struct {
}
func (s *User) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v)
return
}
}
}
func (s *User) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
lowerKey := strings.ToLower(key)
validMap := map[string]string{"createdtime": "created_time", "description": "description", "disabled": "disabled", "id": "id", "name": "name", "updatedtime": "updated_time"}
for k, v := range validMap {
if k == lowerKey || v == lowerKey {
s.defaultFields = strutil.StrListDelete(s.defaultFields, v)
return
}
}
}
func (s User) MarshalJSON() ([]byte, error) {
+13 -8
View File
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUsers_List(t *testing.T) {
@@ -50,6 +51,8 @@ func TestUsers_List(t *testing.T) {
assert.Nil(apiErr)
}
ul, apiErr, err = org.ListUsers(ctx)
require.NoError(t, err)
assert.Nil(apiErr)
assert.ElementsMatch(comparableSlice(expected), comparableSlice(ul))
}
@@ -81,8 +84,8 @@ func TestUser_Crud(t *testing.T) {
checkUser := func(step string, u *users.User, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
}
assert.NotNil(u, "returned no resource", step)
gotName := ""
@@ -108,12 +111,14 @@ func TestUser_Crud(t *testing.T) {
u, apiErr, err = org.UpdateUser(tc.Context(), u)
checkUser("update", u, apiErr, err, "")
existed, apiErr, err := org.DeleteUser(tc.Context(), u)
assert.NoError(t, err)
existed, _, err := org.DeleteUser(tc.Context(), u)
require.NoError(t, err)
assert.Nil(t, apiErr)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
existed, apiErr, err = org.DeleteUser(tc.Context(), u)
assert.NoError(t, err)
require.NoError(t, err)
assert.Nil(t, apiErr)
assert.False(t, existed, "Expected user to not exist when deleted, but it did.")
}
@@ -141,15 +146,15 @@ func TestUser_Errors(t *testing.T) {
_, apiErr, err = org.ReadUser(ctx, &users.User{Id: iam.UserPrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
assert.EqualValues(apiErr.Status, http.StatusNotFound)
_, apiErr, err = org.ReadUser(ctx, &users.User{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
_, apiErr, err = org.UpdateUser(ctx, &users.User{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
assert.EqualValues(apiErr.Status, http.StatusBadRequest)
}