2022-07-20 16:24:46 -07:00
// Copyright (c) HashiCorp, Inc.
2020-08-20 08:20:26 -07:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-08-20 08:20:26 -07:00
package hosts
import (
"context"
"fmt"
2020-09-19 00:18:17 -04:00
"net"
2020-09-12 22:07:56 -07:00
"strings"
2020-08-20 08:20:26 -07:00
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/globals"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-20 08:20:26 -07:00
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/host"
2023-04-24 19:27:41 -04:00
hostplugin "github.com/hashicorp/boundary/internal/host/plugin"
2020-08-20 08:20:26 -07:00
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/host/static/store"
2023-10-30 14:41:12 -07:00
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
2021-01-20 10:53:01 -05:00
"github.com/hashicorp/boundary/internal/perms"
2023-04-24 19:27:41 -04:00
plugin "github.com/hashicorp/boundary/internal/plugin"
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/internal/requests"
2020-09-04 14:48:11 -07:00
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
2021-07-27 09:59:52 -07:00
"github.com/hashicorp/boundary/internal/types/subtypes"
2023-10-30 14:41:12 -07:00
"github.com/hashicorp/boundary/internal/util"
2021-08-26 10:44:41 -04:00
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts"
2021-11-02 14:06:20 -07:00
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins"
2023-10-11 18:26:16 +00:00
"golang.org/x/exp/maps"
2020-08-20 08:20:26 -07:00
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/wrapperspb"
)
2021-01-20 10:53:01 -05:00
var (
maskManager handlers . MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
2023-11-15 19:55:46 -05:00
idActionsTypeMap = map [ globals . Subtype ] action . ActionSet {
2023-10-31 13:44:17 +00:00
static . Subtype : action . NewActionSet (
2021-11-03 12:39:41 -04:00
action . NoOp ,
action . Read ,
action . Update ,
action . Delete ,
2023-10-31 13:44:17 +00:00
),
hostplugin . Subtype : action . NewActionSet (
2021-11-03 12:39:41 -04:00
action . NoOp ,
action . Read ,
2023-10-31 13:44:17 +00:00
),
2021-01-20 10:53:01 -05:00
}
// CollectionActions contains the set of actions that can be performed on
// this collection
2023-10-31 13:44:17 +00:00
CollectionActions = action . NewActionSet (
2021-01-20 10:53:01 -05:00
action . Create ,
action . List ,
2023-10-31 13:44:17 +00:00
)
2021-01-20 10:53:01 -05:00
)
2020-08-20 08:20:26 -07:00
func init () {
var err error
2023-06-06 17:09:08 -04:00
if maskManager , err = handlers . NewMaskManager (
context . Background (),
handlers . MaskDestination { & store . Host {}},
handlers . MaskSource { & pb . Host {}, & pb . StaticHostAttributes {}},
); err != nil {
2020-08-20 08:20:26 -07:00
panic ( err )
}
2023-10-11 18:26:16 +00:00
// TODO: refactor to remove idActionsMap and CollectionActions package variables
action . RegisterResource ( resource . Host , action . Union ( maps . Values ( idActionsTypeMap ) ... ), CollectionActions )
2020-08-20 08:20:26 -07:00
}
type Service struct {
2022-08-30 14:22:10 -04:00
pbs . UnsafeHostServiceServer
2020-11-11 14:41:05 -05:00
2020-08-20 08:20:26 -07:00
staticRepoFn common . StaticRepoFactory
2021-11-02 14:06:20 -07:00
pluginRepoFn common . PluginHostRepoFactory
2023-10-30 14:41:12 -07:00
maxPageSize uint
2020-08-20 08:20:26 -07:00
}
2022-08-30 14:22:10 -04:00
var _ pbs . HostServiceServer = ( * Service )( nil )
2020-08-20 08:20:26 -07:00
2020-10-08 12:36:01 -07:00
// NewService returns a host Service which handles host related requests to boundary and uses the provided
2020-08-20 08:20:26 -07:00
// repositories for storage and retrieval.
2023-10-30 14:41:12 -07:00
func NewService ( ctx context . Context , repoFn common . StaticRepoFactory , pluginRepoFn common . PluginHostRepoFactory , maxPageSize uint ) ( Service , error ) {
2021-04-16 12:06:03 -05:00
const op = "hosts.NewService"
2020-08-20 08:20:26 -07:00
if repoFn == nil {
2023-06-06 17:09:08 -04:00
return Service {}, errors . New ( ctx , errors . InvalidParameter , op , "missing static repository" )
2020-08-20 08:20:26 -07:00
}
2021-11-02 14:06:20 -07:00
if pluginRepoFn == nil {
2023-06-06 17:09:08 -04:00
return Service {}, errors . New ( ctx , errors . InvalidParameter , op , "missing plugin host repository" )
2021-11-02 14:06:20 -07:00
}
2023-10-30 14:41:12 -07:00
if maxPageSize == 0 {
maxPageSize = uint ( globals . DefaultMaxPageSize )
}
return Service { staticRepoFn : repoFn , pluginRepoFn : pluginRepoFn , maxPageSize : maxPageSize }, nil
2020-08-20 08:20:26 -07:00
}
func ( s Service ) ListHosts ( ctx context . Context , req * pbs . ListHostsRequest ) ( * pbs . ListHostsResponse , error ) {
2023-10-30 14:41:12 -07:00
const op = "hosts.(Service).ListHosts"
2023-06-06 17:09:08 -04:00
if err := validateListRequest ( ctx , req ); err != nil {
2023-10-30 14:41:12 -07:00
return nil , errors . Wrap ( ctx , err , op )
2020-08-20 08:20:26 -07:00
}
2020-09-04 14:48:11 -07:00
_ , authResults := s . parentAndAuthResult ( ctx , req . GetHostCatalogId (), action . List )
if authResults . Error != nil {
return nil , authResults . Error
}
2023-10-30 14:41:12 -07:00
pageSize := int ( s . maxPageSize )
// Use the requested page size only if it is smaller than
// the configured max.
if req . GetPageSize () != 0 && uint ( req . GetPageSize ()) < s . maxPageSize {
pageSize = int ( req . GetPageSize ())
2020-08-20 08:20:26 -07:00
}
2023-10-30 14:41:12 -07:00
var filterItemFn func ( ctx context . Context , item host . Host , plg * plugin . Plugin ) ( bool , error )
switch {
case req . GetFilter () != "" :
// Only use a filter if we need to
filter , err := handlers . NewFilter ( ctx , req . GetFilter ())
if err != nil {
return nil , err
}
// TODO: replace the need for this function with some way to convert the `filter`
// to a domain type. This would allow filtering to happen in the domain, and we could
// remove this callback altogether.
filterItemFn = func ( ctx context . Context , item host . Host , plg * plugin . Plugin ) ( bool , error ) {
outputOpts , ok := newOutputOpts ( ctx , item , plg , authResults )
if ! ok {
return false , nil
}
pbItem , err := toProto ( ctx , item , outputOpts ... )
if err != nil {
return false , err
}
filterable , err := subtypes . Filterable ( ctx , pbItem )
if err != nil {
return false , err
}
return filter . Match ( filterable ), nil
}
default :
filterItemFn = func ( ctx context . Context , item host . Host , plg * plugin . Plugin ) ( bool , error ) {
return true , nil
}
2021-05-04 19:57:35 -05:00
}
2023-10-30 14:41:12 -07:00
grantsHash , err := authResults . GrantsHash ( ctx )
2021-02-22 10:09:25 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
2023-10-30 14:41:12 -07:00
var listResp * pagination . ListResponse [ host . Host ]
var sortBy string
var plg * plugin . Plugin
switch globals . ResourceInfoFromPrefix ( req . GetHostCatalogId ()). Subtype {
case static . Subtype :
// Wrap the filter item func that takes a plugin, since the static host
// domain does not use a plugin.
staticFilterItemFn := func ( ctx context . Context , item host . Host ) ( bool , error ) {
return filterItemFn ( ctx , item , nil )
2021-02-22 10:09:25 -07:00
}
2023-10-30 14:41:12 -07:00
repo , err := s . staticRepoFn ()
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
2021-11-02 14:06:20 -07:00
}
2023-10-30 14:41:12 -07:00
if req . GetListToken () == "" {
sortBy = "created_time"
listResp , err = static . ListHosts ( ctx , grantsHash , pageSize , staticFilterItemFn , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
} else {
listToken , err := handlers . ParseListToken ( ctx , req . GetListToken (), resource . Host , grantsHash )
if err != nil {
return nil , err
}
switch st := listToken . Subtype .( type ) {
case * listtoken . PaginationToken :
sortBy = "created_time"
listResp , err = static . ListHostsPage ( ctx , grantsHash , pageSize , staticFilterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
case * listtoken . StartRefreshToken :
sortBy = "updated_time"
listResp , err = static . ListHostsRefresh ( ctx , grantsHash , pageSize , staticFilterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
case * listtoken . RefreshToken :
sortBy = "updated_time"
listResp , err = static . ListHostsRefreshPage ( ctx , grantsHash , pageSize , staticFilterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
default :
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . InvalidArgument , "unexpected list token subtype: %T" , st )
}
2021-05-04 19:57:35 -05:00
}
2023-10-30 14:41:12 -07:00
case hostplugin . Subtype :
repo , err := s . pluginRepoFn ()
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
if req . GetListToken () == "" {
sortBy = "created_time"
listResp , plg , err = hostplugin . ListHosts ( ctx , grantsHash , pageSize , filterItemFn , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
} else {
listToken , err := handlers . ParseListToken ( ctx , req . GetListToken (), resource . Host , grantsHash )
if err != nil {
return nil , err
}
switch st := listToken . Subtype .( type ) {
case * listtoken . PaginationToken :
sortBy = "created_time"
listResp , plg , err = hostplugin . ListHostsPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
case * listtoken . StartRefreshToken :
sortBy = "updated_time"
listResp , plg , err = hostplugin . ListHostsRefresh ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
case * listtoken . RefreshToken :
sortBy = "updated_time"
listResp , plg , err = hostplugin . ListHostsRefreshPage ( ctx , grantsHash , pageSize , filterItemFn , listToken , repo , req . GetHostCatalogId ())
if err != nil {
return nil , err
}
default :
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . InvalidArgument , "unexpected list token subtype: %T" , st )
}
}
}
finalItems := make ([] * pb . Host , 0 , len ( listResp . Items ))
for _ , item := range listResp . Items {
outputOpts , ok := newOutputOpts ( ctx , item , plg , authResults )
if ! ok {
continue
2021-05-04 19:57:35 -05:00
}
2023-10-30 14:41:12 -07:00
pbItem , err := toProto ( ctx , item , outputOpts ... )
2021-05-04 19:57:35 -05:00
if err != nil {
2023-10-30 14:41:12 -07:00
continue
2021-05-04 19:57:35 -05:00
}
2023-10-30 14:41:12 -07:00
finalItems = append ( finalItems , pbItem )
}
respType := "delta"
if listResp . CompleteListing {
respType = "complete"
}
resp := & pbs . ListHostsResponse {
Items : finalItems ,
EstItemCount : uint32 ( listResp . EstimatedItemCount ),
RemovedIds : listResp . DeletedIds ,
ResponseType : respType ,
SortBy : sortBy ,
SortDir : "desc" ,
}
2021-05-04 19:57:35 -05:00
2023-10-30 14:41:12 -07:00
if listResp . ListToken != nil {
resp . ListToken , err = handlers . MarshalListToken ( ctx , listResp . ListToken , pbs . ResourceType_RESOURCE_TYPE_HOST )
2022-04-13 12:56:31 -04:00
if err != nil {
return nil , err
}
2020-08-20 08:20:26 -07:00
}
2023-10-30 14:41:12 -07:00
return resp , nil
2020-08-20 08:20:26 -07:00
}
// GetHost implements the interface pbs.HostServiceServer.
func ( s Service ) GetHost ( ctx context . Context , req * pbs . GetHostRequest ) ( * pbs . GetHostResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "hosts.(Service).GetHost"
2020-08-20 08:20:26 -07:00
if err := validateGetRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
_ , authResults := s . parentAndAuthResult ( ctx , req . GetId (), action . Read )
if authResults . Error != nil {
return nil , authResults . Error
}
2021-11-02 14:06:20 -07:00
h , plg , err := s . getFromRepo ( ctx , req . GetId ())
2020-08-20 08:20:26 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-11-02 14:06:20 -07:00
if plg != nil {
outputOpts = append ( outputOpts , handlers . WithPlugin ( plg ))
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2023-11-15 19:55:46 -05:00
idActions := idActionsTypeMap [ globals . ResourceInfoFromPrefix ( req . GetId ()). Subtype ]
2021-11-03 12:39:41 -04:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , h . GetPublicId (), idActions ). Strings ()))
2021-05-04 19:57:35 -05:00
}
2022-02-01 10:06:13 -08:00
outputOpts = append ( outputOpts , handlers . WithHostSetIds ( h . GetSetIds ()))
2021-11-13 17:18:20 -05:00
item , err := toProto ( ctx , h , outputOpts ... )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
return & pbs . GetHostResponse { Item : item }, nil
2020-08-20 08:20:26 -07:00
}
// CreateHost implements the interface pbs.HostServiceServer.
func ( s Service ) CreateHost ( ctx context . Context , req * pbs . CreateHostRequest ) ( * pbs . CreateHostResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "hosts.(Service).CreateHost"
2020-08-20 08:20:26 -07:00
if err := validateCreateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
_ , authResults := s . parentAndAuthResult ( ctx , req . GetItem (). GetHostCatalogId (), action . Create )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-09-02 19:43:41 -07:00
h , err := s . createInRepo ( ctx , authResults . Scope . GetId (), req . GetItem (). GetHostCatalogId (), req . GetItem ())
2020-08-20 08:20:26 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2023-11-15 19:55:46 -05:00
idActions := idActionsTypeMap [ globals . ResourceInfoFromPrefix ( req . GetItem (). GetHostCatalogId ()). Subtype ]
2021-11-03 12:39:41 -04:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , h . GetPublicId (), idActions ). Strings ()))
2021-05-04 19:57:35 -05:00
}
2021-11-13 17:18:20 -05:00
item , err := toProto ( ctx , h , outputOpts ... )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
2020-08-20 08:20:26 -07:00
return & pbs . CreateHostResponse {
2021-05-04 19:57:35 -05:00
Item : item ,
Uri : fmt . Sprintf ( "hosts/%s" , item . GetId ()),
2020-08-20 08:20:26 -07:00
}, nil
}
// UpdateHost implements the interface pbs.HostServiceServer.
func ( s Service ) UpdateHost ( ctx context . Context , req * pbs . UpdateHostRequest ) ( * pbs . UpdateHostResponse , error ) {
2021-05-04 19:57:35 -05:00
const op = "hosts.(Service).UpdateHost"
2020-08-20 08:20:26 -07:00
if err := validateUpdateRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
cat , authResults := s . parentAndAuthResult ( ctx , req . GetId (), action . Update )
if authResults . Error != nil {
return nil , authResults . Error
}
2021-05-04 19:57:35 -05:00
h , err := s . updateInRepo ( ctx , authResults . Scope . GetId (), cat . GetPublicId (), req . GetId (), req . GetUpdateMask (). GetPaths (), req . GetItem ())
2020-08-20 08:20:26 -07:00
if err != nil {
return nil , err
}
2021-05-04 19:57:35 -05:00
outputFields , ok := requests . OutputFields ( ctx )
if ! ok {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "no request context found" )
2021-05-04 19:57:35 -05:00
}
outputOpts := make ([] handlers . Option , 0 , 3 )
2023-01-06 17:27:19 -06:00
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
2023-11-15 19:55:46 -05:00
idActions := idActionsTypeMap [ globals . ResourceInfoFromPrefix ( req . GetId ()). Subtype ]
2021-11-03 12:39:41 -04:00
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authResults . FetchActionSetForId ( ctx , h . GetPublicId (), idActions ). Strings ()))
2021-05-04 19:57:35 -05:00
}
2022-02-01 10:06:13 -08:00
outputOpts = append ( outputOpts , handlers . WithHostSetIds ( h . GetSetIds ()))
2021-05-04 19:57:35 -05:00
2021-11-13 17:18:20 -05:00
item , err := toProto ( ctx , h , outputOpts ... )
2021-05-04 19:57:35 -05:00
if err != nil {
return nil , err
}
return & pbs . UpdateHostResponse { Item : item }, nil
2020-08-20 08:20:26 -07:00
}
// DeleteHost implements the interface pbs.HostServiceServer.
func ( s Service ) DeleteHost ( ctx context . Context , req * pbs . DeleteHostRequest ) ( * pbs . DeleteHostResponse , error ) {
if err := validateDeleteRequest ( req ); err != nil {
return nil , err
}
2020-09-04 14:48:11 -07:00
_ , authResults := s . parentAndAuthResult ( ctx , req . GetId (), action . Delete )
if authResults . Error != nil {
return nil , authResults . Error
}
2020-09-08 23:57:26 -04:00
_ , err := s . deleteFromRepo ( ctx , authResults . Scope . GetId (), req . GetId ())
2020-08-20 08:20:26 -07:00
if err != nil {
return nil , err
}
2021-04-26 19:38:55 -05:00
return nil , nil
2020-08-20 08:20:26 -07:00
}
2021-11-02 14:06:20 -07:00
func ( s Service ) getFromRepo ( ctx context . Context , id string ) ( host . Host , * plugins . PluginInfo , error ) {
var h host . Host
var plg * plugins . PluginInfo
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( id ). Subtype {
2023-11-02 16:03:47 -04:00
case static . Subtype :
2021-11-02 14:06:20 -07:00
repo , err := s . staticRepoFn ()
if err != nil {
return nil , nil , err
}
h , err = repo . LookupHost ( ctx , id )
if err != nil {
return nil , nil , err
}
2023-10-30 14:41:12 -07:00
if util . IsNil ( h ) {
2021-11-02 14:06:20 -07:00
return nil , nil , handlers . NotFoundErrorf ( "Host %q doesn't exist." , id )
}
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-02 14:06:20 -07:00
repo , err := s . pluginRepoFn ()
if err != nil {
return nil , nil , err
}
ph , hPlg , err := repo . LookupHost ( ctx , id )
if err != nil {
return nil , nil , err
}
if ph == nil {
return nil , nil , handlers . NotFoundErrorf ( "Host %q doesn't exist." , id )
}
h = ph
plg = toPluginInfo ( hPlg )
2020-08-20 08:20:26 -07:00
}
2021-11-02 14:06:20 -07:00
return h , plg , nil
2020-08-20 08:20:26 -07:00
}
2022-07-20 16:24:46 -07:00
func ( s Service ) createInRepo ( ctx context . Context , projectId , catalogId string , item * pb . Host ) ( * static . Host , error ) {
2021-04-16 12:06:03 -05:00
const op = "hosts.(Service).createInRepo"
2022-04-13 12:56:31 -04:00
ha := item . GetStaticHostAttributes ()
2020-08-20 08:20:26 -07:00
var opts [] static . Option
if ha . GetAddress () != nil {
opts = append ( opts , static . WithAddress ( ha . GetAddress (). GetValue ()))
}
if item . GetName () != nil {
opts = append ( opts , static . WithName ( item . GetName (). GetValue ()))
}
if item . GetDescription () != nil {
opts = append ( opts , static . WithDescription ( item . GetDescription (). GetValue ()))
}
2023-06-06 17:09:08 -04:00
h , err := static . NewHost ( ctx , catalogId , opts ... )
2020-08-20 08:20:26 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "Unable to build host for creation" ))
2020-08-20 08:20:26 -07:00
}
repo , err := s . staticRepoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-08-20 08:20:26 -07:00
}
2022-07-20 16:24:46 -07:00
out , err := repo . CreateHost ( ctx , projectId , h )
2020-08-20 08:20:26 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "Unable to create host" ))
2020-08-20 08:20:26 -07:00
}
if out == nil {
2020-09-25 13:41:20 -07:00
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "Unable to create host but no error returned from repository." )
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-08-20 08:20:26 -07:00
}
2022-07-20 16:24:46 -07:00
func ( s Service ) updateInRepo ( ctx context . Context , projectId , catalogId , id string , mask [] string , item * pb . Host ) ( * static . Host , error ) {
2021-04-16 12:06:03 -05:00
const op = "hosts.(Service).updateInRepo"
2022-04-13 12:56:31 -04:00
ha := item . GetStaticHostAttributes ()
2020-08-20 08:20:26 -07:00
var opts [] static . Option
if desc := item . GetDescription (); desc != nil {
opts = append ( opts , static . WithDescription ( desc . GetValue ()))
}
if name := item . GetName (); name != nil {
opts = append ( opts , static . WithName ( name . GetValue ()))
}
if addr := ha . GetAddress (); addr != nil {
opts = append ( opts , static . WithAddress ( addr . GetValue ()))
}
2023-06-06 17:09:08 -04:00
h , err := static . NewHost ( ctx , catalogId , opts ... )
2020-08-20 08:20:26 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "Unable to build host for update" ))
2020-08-20 08:20:26 -07:00
}
h . PublicId = id
dbMask := maskManager . Translate ( mask )
if len ( dbMask ) == 0 {
2020-08-28 00:17:09 -04:00
return nil , handlers . InvalidArgumentErrorf ( "No valid fields included in the update mask." , map [ string ] string { "update_mask" : "No valid fields provided in the update mask." })
2020-08-20 08:20:26 -07:00
}
repo , err := s . staticRepoFn ()
if err != nil {
return nil , err
}
2022-07-20 16:24:46 -07:00
out , rowsUpdated , err := repo . UpdateHost ( ctx , projectId , h , item . GetVersion (), dbMask )
2020-08-20 08:20:26 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update host" ))
2020-08-20 08:20:26 -07:00
}
if rowsUpdated == 0 {
2020-09-25 13:41:20 -07:00
return nil , handlers . NotFoundErrorf ( "Host %q doesn't exist or incorrect version provided." , id )
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
return out , nil
2020-08-20 08:20:26 -07:00
}
2022-07-20 16:24:46 -07:00
func ( s Service ) deleteFromRepo ( ctx context . Context , projectId , id string ) ( bool , error ) {
2021-04-16 12:06:03 -05:00
const op = "hosts.(Service).deleteFromRepo"
2020-08-20 08:20:26 -07:00
repo , err := s . staticRepoFn ()
if err != nil {
return false , err
}
2022-07-20 16:24:46 -07:00
rows , err := repo . DeleteHost ( ctx , projectId , id )
2020-08-20 08:20:26 -07:00
if err != nil {
2021-07-29 18:52:56 -06:00
return false , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete host" ))
2020-08-20 08:20:26 -07:00
}
return rows > 0 , nil
}
2021-11-02 14:06:20 -07:00
func ( s Service ) parentAndAuthResult ( ctx context . Context , id string , a action . Type ) ( host . Catalog , auth . VerifyResults ) {
2020-09-04 14:48:11 -07:00
res := auth . VerifyResults {}
2021-11-02 14:06:20 -07:00
staticRepo , err := s . staticRepoFn ()
if err != nil {
res . Error = err
return nil , res
}
pluginRepo , err := s . pluginRepoFn ()
2020-09-04 14:48:11 -07:00
if err != nil {
res . Error = err
return nil , res
}
var parentId string
opts := [] auth . Option { auth . WithType ( resource . Host ), auth . WithAction ( a )}
switch a {
case action . List , action . Create :
parentId = id
default :
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( id ). Subtype {
2023-11-02 16:03:47 -04:00
case static . Subtype :
2021-11-02 14:06:20 -07:00
h , err := staticRepo . LookupHost ( ctx , id )
if err != nil {
res . Error = err
return nil , res
}
if h == nil {
res . Error = handlers . NotFoundError ()
return nil , res
}
parentId = h . GetCatalogId ()
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-02 14:06:20 -07:00
h , _ , err := pluginRepo . LookupHost ( ctx , id )
if err != nil {
res . Error = err
return nil , res
}
if h == nil {
res . Error = handlers . NotFoundError ()
return nil , res
}
parentId = h . GetCatalogId ()
}
opts = append ( opts , auth . WithId ( id ))
}
var cat host . Catalog
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( id ). Subtype {
2023-11-02 16:03:47 -04:00
case static . Subtype :
2021-11-16 17:36:09 -07:00
stcat , err := staticRepo . LookupCatalog ( ctx , parentId )
2020-09-04 14:48:11 -07:00
if err != nil {
res . Error = err
return nil , res
}
2021-11-16 17:36:09 -07:00
if stcat == nil {
2021-11-02 14:06:20 -07:00
res . Error = handlers . NotFoundError ()
return nil , res
}
2021-11-16 17:36:09 -07:00
cat = stcat
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-16 17:36:09 -07:00
plcat , _ , err := pluginRepo . LookupCatalog ( ctx , parentId )
2021-11-02 14:06:20 -07:00
if err != nil {
res . Error = err
return nil , res
}
2021-11-16 17:36:09 -07:00
if plcat == nil {
2020-09-08 23:57:26 -04:00
res . Error = handlers . NotFoundError ()
2020-09-04 14:48:11 -07:00
return nil , res
}
2021-11-16 17:36:09 -07:00
cat = plcat
2020-09-04 14:48:11 -07:00
}
2022-07-20 16:24:46 -07:00
opts = append ( opts , auth . WithScopeId ( cat . GetProjectId ()), auth . WithPin ( parentId ))
2021-11-02 14:06:20 -07:00
return cat , auth . Verify ( ctx , opts ... )
}
2020-09-04 14:48:11 -07:00
2023-04-24 19:27:41 -04:00
func toPluginInfo ( plg * plugin . Plugin ) * plugins . PluginInfo {
2021-11-02 14:06:20 -07:00
if plg == nil {
return nil
2020-09-04 14:48:11 -07:00
}
2021-11-02 14:06:20 -07:00
return & plugins . PluginInfo {
Id : plg . GetPublicId (),
Name : plg . GetName (),
Description : plg . GetDescription (),
2020-09-04 14:48:11 -07:00
}
}
2023-10-30 14:41:12 -07:00
func newOutputOpts ( ctx context . Context , item host . Host , plg * plugin . Plugin , authResults auth . VerifyResults ) ([] handlers . Option , bool ) {
res := perms . Resource {
2025-01-24 10:23:33 -08:00
ScopeId : authResults . Scope . Id ,
ParentScopeId : authResults . Scope . ParentScopeId ,
Type : resource . Host ,
Pin : item . GetCatalogId (),
Id : item . GetPublicId (),
2023-10-30 14:41:12 -07:00
}
res . Id = item . GetPublicId ()
idActions := idActionsTypeMap [ globals . ResourceInfoFromPrefix ( res . Id ). Subtype ]
authorizedActions := authResults . FetchActionSetForId ( ctx , item . GetPublicId (), idActions , auth . WithResource ( & res )). Strings ()
if len ( authorizedActions ) == 0 {
return nil , false
}
outputFields := authResults . FetchOutputFields ( res , action . List ). SelfOrDefaults ( authResults . UserId )
outputOpts := make ([] handlers . Option , 0 , 3 )
outputOpts = append ( outputOpts , handlers . WithOutputFields ( outputFields ))
if plg != nil {
outputOpts = append ( outputOpts , handlers . WithPlugin ( toPluginInfo ( plg )))
}
if outputFields . Has ( globals . ScopeField ) {
outputOpts = append ( outputOpts , handlers . WithScope ( authResults . Scope ))
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
outputOpts = append ( outputOpts , handlers . WithAuthorizedActions ( authorizedActions ))
}
outputOpts = append ( outputOpts , handlers . WithHostSetIds ( item . GetSetIds ()))
return outputOpts , true
}
2021-11-13 17:18:20 -05:00
func toProto ( ctx context . Context , in host . Host , opt ... handlers . Option ) ( * pb . Host , error ) {
2021-05-04 19:57:35 -05:00
opts := handlers . GetOpts ( opt ... )
if opts . WithOutputFields == nil {
return nil , handlers . ApiErrorWithCodeAndMessage ( codes . Internal , "output fields not found when building host proto" )
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
outputFields := * opts . WithOutputFields
out := pb . Host {}
if outputFields . Has ( globals . IdField ) {
out . Id = in . GetPublicId ()
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . HostCatalogIdField ) {
out . HostCatalogId = in . GetCatalogId ()
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . TypeField ) {
2021-11-02 14:06:20 -07:00
switch in .( type ) {
case * static . Host :
2023-11-02 16:03:47 -04:00
out . Type = static . Subtype . String ()
2023-04-24 19:27:41 -04:00
case * hostplugin . Host :
2023-11-02 16:03:47 -04:00
out . Type = hostplugin . Subtype . String ()
2021-11-02 14:06:20 -07:00
}
2020-08-20 08:20:26 -07:00
}
2021-05-04 19:57:35 -05:00
if outputFields . Has ( globals . DescriptionField ) && in . GetDescription () != "" {
out . Description = & wrapperspb . StringValue { Value : in . GetDescription ()}
}
if outputFields . Has ( globals . NameField ) && in . GetName () != "" {
out . Name = & wrapperspb . StringValue { Value : in . GetName ()}
}
if outputFields . Has ( globals . CreatedTimeField ) {
out . CreatedTime = in . GetCreateTime (). GetTimestamp ()
}
if outputFields . Has ( globals . UpdatedTimeField ) {
out . UpdatedTime = in . GetUpdateTime (). GetTimestamp ()
}
if outputFields . Has ( globals . VersionField ) {
2021-11-13 17:18:20 -05:00
out . Version = in . GetVersion ()
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . ScopeField ) {
out . Scope = opts . WithScope
}
if outputFields . Has ( globals . AuthorizedActionsField ) {
out . AuthorizedActions = opts . WithAuthorizedActions
}
2021-11-13 17:18:20 -05:00
if outputFields . Has ( globals . HostSetIdsField ) && len ( opts . WithHostSetIds ) > 0 {
out . HostSetIds = opts . WithHostSetIds
2021-05-04 19:57:35 -05:00
}
if outputFields . Has ( globals . AttributesField ) {
2021-11-02 14:06:20 -07:00
switch h := in .( type ) {
case * static . Host :
2022-04-13 12:56:31 -04:00
out . Attrs = & pb . Host_StaticHostAttributes {
StaticHostAttributes : & pb . StaticHostAttributes {
Address : wrapperspb . String ( h . GetAddress ()),
},
2021-11-02 14:06:20 -07:00
}
}
}
if outputFields . Has ( globals . PluginField ) {
out . Plugin = opts . WithPlugin
}
switch h := in .( type ) {
2023-04-24 19:27:41 -04:00
case * hostplugin . Host :
2021-11-02 14:06:20 -07:00
if outputFields . Has ( globals . IpAddressesField ) {
out . IpAddresses = h . IpAddresses
}
if outputFields . Has ( globals . DnsNamesField ) {
out . DnsNames = h . DnsNames
2021-05-04 19:57:35 -05:00
}
2021-11-13 17:18:20 -05:00
if outputFields . Has ( globals . ExternalIdField ) {
out . ExternalId = h . ExternalId
}
2023-03-15 15:24:25 +00:00
if outputFields . Has ( globals . ExternalNameField ) {
out . ExternalName = h . ExternalName
}
2020-08-20 08:20:26 -07:00
}
return & out , nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
2022-08-25 09:12:24 -07:00
// - The path passed in is correctly formatted
// - All required parameters are set
// - There are no conflicting parameters provided
// - The type asserted by the ID and/or field is known
// - If relevant, the type derived from the id prefix matches what is claimed by the type field
2020-08-20 08:20:26 -07:00
func validateGetRequest ( req * pbs . GetHostRequest ) error {
2021-04-08 20:54:32 -04:00
return handlers . ValidateGetRequest ( func () map [ string ] string {
2020-09-04 14:48:11 -07:00
badFields := map [ string ] string {}
2023-11-15 19:55:46 -05:00
ct := globals . ResourceInfoFromPrefix ( req . GetId ()). Subtype
if ct == globals . UnknownSubtype {
2020-09-04 14:48:11 -07:00
badFields [ "id" ] = "Improperly formatted identifier used."
}
return badFields
2023-03-10 15:06:39 -05:00
}, req , globals . StaticHostPrefix , globals . PluginHostPrefix , globals . PluginHostPreviousPrefix )
2020-08-20 08:20:26 -07:00
}
func validateCreateRequest ( req * pbs . CreateHostRequest ) error {
2020-08-21 08:15:32 -07:00
return handlers . ValidateCreateRequest ( req . GetItem (), func () map [ string ] string {
badFields := map [ string ] string {}
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( req . GetItem (). GetHostCatalogId ()), globals . StaticHostCatalogPrefix ) {
2020-08-21 08:15:32 -07:00
badFields [ "host_catalog_id" ] = "The field is incorrectly formatted."
2020-08-20 08:20:26 -07:00
}
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( req . GetItem (). GetHostCatalogId ()). Subtype {
2023-11-02 16:03:47 -04:00
case static . Subtype :
if req . GetItem (). GetType () != "" && req . GetItem (). GetType () != static . Subtype . String () {
2021-11-02 14:06:20 -07:00
badFields [ globals . TypeField ] = "Doesn't match the parent resource's type."
}
if len ( req . GetItem (). GetIpAddresses ()) > 0 {
badFields [ globals . IpAddressesField ] = "This field is not supported for this host type."
}
if len ( req . GetItem (). GetDnsNames ()) > 0 {
badFields [ globals . DnsNamesField ] = "This field is not supported for this host type."
2020-08-21 08:15:32 -07:00
}
2022-04-13 12:56:31 -04:00
attrs := req . GetItem (). GetStaticHostAttributes ()
2020-09-19 00:18:17 -04:00
switch {
2022-06-19 10:55:37 -04:00
case attrs == nil :
2023-01-26 10:35:29 -08:00
badFields [ globals . AttributesField ] = "This is a required field."
2020-09-19 00:18:17 -04:00
default :
2022-06-19 10:55:37 -04:00
if attrs . GetAddress () == nil ||
len ( attrs . GetAddress (). GetValue ()) < static . MinHostAddressLength ||
len ( attrs . GetAddress (). GetValue ()) > static . MaxHostAddressLength {
2023-01-26 10:35:29 -08:00
badFields [ globals . AttributesAddressField ] = fmt . Sprintf ( "Address length must be between %d and %d characters." , static . MinHostAddressLength , static . MaxHostAddressLength )
} else {
_ , _ , err := net . SplitHostPort ( attrs . GetAddress (). GetValue ())
switch {
case err == nil :
badFields [ globals . AttributesAddressField ] = "Address for static hosts does not support a port."
case strings . Contains ( err . Error (), globals . MissingPortErrStr ):
// Bare hostname, which we want
default :
badFields [ globals . AttributesAddressField ] = fmt . Sprintf ( "Error parsing address: %v." , err )
}
2022-06-19 10:55:37 -04:00
}
2020-09-19 00:18:17 -04:00
}
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-02 14:06:20 -07:00
badFields [ globals . HostCatalogIdField ] = "Cannot manually create hosts for this type of catalog."
2020-08-20 08:20:26 -07:00
}
2020-08-21 08:15:32 -07:00
return badFields
})
2020-08-20 08:20:26 -07:00
}
func validateUpdateRequest ( req * pbs . UpdateHostRequest ) error {
2021-04-08 20:54:32 -04:00
return handlers . ValidateUpdateRequest ( req , req . GetItem (), func () map [ string ] string {
2020-08-21 08:15:32 -07:00
badFields := map [ string ] string {}
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( req . GetId ()). Subtype {
2023-11-02 16:03:47 -04:00
case static . Subtype :
if req . GetItem (). GetType () != "" && req . GetItem (). GetType () != static . Subtype . String () {
2021-11-02 14:06:20 -07:00
badFields [ globals . TypeField ] = "Cannot modify the resource type."
2023-01-26 10:35:29 -08:00
}
if handlers . MaskContains ( req . GetUpdateMask (). GetPaths (), globals . AttributesAddressField ) {
2022-04-13 12:56:31 -04:00
attrs := req . GetItem (). GetStaticHostAttributes ()
2023-01-26 10:35:29 -08:00
switch {
case attrs == nil :
badFields [ globals . AttributesField ] = "Attributes field not supplied in request"
default :
if attrs . GetAddress () == nil ||
len ( strings . TrimSpace ( attrs . GetAddress (). GetValue ())) < static . MinHostAddressLength ||
len ( strings . TrimSpace ( attrs . GetAddress (). GetValue ())) > static . MaxHostAddressLength {
badFields [ globals . AttributesAddressField ] = fmt . Sprintf ( "Address length must be between %d and %d characters." , static . MinHostAddressLength , static . MaxHostAddressLength )
} else {
_ , _ , err := net . SplitHostPort ( attrs . GetAddress (). GetValue ())
switch {
case err == nil :
badFields [ globals . AttributesAddressField ] = "Address for static hosts does not support a port."
case strings . Contains ( err . Error (), globals . MissingPortErrStr ):
// Bare hostname, which we want
default :
badFields [ globals . AttributesAddressField ] = fmt . Sprintf ( "Error parsing address: %v." , err )
2022-06-19 10:55:37 -04:00
}
2020-09-12 22:07:56 -07:00
}
}
2020-09-08 16:04:19 -07:00
}
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-02 14:06:20 -07:00
badFields [ globals . IdField ] = "Cannot modify this type of host."
2020-09-08 16:04:19 -07:00
default :
2020-09-04 14:48:11 -07:00
badFields [ "id" ] = "Improperly formatted identifier used."
}
2020-08-21 08:15:32 -07:00
return badFields
2023-03-10 15:06:39 -05:00
}, globals . StaticHostPrefix )
2020-08-21 08:15:32 -07:00
}
2020-08-20 08:20:26 -07:00
2020-08-21 08:15:32 -07:00
func validateDeleteRequest ( req * pbs . DeleteHostRequest ) error {
2021-11-02 14:06:20 -07:00
return handlers . ValidateDeleteRequest ( func () map [ string ] string {
badFields := map [ string ] string {}
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( req . GetId ()). Subtype {
2023-11-02 16:03:47 -04:00
case hostplugin . Subtype :
2021-11-02 14:06:20 -07:00
badFields [ globals . IdField ] = "Cannot manually delete this type of host."
}
return badFields
2023-03-10 15:06:39 -05:00
}, req , globals . StaticHostPrefix )
2020-08-20 08:20:26 -07:00
}
2023-06-06 17:09:08 -04:00
func validateListRequest ( ctx context . Context , req * pbs . ListHostsRequest ) error {
2020-08-20 08:20:26 -07:00
badFields := map [ string ] string {}
2023-03-10 15:06:39 -05:00
if ! handlers . ValidId ( handlers . Id ( req . GetHostCatalogId ()), globals . StaticHostCatalogPrefix , globals . PluginHostCatalogPrefix , globals . PluginHostCatalogPreviousPrefix ) {
2020-08-20 08:20:26 -07:00
badFields [ "host_catalog_id" ] = "The field is incorrectly formatted."
}
2023-06-06 17:09:08 -04:00
if _ , err := handlers . NewFilter ( ctx , req . GetFilter ()); err != nil {
2021-02-22 10:09:25 -07:00
badFields [ "filter" ] = fmt . Sprintf ( "This field could not be parsed. %v" , err )
}
2020-08-20 08:20:26 -07:00
if len ( badFields ) > 0 {
return handlers . InvalidArgumentErrorf ( "Improperly formatted identifier." , badFields )
}
return nil
}