...

Source file src/edge-infra.dev/pkg/edge/bsl-reconciler/bsl_reconciler.go

Documentation: edge-infra.dev/pkg/edge/bsl-reconciler

     1  package edgebsl
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/gin-contrib/requestid"
    17  	"github.com/gin-gonic/gin"
    18  
    19  	server "edge-infra.dev/pkg/x/tonic"
    20  
    21  	"edge-infra.dev/pkg/edge/api/middleware"
    22  	"edge-infra.dev/pkg/lib/gcp/cloudsql"
    23  
    24  	"github.com/go-logr/logr"
    25  
    26  	"edge-infra.dev/pkg/edge/api/graph/model"
    27  	"edge-infra.dev/pkg/edge/api/services"
    28  
    29  	btypes "edge-infra.dev/pkg/edge/api/types"
    30  	"edge-infra.dev/pkg/edge/bsl"
    31  	bslMetrics "edge-infra.dev/pkg/edge/bsl-reconciler/metrics"
    32  	"edge-infra.dev/pkg/edge/constants"
    33  	secretMgrApi "edge-infra.dev/pkg/lib/gcp/secretmanager"
    34  	gcputils "edge-infra.dev/pkg/lib/gcp/utils"
    35  	logger "edge-infra.dev/pkg/lib/logging"
    36  	"edge-infra.dev/pkg/lib/runtime/manager"
    37  	"edge-infra.dev/pkg/lib/runtime/metrics"
    38  )
    39  
    40  type BSL struct {
    41  	logger                logr.Logger
    42  	metrics               *bslMetrics.Metrics
    43  	secretManagerProvider btypes.SecretManagerFunc
    44  	bslConfig             BslConfig
    45  	db                    *sql.DB
    46  	TenantService         services.TenantService
    47  }
    48  
    49  const (
    50  	DefaultReconcileInterval   = "60"
    51  	alreadyExistsLogFormat     = "%s for org %s"
    52  	SubscriptionOrgNameAttrKey = "OnboardedOrganization"
    53  
    54  	// Username and Password TODO will remove
    55  	Username = "username"
    56  	Password = "password"
    57  )
    58  
    59  func RunManager(ctx context.Context) error {
    60  	log := logger.NewLogger().Logger
    61  
    62  	cfg, _, err := NewConfig(os.Args)
    63  	if err != nil {
    64  		log.Error(err, "failed to parse startup configuration")
    65  		os.Exit(1)
    66  	}
    67  
    68  	secretManagerProvider := func(ctx context.Context, projectID string) (btypes.SecretManagerService, error) {
    69  		return secretMgrApi.NewWithOptions(ctx, projectID)
    70  	}
    71  
    72  	startManager(ctx, cfg, secretManagerProvider, log, metrics.DefaultBindAddress)
    73  
    74  	return nil
    75  }
    76  
    77  func startManager(ctx context.Context, cfg BslConfig, secretManagerProvider btypes.SecretManagerFunc, log logr.Logger, port string) {
    78  	mgr, err := manager.New(manager.Options{MetricsBindAddress: port})
    79  	if err != nil {
    80  		log.Error(err, "failed to create a new manager")
    81  		os.Exit(1)
    82  	}
    83  
    84  	dbConnection, err := DB(cfg, log)
    85  	if err != nil {
    86  		log.Error(err, "An error occurred creating sql db connection")
    87  		os.Exit(1)
    88  	}
    89  
    90  	bslOperations := New(secretManagerProvider, log, cfg, dbConnection)
    91  	if err != nil {
    92  		log.Error(err, "failed to create a bsl ops")
    93  		os.Exit(1)
    94  	}
    95  
    96  	if err := mgr.Add(bslOperations); err != nil {
    97  		log.Error(err, "failed to add bsl ops")
    98  		os.Exit(1)
    99  	}
   100  
   101  	bslServer := server.NewWithOptions(cfg.Port, cfg.Mode).
   102  		SetLogger(log).
   103  		SetMiddlewares(middleware.SetRequestContext(), requestid.New(), middleware.RequestLogger(log), gin.Recovery(), server.CorsMiddleware()).
   104  		WithHealthRoute().
   105  		WithReadyRoute().
   106  		SetRoutes(server.Route{
   107  			Path:   "/new-org-created/",
   108  			Action: http.MethodPost,
   109  			Handlers: []gin.HandlerFunc{
   110  				gin.BasicAuth(gin.Accounts{
   111  					cfg.BasicAuthCreds.Username: cfg.BasicAuthCreds.Password,
   112  				}),
   113  				func(c *gin.Context) {
   114  					payload, err := io.ReadAll(c.Request.Body)
   115  					if err != nil {
   116  						log.Error(err, "Error reading BSL new org payload data")
   117  					} else {
   118  						payloadJSONStr := string(payload)
   119  						log.Info(fmt.Sprintf("BSL Organization Created: %s", payloadJSONStr))
   120  
   121  						bslSubscriptionPostPayload := &BSLSubscriptionPayload{}
   122  
   123  						if err := json.Unmarshal(payload, bslSubscriptionPostPayload); err != nil {
   124  							log.Error(err, "Error de-coding BSL new org JSON payload data")
   125  						}
   126  
   127  						var OrgName string
   128  
   129  						for _, val := range bslSubscriptionPostPayload.Attributes {
   130  							if val.Key == SubscriptionOrgNameAttrKey {
   131  								OrgName = val.Value
   132  							}
   133  						}
   134  
   135  						orgToProcess := &AllEdgeOrgsPageContent{
   136  							OrganizationName: OrgName,
   137  							ID:               "",
   138  							DisplayName:      "",
   139  							Parent:           true,
   140  						}
   141  
   142  						tenantsNames := bslOperations.GetTenantsList(ctx)
   143  						sctmrg, _ := secretMgrApi.NewWithOptions(ctx, cfg.TopLevelProjectID)
   144  
   145  						log.Info("Starting org processing for: ", orgToProcess.OrganizationName)
   146  						err := bslOperations.processEdgeOrganization(c, orgToProcess, sctmrg, tenantsNames)
   147  
   148  						if err != nil {
   149  							log.Error(err, "Error processing organization")
   150  						}
   151  
   152  						c.IndentedJSON(http.StatusOK, orgToProcess)
   153  					}
   154  				}},
   155  		}).With404Route()
   156  
   157  	if err := mgr.Add(bslServer); err != nil {
   158  		log.Error(err, "failed to add bsl server")
   159  		os.Exit(1)
   160  	}
   161  
   162  	if err := mgr.Start(ctx); err != nil {
   163  		log.Error(err, "bsl errored")
   164  		os.Exit(1)
   165  	}
   166  }
   167  
   168  func New(secretManagerProvider btypes.SecretManagerFunc, log logr.Logger, cfg BslConfig, dbConnection *sql.DB) *BSL {
   169  	m := bslMetrics.NewMetrics()
   170  	m.InitMetrics()
   171  	b := &BSL{
   172  		logger:                log,
   173  		metrics:               m,
   174  		secretManagerProvider: secretManagerProvider,
   175  		bslConfig:             cfg,
   176  		db:                    dbConnection,
   177  		TenantService:         services.NewTenantService(dbConnection, nil, nil),
   178  	}
   179  	return b
   180  }
   181  
   182  func (b *BSL) Start(ctx context.Context) error {
   183  	reconcileInterval, err := strconv.Atoi(b.bslConfig.BslReconcilesInterval)
   184  	if err != nil {
   185  		return fmt.Errorf("unable to convert BSL_RECONCILE_INTERVAL, %v", err)
   186  	}
   187  
   188  	if err = b.Reconcile(ctx); err != nil {
   189  		return fmt.Errorf("unable to reconcile, %v", err)
   190  	}
   191  
   192  	for {
   193  		select {
   194  		case <-time.After(time.Duration(reconcileInterval) * time.Minute):
   195  			err = b.Reconcile(ctx)
   196  			if err != nil {
   197  				return fmt.Errorf("unable to reconcile, %v", err)
   198  			}
   199  		case <-ctx.Done():
   200  			return nil
   201  		}
   202  	}
   203  }
   204  
   205  // Reconcile is part of the main kubernetes reconciliation loop which aims to
   206  // move the current state of the cluster closer to the desired state.
   207  func (b *BSL) Reconcile(ctx context.Context) error {
   208  	log := b.logger.WithValues("bsl operator", "reconcile")
   209  	b.metrics.ReconcileInc()
   210  
   211  	tenantsNames := b.GetTenantsList(ctx)
   212  
   213  	allOrgsList, err := b.bslConfig.GetAllEdgeOrgs(ctx)
   214  	if err != nil {
   215  		log.Error(err, "failed to get bsl edge orgs")
   216  		b.metrics.ErrorInc("bsl_error", "", err.Error())
   217  		return err
   218  	}
   219  
   220  	sm, err := b.secretManagerProvider(ctx, b.bslConfig.TopLevelProjectID)
   221  	if err != nil {
   222  		log.Error(err, "fail to create new secret manager api", "project", b.bslConfig.TopLevelProjectID)
   223  		b.metrics.ErrorInc("kube_error", "", err.Error())
   224  		return err
   225  	}
   226  
   227  	defer gcputils.CloseConnection(ctx, sm)
   228  
   229  	return b.processAllEdgeOrganizations(ctx, allOrgsList, sm, tenantsNames)
   230  }
   231  
   232  func (b *BSL) GetTenantsList(ctx context.Context) map[string]struct{} {
   233  	log := b.logger.WithValues("bsl operator", "Get Tenants List")
   234  
   235  	tenants, err := b.TenantService.List(ctx)
   236  
   237  	if err != nil {
   238  		log.Error(err, "An error occurred getting tenants info")
   239  		b.metrics.ErrorInc("sql_error", "", err.Error())
   240  	}
   241  
   242  	tenantsNames := make(map[string]struct{}, 0)
   243  	for _, tenant := range tenants {
   244  		tenantsNames[tenant.OrgName] = struct{}{}
   245  	}
   246  
   247  	return tenantsNames
   248  }
   249  
   250  func (b *BSL) CreateUserAndSecret(ctx context.Context, sm btypes.SecretManagerService, orgName string, bslConfig *BslConfig, log logr.Logger, secretName string, bffuser bool) error {
   251  	//check to see if the secret exists in the edge-system namespace
   252  	_, err := sm.GetSecret(ctx, secretName)
   253  	if err != nil && strings.Contains(err.Error(), "not found") { //nolint
   254  		b.metrics.NewOrgInc(secretName)
   255  		err = nil
   256  		username := newBSLUsername(6, bffuser)
   257  		password, bslerr := bslConfig.CreateBSLUser(ctx, orgName, username)
   258  		if bslerr != nil {
   259  			log.Error(bslerr, LogWithSecret(secretName, "failed to create bsl user"))
   260  			return fmt.Errorf("fail to create new bff user and password, error; %v", bslerr)
   261  		}
   262  		//create secret in edge-system, if the secret and user does not already exist
   263  		// create shared and secret key for user
   264  		res, err := bslConfig.CreateBSLUserAccessKey(ctx, orgName, username)
   265  		if err != nil {
   266  			log.Error(err, LogWithSecret(secretName, "failed to create access key for user"))
   267  			return fmt.Errorf("failed to create access key for user, error; %v", bslerr)
   268  		}
   269  		if err := createUpdateSecretManagerSecret(ctx, sm, secretName, username, password, res.SharedKey, res.SecretKey); err != nil {
   270  			log.Error(err, LogWithSecret(secretName, "failed to create bsl org secret in secret manager"))
   271  			return err
   272  		}
   273  
   274  		if bffuser {
   275  			//assign bff user to EDGE_SUPER_ADMIN
   276  			if err := bslConfig.AssignBSLUserToGroup(ctx, orgName, string(model.RoleEdgeSuperAdmin), username); err != nil {
   277  				if !errors.Is(err, ErrorResourceAlreadyExists) {
   278  					log.Error(err, LogWithSecret(secretName, "failed to assign bsl user to EDGE_SUPER_ADMIN"))
   279  				} else {
   280  					log.Info(LogWithSecret(secretName, "failed to assign bsl user to EDGE_SUPER_ADMIN, the user is already assigned to EDGE_SUPER_ADMIN"))
   281  				}
   282  			}
   283  		} else {
   284  			//assign bff user to EDGE_ORG_ADMIN
   285  			if err := bslConfig.AssignBSLUserToGroup(ctx, orgName, string(model.RoleEdgeOrgAdmin), username); err != nil {
   286  				if !errors.Is(err, ErrorResourceAlreadyExists) {
   287  					log.Error(err, LogWithSecret(secretName, "failed to assign bsl user to EDGE_ORG_ADMIN"))
   288  				} else {
   289  					log.Info(LogWithSecret(secretName, "failed to assign bsl user to EDGE_ORG_ADMIN, the user is already assigned to EDGE_ORG_ADMIN"))
   290  				}
   291  			}
   292  		}
   293  	}
   294  	return err
   295  }
   296  
   297  func createUpdateSecretManagerSecret(ctx context.Context, sm btypes.SecretManagerService, name, username, password, sharedKey, secretKey string) error {
   298  	data, err := json.Marshal(map[string]string{
   299  		Username:      username,
   300  		Password:      password,
   301  		bsl.SharedKey: sharedKey,
   302  		bsl.SecretKey: secretKey,
   303  	})
   304  	if err != nil {
   305  		return err
   306  	}
   307  	labels := map[string]string{
   308  		secretMgrApi.SecretLabel:                  "platform",
   309  		secretMgrApi.SecretTypeLabel:              "bsl-secret",
   310  		secretMgrApi.SecretOwnerLabel:             "edge",
   311  		secretMgrApi.SecretNamespaceSelectorLabel: string(constants.PlatformNamespaceSelector),
   312  	}
   313  	return sm.AddSecret(ctx, name, data, labels, true, nil, "")
   314  }
   315  
   316  func DB(config BslConfig, log logr.Logger) (*sql.DB, error) {
   317  	dbConnection, err := cloudsql.GCPPostgresConnection(config.SQL.ConnectionName).
   318  		DBName(config.SQL.DatabaseName).
   319  		Password(config.SQL.Password).
   320  		Username(config.SQL.User).
   321  		NewConnection()
   322  	if err != nil {
   323  		log.Error(err, "failed to create sql db connection")
   324  	}
   325  	return dbConnection, err
   326  }
   327  

View as plain text