make client interface

This commit is contained in:
Mike Mason
2023-07-18 13:36:41 +00:00
parent 05762f5d75
commit f828546cfe
10 changed files with 72 additions and 46 deletions

View File

@@ -10,41 +10,46 @@ import (
"go.equinixmetal.net/infra9-metal-bridge/internal/metal/providers" "go.equinixmetal.net/infra9-metal-bridge/internal/metal/providers"
) )
// Client is the Equinix Metal API Client struct. // Client is the Equinix Metal Client Interface.
type Client struct { type Client interface {
providers.Provider
}
// client is the Equinix Metal Client struct.
type client struct {
logger *zap.Logger logger *zap.Logger
provider providers.Provider provider providers.Provider
} }
// GetOrganizationDetails fetches the organization id provided with its memberships. // GetOrganizationDetails fetches the organization id provided with its memberships.
func (c *Client) GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) { func (c *client) GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) {
return c.provider.GetOrganizationDetails(ctx, id) return c.provider.GetOrganizationDetails(ctx, id)
} }
// GetProjectDetails fetchs the provided project id with membership information. // GetProjectDetails fetchs the provided project id with membership information.
func (c *Client) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) { func (c *client) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) {
return c.provider.GetProjectDetails(ctx, id) return c.provider.GetProjectDetails(ctx, id)
} }
// GetUserDetails fetches the provided user id. // GetUserDetails fetches the provided user id.
func (c *Client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*models.UserDetails, error) { func (c *client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*models.UserDetails, error) {
return c.provider.GetUserDetails(ctx, id) return c.provider.GetUserDetails(ctx, id)
} }
// GetUserOrganizationRole returns the role for the user in the organization. // GetUserOrganizationRole returns the role for the user in the organization.
func (c *Client) GetUserOrganizationRole(ctx context.Context, userID, orgID gidx.PrefixedID) (string, error) { func (c *client) GetUserOrganizationRole(ctx context.Context, userID, orgID gidx.PrefixedID) (string, error) {
return c.provider.GetUserOrganizationRole(ctx, userID, orgID) return c.provider.GetUserOrganizationRole(ctx, userID, orgID)
} }
// GetUserProjectRole returns the role for the user in the project. // GetUserProjectRole returns the role for the user in the project.
func (c *Client) GetUserProjectRole(ctx context.Context, userID, projID gidx.PrefixedID) (string, error) { func (c *client) GetUserProjectRole(ctx context.Context, userID, projID gidx.PrefixedID) (string, error) {
return c.provider.GetUserProjectRole(ctx, userID, projID) return c.provider.GetUserProjectRole(ctx, userID, projID)
} }
// New creates a new Client. // New creates a new Equinix Metal Client.
func New(options ...Option) (*Client, error) { func New(options ...Option) (Client, error) {
client := new(Client) client := new(client)
for _, opt := range options { for _, opt := range options {
if err := opt(client); err != nil { if err := opt(client); err != nil {

View File

@@ -9,11 +9,11 @@ import (
) )
// Option is a Client configuration Option definition. // Option is a Client configuration Option definition.
type Option func(c *Client) error type Option func(c *client) error
// WithProvider sets the provider on the client. // WithProvider sets the provider on the client.
func WithProvider(provider providers.Provider) Option { func WithProvider(provider providers.Provider) Option {
return func(c *Client) error { return func(c *client) error {
c.provider = provider c.provider = provider
return nil return nil
@@ -22,7 +22,7 @@ func WithProvider(provider providers.Provider) Option {
// WithLogger sets the logger for the client. // WithLogger sets the logger for the client.
func WithLogger(logger *zap.Logger) Option { func WithLogger(logger *zap.Logger) Option {
return func(c *Client) error { return func(c *client) error {
c.logger = logger c.logger = logger
return nil return nil
@@ -31,7 +31,7 @@ func WithLogger(logger *zap.Logger) Option {
// WithConfig applies all configurations defined in the config. // WithConfig applies all configurations defined in the config.
func WithConfig(config Config) Option { func WithConfig(config Config) Option {
return func(c *Client) error { return func(c *client) error {
var options []Option var options []Option
if config.EMGQL.Populated() { if config.EMGQL.Populated() {

View File

@@ -26,7 +26,7 @@ type roleAssignmentData struct {
} }
// AssignRole assigns the provided member ID to the given role ID. // AssignRole assigns the provided member ID to the given role ID.
func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { func (c *client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error {
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
body, err := encodeJSON(RoleAssign{ body, err := encodeJSON(RoleAssign{
@@ -50,7 +50,7 @@ func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberI
} }
// UnassignRole removes the provided member ID from the given role ID. // UnassignRole removes the provided member ID from the given role ID.
func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { func (c *client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error {
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
body, err := encodeJSON(RoleAssign{ body, err := encodeJSON(RoleAssign{
@@ -74,7 +74,7 @@ func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, membe
} }
// ListRoleAssignments lists all assignments for the given role. // ListRoleAssignments lists all assignments for the given role.
func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) { func (c *client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) {
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
var response roleAssignmentData var response roleAssignmentData
@@ -98,7 +98,7 @@ func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID
} }
// RoleHasAssignment gets the assignments for the given role and check for the provided member id. // RoleHasAssignment gets the assignments for the given role and check for the provided member id.
func (c *Client) RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error) { func (c *client) RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error) {
assignments, err := c.ListRoleAssignments(ctx, roleID) assignments, err := c.ListRoleAssignments(ctx, roleID)
if err != nil { if err != nil {
return false, err return false, err

View File

@@ -12,21 +12,37 @@ import (
"time" "time"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"go.infratographer.com/x/gidx"
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( const (
defaultPermissionsURL = "https://permissions-api.hollow-a.sv15.metalkube.net" defaultPermissionsURL = "https://permissions-api.hollow-a.sv15.metalkube.net"
DefaultHTTPClientTimeout = 5 * time.Second defaultHTTPClientTimeout = 5 * time.Second
) )
// DefaultHTTPClient is the default HTTP client for the Permissions Client.
var DefaultHTTPClient = &http.Client{ var DefaultHTTPClient = &http.Client{
Timeout: DefaultHTTPClientTimeout, Timeout: defaultHTTPClientTimeout,
} }
// Client is the permissions client. // Client defines the Permissions API client interface.
type Client struct { type Client interface {
AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error
CreateRole(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (gidx.PrefixedID, error)
DeleteResourceRelationship(ctx context.Context, resourceID gidx.PrefixedID, relation string, relatedResourceID gidx.PrefixedID) error
DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error
FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error)
ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error)
ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error)
ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error)
RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error)
UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error
}
// client is the permissions client.
type client struct {
logger *zap.SugaredLogger logger *zap.SugaredLogger
httpClient *http.Client httpClient *http.Client
@@ -39,7 +55,7 @@ type Client struct {
// Do executes the provided request. // Do executes the provided request.
// If the out value is provided, the response will attempt to be json decoded. // If the out value is provided, the response will attempt to be json decoded.
func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { func (c *client) Do(req *http.Request, out any) (*http.Response, error) {
if c.token != "" { if c.token != "" {
req.Header.Set(echo.HeaderAuthorization, "Bearer "+c.token) req.Header.Set(echo.HeaderAuthorization, "Bearer "+c.token)
} }
@@ -63,7 +79,7 @@ func (c *Client) Do(req *http.Request, out any) (*http.Response, error) {
} }
// DoRequest creates a new request from the provided parameters and executes the request. // DoRequest creates a new request from the provided parameters and executes the request.
func (c *Client) DoRequest(ctx context.Context, method, path string, body io.Reader, out any) (*http.Response, error) { func (c *client) DoRequest(ctx context.Context, method, path string, body io.Reader, out any) (*http.Response, error) {
path = strings.TrimPrefix(path, c.baseURL.Path) path = strings.TrimPrefix(path, c.baseURL.Path)
pathURL, err := url.Parse(path) pathURL, err := url.Parse(path)
@@ -109,8 +125,8 @@ func encodeJSON(v any) (*bytes.Buffer, error) {
} }
// NewClient creats a new permissions client. // NewClient creats a new permissions client.
func NewClient(token string, options ...Option) (*Client, error) { func NewClient(token string, options ...Option) (Client, error) {
client := &Client{ client := &client{
logger: zap.NewNop().Sugar(), logger: zap.NewNop().Sugar(),
httpClient: DefaultHTTPClient, httpClient: DefaultHTTPClient,
token: token, token: token,

View File

@@ -29,7 +29,7 @@ func MustViperFlags(v *viper.Viper, flags *pflag.FlagSet) {
// WithConfig applies all configurations defined in the config. // WithConfig applies all configurations defined in the config.
func WithConfig(config Config) Option { func WithConfig(config Config) Option {
return func(c *Client) error { return func(c *client) error {
var options []Option var options []Option
if config.BaseURL != "" { if config.BaseURL != "" {
@@ -52,7 +52,7 @@ func WithConfig(config Config) Option {
// WithBaseURL updates the baseurl used by the client. // WithBaseURL updates the baseurl used by the client.
func WithBaseURL(baseURL string) Option { func WithBaseURL(baseURL string) Option {
return func(c *Client) error { return func(c *client) error {
u, err := url.Parse(baseURL) u, err := url.Parse(baseURL)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse emapi base url %s: %w", baseURL, err) return fmt.Errorf("failed to parse emapi base url %s: %w", baseURL, err)
@@ -66,7 +66,7 @@ func WithBaseURL(baseURL string) Option {
// WithBearerToken sets the bearer token to authenticate the request with. // WithBearerToken sets the bearer token to authenticate the request with.
func WithBearerToken(token string) Option { func WithBearerToken(token string) Option {
return func(c *Client) error { return func(c *client) error {
c.token = token c.token = token
return nil return nil

View File

@@ -7,11 +7,11 @@ import (
) )
// Option is a client configuration option definition. // Option is a client configuration option definition.
type Option func(*Client) error type Option func(*client) error
// WithLogger sets the logger for the client. // WithLogger sets the logger for the client.
func WithLogger(logger *zap.SugaredLogger) Option { func WithLogger(logger *zap.SugaredLogger) Option {
return func(c *Client) error { return func(c *client) error {
c.logger = logger c.logger = logger
return nil return nil
@@ -19,9 +19,9 @@ func WithLogger(logger *zap.SugaredLogger) Option {
} }
// WithHTTPClient sets the http client to be used by the client. // WithHTTPClient sets the http client to be used by the client.
func WithHTTPClient(client *http.Client) Option { func WithHTTPClient(httpClient *http.Client) Option {
return func(c *Client) error { return func(c *client) error {
c.httpClient = client c.httpClient = httpClient
return nil return nil
} }

View File

@@ -34,7 +34,7 @@ type ResourceRelationshipDeleteResponse struct {
} }
// DeleteResourceRelationship deletes the provided resources relationship to the given subject id. // DeleteResourceRelationship deletes the provided resources relationship to the given subject id.
func (c *Client) DeleteResourceRelationship(ctx context.Context, resourceID gidx.PrefixedID, relation string, relatedResourceID gidx.PrefixedID) error { func (c *client) DeleteResourceRelationship(ctx context.Context, resourceID gidx.PrefixedID, relation string, relatedResourceID gidx.PrefixedID) error {
path := fmt.Sprintf("/api/v1/resources/%s/relationships", resourceID.String()) path := fmt.Sprintf("/api/v1/resources/%s/relationships", resourceID.String())
body, err := encodeJSON(ResourceRelationshipRequest{ body, err := encodeJSON(ResourceRelationshipRequest{
@@ -61,7 +61,7 @@ func (c *Client) DeleteResourceRelationship(ctx context.Context, resourceID gidx
// ListResourceRelationships returns resources related to the given id. // ListResourceRelationships returns resources related to the given id.
// If relatedResourceType is not provied, relations to subjects are returned. // If relatedResourceType is not provied, relations to subjects are returned.
// If relatedResourceType is provided, relations to the given resource are returned which match the given type. // If relatedResourceType is provided, relations to the given resource are returned which match the given type.
func (c *Client) ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error) { func (c *client) ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error) {
query := url.Values{ query := url.Values{
"resourceType": []string{relatedResourceType}, "resourceType": []string{relatedResourceType},
} }

View File

@@ -34,7 +34,7 @@ type ResourceRole struct {
} }
// CreateRole creates a role on the given resource id with the provided actions. // CreateRole creates a role on the given resource id with the provided actions.
func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (gidx.PrefixedID, error) { func (c *client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (gidx.PrefixedID, error) {
path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String()) path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String())
body, err := encodeJSON(ResourceRoleCreate{ body, err := encodeJSON(ResourceRoleCreate{
@@ -59,7 +59,7 @@ func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, act
} }
// DeleteRole deletes the provided role. // DeleteRole deletes the provided role.
func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { func (c *client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error {
path := fmt.Sprintf("/api/v1/roles/%s", roleID.String()) path := fmt.Sprintf("/api/v1/roles/%s", roleID.String())
var response ResourceRoleDeleteResponse var response ResourceRoleDeleteResponse
@@ -76,7 +76,7 @@ func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error {
} }
// ListResourceRoles fetches all roles assigned to the provided resource. // ListResourceRoles fetches all roles assigned to the provided resource.
func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) { func (c *client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) {
path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String()) path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String())
var response struct { var response struct {
@@ -91,7 +91,7 @@ func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.Prefixed
} }
// FindResourceRoleByActions fetches roles assigned to the provided resource and finds the first role where the actions match the provided actions. // FindResourceRoleByActions fetches roles assigned to the provided resource and finds the first role where the actions match the provided actions.
func (c *Client) FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error) { func (c *client) FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error) {
roles, err := c.ListResourceRoles(ctx, resourceID) roles, err := c.ListResourceRoles(ctx, resourceID)
if err != nil { if err != nil {
return ResourceRole{}, err return ResourceRole{}, err

View File

@@ -21,7 +21,7 @@ func WithLogger(logger *zap.SugaredLogger) Option {
} }
// WithMetalClient sets the Equinix Metal client used by the service. // WithMetalClient sets the Equinix Metal client used by the service.
func WithMetalClient(client *metal.Client) Option { func WithMetalClient(client metal.Client) Option {
return func(s *service) error { return func(s *service) error {
s.metal = client s.metal = client
@@ -30,7 +30,7 @@ func WithMetalClient(client *metal.Client) Option {
} }
// WithPermissionsClient sets the permissions client used by the service. // WithPermissionsClient sets the permissions client used by the service.
func WithPermissionsClient(client *permissions.Client) Option { func WithPermissionsClient(client permissions.Client) Option {
return func(s *service) error { return func(s *service) error {
s.perms = client s.perms = client

View File

@@ -78,13 +78,18 @@ type Service interface {
IsAssignableResource(id gidx.PrefixedID) bool IsAssignableResource(id gidx.PrefixedID) bool
} }
// EventPublisher defines the required methods to publish events.
type EventPublisher interface {
PublishChange(ctx context.Context, subjectType string, change events.ChangeMessage) error
}
var _ Service = &service{} var _ Service = &service{}
type service struct { type service struct {
logger *zap.SugaredLogger logger *zap.SugaredLogger
publisher *events.Publisher publisher EventPublisher
metal *metal.Client metal metal.Client
perms *permissions.Client perms permissions.Client
idPrefixMap map[string]ObjectType idPrefixMap map[string]ObjectType
rootResource prefixedID rootResource prefixedID
@@ -100,7 +105,7 @@ func (r prefixedID) PrefixedID() gidx.PrefixedID {
} }
// New creates a new service. // New creates a new service.
func New(publisher *events.Publisher, metal *metal.Client, perms *permissions.Client, options ...Option) (Service, error) { func New(publisher EventPublisher, metal metal.Client, perms permissions.Client, options ...Option) (Service, error) {
svc := &service{ svc := &service{
publisher: publisher, publisher: publisher,
metal: metal, metal: metal,