feat: package structure (#2)
Update structure to match other Boundary plugins which groups packages by service
This commit is contained in:
1 parent
cd73a74e1e
commit
6588e10934
8 files changed
+309
-280
No files matched your search
+13
-269
@@ -4,282 +4,26 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||
errors "github.com/hashicorp/boundary-plugin-gcp/internal/errors"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||
"github.com/hashicorp/boundary-plugin-gcp/plugin/service/host"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// GooglePlugin implements the GooglePluginServiceServer interface for the
|
||||
// Google host service plugin.
|
||||
type GooglePlugin struct {
|
||||
pb.UnimplementedHostPluginServiceServer
|
||||
}
|
||||
|
||||
// Ensure that HostPlugin implements the following services:
|
||||
//
|
||||
// HostPluginServiceServer
|
||||
var (
|
||||
_ pb.HostPluginServiceServer = (*GooglePlugin)(nil)
|
||||
_ pb.HostPluginServiceServer = (*host.HostPlugin)(nil)
|
||||
)
|
||||
|
||||
// OnCreateCatalog is called when a dynamic host catalog is created.
|
||||
func (p *GooglePlugin) OnCreateCatalog(_ context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
|
||||
catalog := req.GetCatalog()
|
||||
if catalog == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "catalog is nil")
|
||||
}
|
||||
|
||||
attrs := catalog.GetAttributes()
|
||||
if attrs == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "attributes are required")
|
||||
}
|
||||
|
||||
if _, err := getCatalogAttributes(attrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.OnCreateCatalogResponse{
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: nil,
|
||||
},
|
||||
}, nil
|
||||
// GCPPlugin contains a collection of all GCP plugin services.
|
||||
type GCPPlugin struct {
|
||||
// HostPlugin implements the HostPluginServiceServer interface for
|
||||
// supporting dynamically sourcing hosts from GCP Compute Engine.
|
||||
*host.HostPlugin
|
||||
}
|
||||
|
||||
// OnUpdateCatalog is called when a dynamic host catalog is updated.
|
||||
func (p *GooglePlugin) OnUpdateCatalog(_ context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
|
||||
currentCatalog := req.GetCurrentCatalog()
|
||||
if currentCatalog == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "current catalog is nil")
|
||||
func NewGCPPlugin() *GCPPlugin {
|
||||
return &GCPPlugin{
|
||||
HostPlugin: &host.HostPlugin{},
|
||||
}
|
||||
|
||||
secrets := req.GetNewCatalog().GetSecrets()
|
||||
if secrets == nil {
|
||||
// If new secrets weren't passed in, don't rotate what we have on
|
||||
// update.
|
||||
return &pb.OnUpdateCatalogResponse{}, nil
|
||||
}
|
||||
|
||||
return &pb.OnUpdateCatalogResponse{
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: nil,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OnDeleteCatalog is called when a dynamic host catalog is deleted.
|
||||
func (p *GooglePlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalogRequest) (*pb.OnDeleteCatalogResponse, error) {
|
||||
catalog := req.GetCatalog()
|
||||
if catalog == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "new catalog is nil")
|
||||
}
|
||||
|
||||
attrs := catalog.GetAttributes()
|
||||
if attrs == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "new catalog missing attributes")
|
||||
}
|
||||
|
||||
if _, err := getCatalogAttributes(attrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.OnDeleteCatalogResponse{}, nil
|
||||
}
|
||||
|
||||
// OnCreateSet is called when a dynamic host set is created.
|
||||
func (p *GooglePlugin) OnCreateSet(_ context.Context, req *pb.OnCreateSetRequest) (*pb.OnCreateSetResponse, error) {
|
||||
if err := validateSet(req.GetSet()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.OnCreateSetResponse{}, nil
|
||||
}
|
||||
|
||||
// OnUpdateSet is called when a dynamic host set is updated.
|
||||
func (p *GooglePlugin) OnUpdateSet(_ context.Context, req *pb.OnUpdateSetRequest) (*pb.OnUpdateSetResponse, error) {
|
||||
if err := validateSet(req.GetNewSet()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.OnUpdateSetResponse{}, nil
|
||||
}
|
||||
|
||||
// OnDeleteSet is called when a dynamic host set is deleted.
|
||||
func (p *GooglePlugin) OnDeleteSet(ctx context.Context, req *pb.OnDeleteSetRequest) (*pb.OnDeleteSetResponse, error) {
|
||||
return &pb.OnDeleteSetResponse{}, nil
|
||||
}
|
||||
|
||||
// ListHosts returns the list of instances based on filter or instance group name.
|
||||
func (p *GooglePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*pb.ListHostsResponse, error) {
|
||||
catalog := req.GetCatalog()
|
||||
if catalog == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "catalog is nil")
|
||||
}
|
||||
|
||||
catalogAttrsRaw := catalog.GetAttributes()
|
||||
if catalogAttrsRaw == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "catalog missing attributes")
|
||||
}
|
||||
|
||||
catalogAttributes, err := getCatalogAttributes(catalogAttrsRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sets := req.GetSets()
|
||||
if sets == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "sets is nil")
|
||||
}
|
||||
|
||||
type hostSetQuery struct {
|
||||
Id string
|
||||
InputInstances *computepb.ListInstancesRequest
|
||||
InputGroups *computepb.ListInstancesInstanceGroupsRequest
|
||||
Project string
|
||||
Zone string
|
||||
Output []*computepb.Instance
|
||||
OutputHosts []*pb.ListHostsResponseHost
|
||||
}
|
||||
|
||||
queries := make([]hostSetQuery, len(sets))
|
||||
for i, set := range sets {
|
||||
// Validate Id since we use it in output
|
||||
if set.GetId() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "set missing id")
|
||||
}
|
||||
|
||||
if set.GetAttributes() == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "set missing attributes")
|
||||
}
|
||||
setAttrs, err := getSetAttributes(set.GetAttributes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if setAttrs.InstanceGroup != "" {
|
||||
queries[i] = hostSetQuery{
|
||||
Id: set.GetId(),
|
||||
InputGroups: buildListInstanceGroupsRequest(setAttrs, catalogAttributes),
|
||||
}
|
||||
} else {
|
||||
queries[i] = hostSetQuery{
|
||||
Id: set.GetId(),
|
||||
InputInstances: buildListInstancesRequest(setAttrs, catalogAttributes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instancesClient, err := compute.NewInstancesRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error creating NewInstancesRESTClient: %s", err)
|
||||
}
|
||||
|
||||
instanceGroupsClient, err := compute.NewInstanceGroupsRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error creating NewInstanceGroupsRESTClient: %s", err)
|
||||
}
|
||||
|
||||
gclient := GoogleClient{
|
||||
InstancesClient: instancesClient,
|
||||
InstanceGroupClient: instanceGroupsClient,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
// Run all queries now and assemble output.
|
||||
var maxLen int
|
||||
for i, query := range queries {
|
||||
var output []*computepb.Instance
|
||||
if query.InputGroups != nil {
|
||||
output, err = gclient.getInstancesForInstanceGroup(query.InputGroups)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error running getInstancesForInstanceGroup for host set id %q: %s", query.Id, err)
|
||||
}
|
||||
} else {
|
||||
output, err = gclient.getInstances(query.InputInstances)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error running getInstances for host set id %q: %s", query.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
queries[i].Output = output
|
||||
|
||||
// Process the output here, we will normalize this into a single
|
||||
// set of hosts afterwards (possibly removing duplicates).
|
||||
for _, instance := range output {
|
||||
host, err := instanceToHost(instance)
|
||||
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "error processing host results for host set id %q: %s", query.Id, err)
|
||||
}
|
||||
|
||||
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||
maxLen++
|
||||
}
|
||||
}
|
||||
|
||||
// Now de-duplicate the hosts for the output. Maintain two sets:
|
||||
// * A slice of hosts that will be used in the output
|
||||
// * A map of hosts indexed by their external ID
|
||||
//
|
||||
// The map will is used in de-duplication to determine whether or
|
||||
// not we've seen the host before to simply add the query's set ID
|
||||
// to the list of set IDs that the host was seen in.
|
||||
hostResultSlice := make([]*pb.ListHostsResponseHost, 0, maxLen)
|
||||
hostResultMap := make(map[string]*pb.ListHostsResponseHost)
|
||||
for _, query := range queries {
|
||||
for _, host := range query.OutputHosts {
|
||||
if existingHost, ok := hostResultMap[host.ExternalId]; ok {
|
||||
// Existing host, just add the set ID to the list of seen IDs
|
||||
// and continue
|
||||
existingHost.SetIds = append(existingHost.SetIds, query.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
// This will be the first seen entry, so append the set ID to
|
||||
// this host, and add it.
|
||||
host.SetIds = append(host.SetIds, query.Id)
|
||||
hostResultSlice = append(hostResultSlice, host)
|
||||
hostResultMap[host.ExternalId] = host
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.ListHostsResponse{
|
||||
Hosts: hostResultSlice,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateSet(s *hostsets.HostSet) error {
|
||||
if s == nil {
|
||||
return status.Error(codes.InvalidArgument, "set is nil")
|
||||
}
|
||||
var attrs SetAttributes
|
||||
attrMap := s.GetAttributes().AsMap()
|
||||
if err := mapstructure.Decode(attrMap, &attrs); err != nil {
|
||||
return status.Errorf(codes.InvalidArgument, "error decoding set attributes: %s", err)
|
||||
}
|
||||
|
||||
badFields := make(map[string]string)
|
||||
_, filterSet := attrMap[ConstListInstancesFilter]
|
||||
_, instanceGroupSet := attrMap[ConstInstanceGroup]
|
||||
|
||||
if instanceGroupSet && filterSet {
|
||||
badFields["attributes"] = "must set instance group or filter, cannot set both"
|
||||
} else if instanceGroupSet && len(attrs.InstanceGroup) == 0 {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstInstanceGroup)] = "must not be empty."
|
||||
} else if filterSet && len(attrs.Filter) == 0 {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstListInstancesFilter)] = "must not be empty."
|
||||
}
|
||||
|
||||
for f := range attrMap {
|
||||
if _, ok := allowedSetFields[f]; !ok {
|
||||
badFields[fmt.Sprintf("attributes.%s", f)] = "Unrecognized field."
|
||||
}
|
||||
}
|
||||
|
||||
if len(badFields) > 0 {
|
||||
return errors.InvalidArgumentError("Invalid arguments in the new set", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user