Files
threshold-plugin-gcp/plugin/service/host/state.go
T

130 lines
3.8 KiB
Go

// Copyright IBM Corp. 2024, 2025
// SPDX-License-Identifier: MPL-2.0
package host
import (
"context"
"errors"
"fmt"
compute "cloud.google.com/go/compute/apiv1"
computepb "cloud.google.com/go/compute/apiv1/computepb"
"github.com/googleapis/gax-go/v2"
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/protobuf/types/known/structpb"
)
type InstancesAPI interface {
List(ctx context.Context, req *computepb.ListInstancesRequest, opts ...gax.CallOption) *compute.InstanceIterator
}
type gcpCatalogPersistedState struct {
// CredentialsConfig is the configuration for the GCP credentials
*credential.PersistedState
// testInstancesAPIFunc provides a way to provide a factory for a mock Instances client
testInstancesAPIFunc instancesAPIFunc
}
type instancesAPIFunc func(...*credential.Config) (InstancesAPI, error)
type gcpCatalogPersistedStateOption func(s *gcpCatalogPersistedState) error
func withCredentials(x *credential.PersistedState) gcpCatalogPersistedStateOption {
return func(s *gcpCatalogPersistedState) error {
if s.PersistedState != nil {
return errors.New("gcp credentials already set")
}
s.PersistedState = x
return nil
}
}
func withTestInstancesAPIFunc(f instancesAPIFunc) gcpCatalogPersistedStateOption {
return func(s *gcpCatalogPersistedState) error {
if s.testInstancesAPIFunc != nil {
return errors.New("test API function already set")
}
s.testInstancesAPIFunc = f
return nil
}
}
func newGCPCatalogPersistedState(opts ...gcpCatalogPersistedStateOption) (*gcpCatalogPersistedState, error) {
s := new(gcpCatalogPersistedState)
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
return s, nil
}
// ToProto converts the state to HostCatalogPersisted proto message.
func (s *gcpCatalogPersistedState) toProto() (*pb.HostCatalogPersisted, error) {
data, err := structpb.NewStruct(s.ToMap())
if err != nil {
return nil, fmt.Errorf("error converting state to structpb.Struct: %w", err)
}
return &pb.HostCatalogPersisted{Secrets: data}, nil
}
// InstancesClient returns a configured Instances client based on the session
// information stored in the state.
func (s *gcpCatalogPersistedState) InstancesClient(
ctx context.Context,
opts ...option.ClientOption,
) (InstancesAPI, error) {
if s.testInstancesAPIFunc != nil {
return s.testInstancesAPIFunc(s.CredentialsConfig)
}
creds, err := s.CredentialsConfig.GenerateCredentials(ctx)
if err != nil {
return nil, fmt.Errorf("error generation GCP credentials: %w", err)
}
if creds == nil {
return nil, fmt.Errorf("nil gcp credentials")
}
clientOptions := []option.ClientOption{option.WithTokenSource(creds.TokenSource)}
clientOptions = append(clientOptions, opts...)
client, err := compute.NewInstancesRESTClient(ctx, clientOptions...)
if err != nil {
return nil, fmt.Errorf("error creating instances client: %w", err)
}
return client, nil
}
// validateCredsCallbackFn returns a ValidateCredsCallback function that
// validates the credentials by listing instances in the project and zone.
func (s *gcpCatalogPersistedState) ValidateCredsCallbackFn(
ctx context.Context,
opts ...option.ClientOption,
) credential.ValidateCredsCallback {
return func(config *credential.Config, opts ...option.ClientOption) error {
s.CredentialsConfig = config
instancesClient, err := s.InstancesClient(ctx, opts...)
if err != nil {
return fmt.Errorf("error creating instances client: %w", err)
}
it := instancesClient.List(ctx, &computepb.ListInstancesRequest{
Project: s.CredentialsConfig.ProjectId,
Zone: s.CredentialsConfig.Zone,
})
_, err = it.Next()
if err != nil && err != iterator.Done {
return fmt.Errorf("error listing instances: %w", err)
}
return nil
}
}