1 package services
2
3 import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "time"
10
11 iamv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
12 "github.com/google/uuid"
13 "github.com/rs/zerolog/log"
14 rbacv1 "k8s.io/api/rbac/v1"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 "sigs.k8s.io/controller-runtime/pkg/client"
17 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
18
19 sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
20 "edge-infra.dev/pkg/edge/api/clients"
21 "edge-infra.dev/pkg/edge/api/graph/mapper"
22 "edge-infra.dev/pkg/edge/api/graph/model"
23 sqlquery "edge-infra.dev/pkg/edge/api/sql"
24 "edge-infra.dev/pkg/edge/api/types"
25 "edge-infra.dev/pkg/edge/constants"
26 "edge-infra.dev/pkg/edge/flux/bootstrap"
27 "edge-infra.dev/pkg/edge/k8objectsutils/gcp/iamcomponent"
28 ptype "edge-infra.dev/pkg/f8n/warehouse/cluster"
29 "edge-infra.dev/pkg/f8n/warehouse/lift/unpack"
30 "edge-infra.dev/pkg/f8n/warehouse/oci/layer"
31 )
32
33
34 type BootstrapService interface {
35 GetSAKeySecret(name, namespace, secretKey, saKey string) (string, error)
36 GetManifests(ctx context.Context, clusterType string, pallets []types.Pallet, cluster *model.Cluster) ([]string, error)
37 CreateClusterBootstrapTokenEntry(ctx context.Context, clusterEdgeID string, secretName string, expireAt time.Time) error
38 GetClusterBootstrapTokens(ctx context.Context, clusterEdgeID string) ([]types.ClusterBootstrapToken, error)
39 DeleteExpiredClusterBootstrapTokens(ctx context.Context, clusterEdgeID string) error
40 }
41
42 type bootstrapService struct {
43 GkeService GkeClient
44 TopLevelProjectID string
45 ArtifactRegistryClient clients.ArtifactRegistryClient
46 SQLDB *sql.DB
47 }
48
49
50
51 func (s *bootstrapService) GetClusterFleetVersion(ctx context.Context, clusterEdgeID string) (string, error) {
52 var fleetVersion string
53 row := s.SQLDB.QueryRowContext(ctx, sqlquery.GetClusterFleetVersion, clusterEdgeID)
54 err := row.Scan(&fleetVersion)
55 if err != nil {
56 return "", sqlerr.Wrap(err)
57 }
58 return fleetVersion, nil
59 }
60
61 func CreateKustomizations(ctx context.Context, bucketName, clusterPath, storeVersion string) ([]string, error) {
62 kustomizationList := []string{}
63 kustomization := bootstrap.KustomizeFluxConfig().
64 Name("flux-config-kustomization").
65 Namespace(constants.FluxEdgeNamespace).
66 BucketName(bucketName).
67 ForStoreVersion(storeVersion).
68 Path(fmt.Sprintf("./%s/fluxcfg/", clusterPath)).
69 Force(true).
70 Build()
71 fluxKustomization, err := json.Marshal(kustomization)
72 if err != nil {
73 err = fmt.Errorf("error marshaling flux kustomization: %w", err)
74 log.Ctx(ctx).Err(err).Msg("cluster kustomization marshalling failed")
75 return nil, err
76 }
77 fluxKustomizationString := string(fluxKustomization)
78 kustomizationList = append(kustomizationList, fluxKustomizationString)
79
80 return kustomizationList, nil
81 }
82
83 func (s *bootstrapService) GetSAKeySecret(name, namespace, secretKey, saKey string) (string, error) {
84 values := []*model.KeyValues{{Key: secretKey, Value: saKey}}
85 secret, err := mapper.ToCreateSecretObject(name, namespace, values)
86 if err != nil {
87 return "", err
88 }
89 monSASecret, err := json.Marshal(secret)
90 if err != nil {
91 return "", err
92 }
93 monSASecretString := string(monSASecret)
94 return monSASecretString, nil
95 }
96
97 func (s *bootstrapService) CreateAgentRoleOnRegion(ctx context.Context, cluster *types.GkeCluster, agentIAM *iamcomponent.Component, clusterName string) error {
98 runtimeClient, err := s.GkeService.GetRuntimeClient(ctx, cluster)
99 if err != nil {
100 return err
101 }
102
103
104 agentRole := &rbacv1.Role{
105 ObjectMeta: metav1.ObjectMeta{
106 Name: "agent-role",
107 Namespace: clusterName,
108 },
109 Rules: []rbacv1.PolicyRule{
110 {
111 APIGroups: []string{"edge.ncr.com"},
112 Resources: []string{"viewrequests", "actionrequests", "stores"},
113 Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
114 },
115 {
116 APIGroups: []string{"edge.ncr.com"},
117 Resources: []string{"viewrequests/status", "actionrequests/status", "stores/status"},
118 Verbs: []string{"create", "get", "update", "list", "watch", "patch", "delete"},
119 },
120 },
121 }
122 agentRole.Kind = "Role"
123 agentRole.APIVersion = rbacv1.SchemeGroupVersion.Group + "/" + rbacv1.SchemeGroupVersion.Version
124
125 agentrole := agentRole.DeepCopy()
126 _, err = controllerutil.CreateOrUpdate(ctx, runtimeClient, agentrole, func() error {
127 agentrole.Rules = agentRole.Rules
128 return nil
129 })
130 if err != nil {
131 return err
132 }
133
134
135 key := client.ObjectKeyFromObject(&agentIAM.ServiceAccount)
136 agentSA := &iamv1beta1.IAMServiceAccount{}
137 err = runtimeClient.Get(ctx, key, agentSA)
138 if err != nil {
139 return errors.New("timeout getting data, unable to fetch service account or service account status")
140 }
141
142
143 agentRoleBinding := &rbacv1.RoleBinding{
144 ObjectMeta: metav1.ObjectMeta{
145 Name: "agent-rolebinding",
146 Namespace: clusterName,
147 },
148 RoleRef: rbacv1.RoleRef{
149 APIGroup: "rbac.authorization.k8s.io",
150 Kind: "Role",
151 Name: "agent-role",
152 },
153 Subjects: []rbacv1.Subject{{
154 Name: *agentSA.Status.Email,
155 Kind: "User",
156 }},
157 }
158
159 agentRoleBinding.Kind = "RoleBinding"
160 agentRoleBinding.APIVersion = rbacv1.SchemeGroupVersion.Group + "/" + rbacv1.SchemeGroupVersion.Version
161
162 agentrolebinding := agentRoleBinding.DeepCopy()
163 _, err = controllerutil.CreateOrUpdate(ctx, runtimeClient, agentrolebinding, func() error {
164 agentrolebinding.Subjects = agentRoleBinding.Subjects
165 return nil
166 })
167 if err != nil {
168 return err
169 }
170
171 return nil
172 }
173
174
175
176 func (s *bootstrapService) GetManifests(ctx context.Context, clusterType string, pallets []types.Pallet, cluster *model.Cluster) ([]string, error) {
177 opts := []unpack.Option{
178 unpack.ForProvider(ptype.Provider(clusterType)),
179 unpack.RenderWith(map[string]string{
180 "gcp_project_id": cluster.ProjectID,
181 "cluster_uuid": cluster.ClusterEdgeID,
182 "foreman_gcp_project_id": s.TopLevelProjectID,
183 }),
184 unpack.ForLayerTypes(layer.Runtime),
185 }
186
187 fleetVersion, err := s.GetClusterFleetVersion(ctx, cluster.ClusterEdgeID)
188 if err != nil {
189 return nil, err
190 }
191
192 manifests := []string{}
193 for _, p := range pallets {
194 deploy, err := p.IsDeployable(fleetVersion)
195 if err != nil {
196 return nil, err
197 }
198 if !deploy {
199 continue
200 }
201
202 a, err := s.ArtifactRegistryClient.Get(s.TopLevelProjectID, p.Name, fleetVersion)
203 if err != nil {
204 return nil, err
205 }
206
207 lays, err := unpack.Layers(a, opts...)
208 if err != nil {
209 return nil, err
210 }
211
212 uns, err := lays[0].Unstructured()
213 if err != nil {
214 return nil, err
215 }
216
217 for _, u := range uns {
218 if !p.IsManifestDeployable(u) {
219 continue
220 }
221
222 bits, err := json.Marshal(u)
223 if err != nil {
224 return nil, err
225 }
226
227 manifests = append(manifests, string(bits))
228 }
229 }
230
231 return manifests, nil
232 }
233
234 func (s *bootstrapService) CreateClusterBootstrapTokenEntry(ctx context.Context, clusterEdgeID string, secretName string, expireAt time.Time) error {
235 _, err := s.SQLDB.ExecContext(ctx, sqlquery.CreateClusterBootstrapToken, uuid.NewString(), secretName, clusterEdgeID, expireAt)
236 return err
237 }
238
239 func (s *bootstrapService) GetClusterBootstrapTokens(ctx context.Context, clusterEdgeID string) ([]types.ClusterBootstrapToken, error) {
240 rows, err := s.SQLDB.QueryContext(ctx, sqlquery.GetClusterBootstrapTokens, clusterEdgeID)
241 if err != nil {
242 return []types.ClusterBootstrapToken{}, sqlerr.Wrap(err)
243 }
244 defer rows.Close()
245 bootstrapTokenList := []types.ClusterBootstrapToken{}
246 for rows.Next() {
247 var bootstrapToken types.ClusterBootstrapToken
248 if err := rows.Scan(&bootstrapToken.SecretName, &bootstrapToken.ExpireAt); err != nil {
249 return []types.ClusterBootstrapToken{}, sqlerr.Wrap(err)
250 }
251 bootstrapTokenList = append(bootstrapTokenList, bootstrapToken)
252 }
253 if err := rows.Err(); err != nil {
254 return nil, sqlerr.Wrap(err)
255 }
256 return bootstrapTokenList, nil
257 }
258
259 func (s *bootstrapService) DeleteExpiredClusterBootstrapTokens(ctx context.Context, clusterEdgeID string) error {
260 _, err := s.SQLDB.ExecContext(ctx, sqlquery.DeleteExpiredClusterBootstrapTokens, clusterEdgeID)
261 return err
262 }
263
264 func NewBootstrapService(topLevelProjectID string, artifactRegistryClient clients.ArtifactRegistryClient, sqlDB *sql.DB) *bootstrapService {
265 return &bootstrapService{
266 TopLevelProjectID: topLevelProjectID,
267 ArtifactRegistryClient: artifactRegistryClient,
268 SQLDB: sqlDB,
269 }
270 }
271
View as plain text