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
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
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
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
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
169
170 return s.SecretService.DeleteExternalSecret(ctx, secretName, pxe.PXENamespace, cluster.ProjectID, cluster, clusterInfra, secretName)
171 }
172
173
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