...

Source file src/edge-infra.dev/pkg/edge/api/services/bootstrap_service.go

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

     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  //go:generate mockgen -destination=../mocks/mock_bootstrap_service.go -package=mocks edge-infra.dev/pkg/edge/api/services BootstrapService
    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  // GetClusterFleetVersion gets the tag or digest for the cluster's fleet package. Returns the {fleet_version} portion
    50  // of a package of the form {fleet_type}(@|:){fleet_version}, e.g. store[@sha256:digest | :tag]
    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  	// create rbac to map gcp service account to join request role
   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  	// get agent service account with updated status
   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  	//kind: User
   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  // GetManifests gets the bootstrapping manifests for installing apps to get
   175  // cluster syncing with edge
   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 { //nolint stupid
   265  	return &bootstrapService{
   266  		TopLevelProjectID:      topLevelProjectID,
   267  		ArtifactRegistryClient: artifactRegistryClient,
   268  		SQLDB:                  sqlDB,
   269  	}
   270  }
   271  

View as plain text