github.com/nais/knorten@v0.0.0-20240104110906-55926958e361/pkg/team/team.go (about)

     1  package team
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  
     8  	"github.com/nais/knorten/pkg/chart"
     9  	"github.com/nais/knorten/pkg/database"
    10  	"github.com/nais/knorten/pkg/database/gensql"
    11  	"github.com/nais/knorten/pkg/k8s"
    12  	"github.com/nais/knorten/pkg/logger"
    13  	"k8s.io/client-go/dynamic"
    14  	"k8s.io/client-go/kubernetes"
    15  )
    16  
    17  type Client struct {
    18  	repo             *database.Repo
    19  	k8sClient        *kubernetes.Clientset
    20  	k8sDynamicClient *dynamic.DynamicClient
    21  	gcpProject       string
    22  	gcpRegion        string
    23  	dryRun           bool
    24  }
    25  
    26  func NewClient(repo *database.Repo, gcpProject, gcpRegion string, dryRun, inCluster bool) (*Client, error) {
    27  	k8sClient, err := k8s.CreateClientset(dryRun, inCluster)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	k8sDynamicClient, err := k8s.CreateDynamicClient(dryRun, inCluster)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	return &Client{
    38  		repo:             repo,
    39  		k8sClient:        k8sClient,
    40  		k8sDynamicClient: k8sDynamicClient,
    41  		gcpProject:       gcpProject,
    42  		gcpRegion:        gcpRegion,
    43  		dryRun:           dryRun,
    44  	}, nil
    45  }
    46  
    47  func (c Client) Create(ctx context.Context, team gensql.Team, log logger.Logger) bool {
    48  	log.Infof("Creating team %v", team.ID)
    49  
    50  	if retry, err := c.create(ctx, team, log); err != nil {
    51  		log.Info("failed creating team")
    52  		return retry
    53  	}
    54  
    55  	log.Infof("Successfully created team %v", team.ID)
    56  	return false
    57  }
    58  
    59  func (c Client) create(ctx context.Context, team gensql.Team, log logger.Logger) (bool, error) {
    60  	existingTeam, err := c.repo.TeamBySlugGet(ctx, team.Slug)
    61  	if err != nil && !errors.Is(err, sql.ErrNoRows) {
    62  		log.WithError(err).Error("failed retrieving team from database")
    63  		return true, err
    64  	}
    65  
    66  	if existingTeam.Slug == team.Slug {
    67  		log.Errorf("there already exists a team with name %v", team.Slug)
    68  		return false, err
    69  	}
    70  
    71  	if err := c.createGCPTeamResources(ctx, team); err != nil {
    72  		log.WithError(err).Error("failed creating GCP resources")
    73  		return true, err
    74  	}
    75  
    76  	namespace := k8s.TeamIDToNamespace(team.ID)
    77  	if err := c.createK8sNamespace(ctx, namespace); err != nil {
    78  		log.WithError(err).Error("failed creating team namespace")
    79  		return true, err
    80  	}
    81  
    82  	if err := c.defaultEgressNetpolsSync(ctx, namespace, team.EnableAllowlist); err != nil {
    83  		log.WithError(err).Error("syncing default egress netpol")
    84  		return true, err
    85  	}
    86  
    87  	if err := c.createK8sServiceAccount(ctx, team.ID, namespace); err != nil {
    88  		log.WithError(err).Error("failed creating k8s service account")
    89  		return true, err
    90  	}
    91  
    92  	if err := c.repo.TeamCreate(ctx, team); err != nil {
    93  		log.WithError(err).Error("failed saving team to database")
    94  		return true, err
    95  	}
    96  
    97  	return false, nil
    98  }
    99  
   100  func (c Client) Update(ctx context.Context, team gensql.Team, log logger.Logger) bool {
   101  	log.Infof("Updating team %v", team.ID)
   102  
   103  	if retry, err := c.update(ctx, team, log); err != nil {
   104  		log.Info("failed updating team")
   105  		return retry
   106  	}
   107  
   108  	return false
   109  }
   110  
   111  func (c Client) update(ctx context.Context, team gensql.Team, log logger.Logger) (bool, error) {
   112  	err := c.repo.TeamUpdate(ctx, team)
   113  	if err != nil {
   114  		log.WithError(err).Error("failed updating team in database")
   115  		return true, err
   116  	}
   117  
   118  	namespace := k8s.TeamIDToNamespace(team.ID)
   119  	namespaceExists, err := c.k8sNamespaceExists(ctx, namespace)
   120  	if err != nil {
   121  		log.WithError(err).Error("failed while checking if namespace exists")
   122  		return true, err
   123  	}
   124  
   125  	if !namespaceExists {
   126  		if err := c.createK8sNamespace(ctx, namespace); err != nil {
   127  			log.WithError(err).Error("failed creating team namespace")
   128  			return true, err
   129  		}
   130  	}
   131  
   132  	if err := c.defaultEgressNetpolsSync(ctx, namespace, team.EnableAllowlist); err != nil {
   133  		log.WithError(err).Error("syncing default egress netpol")
   134  		return true, err
   135  	}
   136  
   137  	serviceAccountExists, err := c.k8sServiceAccountExists(ctx, team.ID, namespace)
   138  	if err != nil {
   139  		log.WithError(err).Error("failed while checking if service accpunt exists")
   140  		return true, err
   141  	}
   142  
   143  	if !serviceAccountExists {
   144  		if err := c.createK8sServiceAccount(ctx, team.ID, namespace); err != nil {
   145  			log.WithError(err).Error("failed creating k8s service account")
   146  			return true, err
   147  		}
   148  	}
   149  
   150  	if err := c.updateGCPTeamResources(ctx, team); err != nil {
   151  		log.WithError(err).Error("failed while updating GCP resources")
   152  		return true, err
   153  	}
   154  
   155  	apps, err := c.repo.ChartsForTeamGet(ctx, team.ID)
   156  	if err != nil {
   157  		log.WithError(err).Errorf("failed getting apps for team %v", team.ID)
   158  		return true, err
   159  	}
   160  
   161  	for _, app := range apps {
   162  		switch app {
   163  		case gensql.ChartTypeJupyterhub:
   164  			log.Info("Trigger update of Jupyter")
   165  			jupyterValues := chart.JupyterConfigurableValues{
   166  				TeamID: team.ID,
   167  			}
   168  			if err := c.repo.RegisterUpdateJupyterEvent(ctx, team.ID, jupyterValues); err != nil {
   169  				log.WithError(err).Error("failed while registering Jupyter update event")
   170  				return true, err
   171  			}
   172  		case gensql.ChartTypeAirflow:
   173  			log.Info("Trigger update of Airflow")
   174  			airflowValues := chart.AirflowConfigurableValues{
   175  				TeamID: team.ID,
   176  			}
   177  			if err := c.repo.RegisterUpdateAirflowEvent(ctx, team.ID, airflowValues); err != nil {
   178  				log.WithError(err).Error("failed while registering Airflow update event")
   179  				return true, err
   180  			}
   181  		}
   182  	}
   183  
   184  	log.Infof("Successfully updated team %v", team.Slug)
   185  	return false, nil
   186  }
   187  
   188  func (c Client) Delete(ctx context.Context, teamID string, log logger.Logger) bool {
   189  	log.Infof("Deleting team %v", teamID)
   190  
   191  	if retry, err := c.delete(ctx, teamID, log); err != nil {
   192  		log.Info("failed updating team")
   193  		return retry
   194  	}
   195  
   196  	log.Infof("Successfully deleted team %v", teamID)
   197  	return false
   198  }
   199  
   200  func (c Client) delete(ctx context.Context, teamID string, log logger.Logger) (bool, error) {
   201  	team, err := c.repo.TeamGet(ctx, teamID)
   202  	if err != nil && errors.Is(err, sql.ErrNoRows) {
   203  		log.WithError(err).Error("failed retrieving team from database")
   204  		return true, err
   205  	}
   206  
   207  	if err = c.deleteGCPTeamResources(ctx, team.ID); err != nil {
   208  		log.WithError(err).Error("failed while deleting GCP resources")
   209  		return true, err
   210  	}
   211  
   212  	if err = c.deleteK8sNamespace(ctx, k8s.TeamIDToNamespace(team.ID)); err != nil {
   213  		log.WithError(err).Error("failed while deleting k8s namespace")
   214  		return true, err
   215  	}
   216  
   217  	if err = c.repo.TeamDelete(ctx, team.ID); err != nil && errors.Is(err, sql.ErrNoRows) {
   218  		log.WithError(err).Error("failed deleting team from database")
   219  		return true, err
   220  	}
   221  
   222  	log.Info("Trigger delete of Airflow")
   223  	// Kun Airflow som har ressurser utenfor clusteret
   224  	if err := c.repo.RegisterDeleteAirflowEvent(ctx, team.ID); err != nil {
   225  		log.WithError(err).Error("failed while registering Airflow delete event")
   226  		return true, err
   227  	}
   228  
   229  	log.Infof("Successfully deleted team %v", teamID)
   230  	return false, nil
   231  }