...

Source file src/edge-infra.dev/pkg/edge/api/services/edgenode/activationcode/service.go

Documentation: edge-infra.dev/pkg/edge/api/services/edgenode/activationcode

     1  package activationcode
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  
    10  	"k8s.io/utils/ptr"
    11  
    12  	gcperror "edge-infra.dev/pkg/edge/api/apierror/gcp"
    13  
    14  	"edge-infra.dev/pkg/edge/api/graph/model"
    15  	"edge-infra.dev/pkg/edge/api/services"
    16  	"edge-infra.dev/pkg/edge/api/services/edgenode/common"
    17  	"edge-infra.dev/pkg/lib/crypto"
    18  	pxe "edge-infra.dev/pkg/sds/ien/k8s/controllers/pxe/common"
    19  )
    20  
    21  type service struct {
    22  	SQLDB                *sql.DB
    23  	TerminalService      services.TerminalService
    24  	StoreClusterService  services.StoreClusterService
    25  	ClusterConfigService services.ClusterConfigService
    26  	SecretService        services.SecretService
    27  	GCPService           services.GCPService
    28  	BannerService        services.BannerService
    29  }
    30  
    31  var (
    32  	ErrInvalidActivationSecretCount   = errors.New("invalid number of activation code secrets")
    33  	ErrInvalidActivationSecret        = errors.New("invalid activation code secret")
    34  	ErrDeletingClusterActivationCodes = errors.New("error deleting cluster's activation codes")
    35  )
    36  
    37  func (s *service) Fetch(ctx context.Context, terminalID string) (*string, error) {
    38  	return common.FetchActivationCode(ctx, s.SQLDB, terminalID)
    39  }
    40  
    41  func (s *service) FetchFromSecret(ctx context.Context, terminalID string) (string, error) {
    42  	terminal, err := s.TerminalService.GetTerminal(ctx, terminalID, ptr.To(false))
    43  	if err != nil {
    44  		return "", err
    45  	}
    46  	cluster, err := s.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  	secretName := fmt.Sprintf("%s-%s", SecretType, terminalID)
    51  	secrets, err := s.GCPService.GetSecrets(ctx, &secretName, ptr.To(Workload), ptr.To(SecretType), true, cluster.ProjectID)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	if len(secrets) != 1 {
    56  		return "", ErrInvalidActivationSecretCount
    57  	}
    58  	if len(secrets[0].Values) != 1 {
    59  		return "", ErrInvalidActivationSecret
    60  	}
    61  	return secrets[0].Values[0].Value, nil
    62  }
    63  
    64  func (s *service) Refresh(ctx context.Context, terminalID string) (string, error) {
    65  	activationCode, err := crypto.GenerateRandomActivationCode()
    66  	if err != nil {
    67  		return "", err
    68  	}
    69  	terminal, err := s.TerminalService.GetTerminal(ctx, terminalID, ptr.To(false))
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	if err := s.Create(ctx, activationCode, terminal); err != nil {
    74  		return "", err
    75  	}
    76  	return activationCode.Plain(), nil
    77  }
    78  
    79  func (s *service) Create(ctx context.Context, activationCode crypto.Credential, terminal *model.Terminal) error {
    80  	_, err := s.SQLDB.ExecContext(ctx, SetActivationCode, hex.EncodeToString(activationCode.Hashed()), terminal.TerminalID)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	cluster, err := s.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	return s.SyncToStore(ctx, terminal.TerminalID, cluster, activationCode.Plain())
    89  }
    90  
    91  func (s *service) SyncToStore(ctx context.Context, terminalID string, cluster *model.Cluster, activationCode string) error {
    92  	// create the secret manager secret to store the activation code
    93  	values := []*model.KeyValues{{Key: SecretKey, Value: activationCode}}
    94  	secretName := fmt.Sprintf("%s-%s", SecretType, terminalID)
    95  	return s.GCPService.AddSecret(ctx, secretName, Workload, SecretType, values, cluster.ProjectID, ptr.To(Workload), nil)
    96  }
    97  
    98  func (s *service) SyncAllToStore(ctx context.Context, clusterEdgeID string) error {
    99  	terminalList, err := s.TerminalService.GetTerminalsByClusterID(ctx, clusterEdgeID)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	cluster, err := s.StoreClusterService.GetCluster(ctx, clusterEdgeID)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	var allErrs error
   109  	for _, terminal := range terminalList {
   110  		activationCode, err := s.FetchFromSecret(ctx, terminal.TerminalID)
   111  		if err != nil {
   112  			// Activation code does not exist or is used
   113  			continue
   114  		}
   115  		if err := s.SyncToStore(ctx, terminal.TerminalID, cluster, activationCode); err != nil {
   116  			allErrs = errors.Join(allErrs, err)
   117  		}
   118  	}
   119  	return allErrs
   120  }
   121  
   122  func (s *service) MarkUsed(ctx context.Context, terminalID string, cluster *model.Cluster, clusterInfra *model.Cluster) error {
   123  	// set to empty string to indicate it has been consumed
   124  	_, err := s.SQLDB.ExecContext(ctx, SetActivationCode, "", terminalID)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	return s.delete(ctx, terminalID, cluster, clusterInfra)
   129  }
   130  
   131  func (s *service) CleanupStore(ctx context.Context, cluster *model.Cluster) error {
   132  	terminalList, err := s.TerminalService.GetTerminalsByClusterID(ctx, cluster.ClusterEdgeID)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	clusterInfra, err := s.BannerService.GetClusterInfraInfo(ctx, cluster.BannerEdgeID)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	var allErrs error
   141  	for _, terminal := range terminalList {
   142  		if err := s.delete(ctx, terminal.TerminalID, cluster, clusterInfra); err != nil {
   143  			allErrs = errors.Join(allErrs, fmt.Errorf("%w: %v", ErrDeletingClusterActivationCodes, err))
   144  		}
   145  	}
   146  	return allErrs
   147  }
   148  
   149  func (s *service) CleanupTerminal(ctx context.Context, terminalID string, clusterEdgeID string) error {
   150  	cluster, err := s.StoreClusterService.GetCluster(ctx, clusterEdgeID)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	clusterInfra, err := s.BannerService.GetClusterInfraInfo(ctx, cluster.BannerEdgeID)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	return s.delete(ctx, terminalID, cluster, clusterInfra)
   159  }
   160  
   161  // delete removes the relevant resources for the terminal's activation code
   162  func (s *service) delete(ctx context.Context, terminalID string, cluster *model.Cluster, clusterInfra *model.Cluster) error {
   163  	secretName := fmt.Sprintf("%s-%s", SecretType, terminalID)
   164  	if _, err := s.GCPService.DeleteSecret(ctx, secretName, cluster.ProjectID); gcperror.IgnoreNotFound(err) != nil {
   165  		return err
   166  	}
   167  
   168  	// TODO: Remove the following line in 0.25
   169  	// - Related issue: https://github.com/ncrvoyix-swt-retail/edge-roadmap/issues/13462
   170  	return s.SecretService.DeleteExternalSecret(ctx, secretName, pxe.PXENamespace, cluster.ProjectID, cluster, clusterInfra, secretName)
   171  }
   172  
   173  // New returns an instance of the EdgeNode ActivationCode service
   174  func New(sqlDB *sql.DB, terminalSvc services.TerminalService, storeSvc services.StoreClusterService, clusterConfigSvc services.ClusterConfigService, secretSvc services.SecretService, gcpSvc services.GCPService, bannerSvc services.BannerService) common.ActivationCode {
   175  	return &service{
   176  		SQLDB:                sqlDB,
   177  		TerminalService:      terminalSvc,
   178  		StoreClusterService:  storeSvc,
   179  		ClusterConfigService: clusterConfigSvc,
   180  		SecretService:        secretSvc,
   181  		GCPService:           gcpSvc,
   182  		BannerService:        bannerSvc,
   183  	}
   184  }
   185  

View as plain text