github.com/navikt/knorten@v0.0.0-20240419132333-1333f46ed8b6/pkg/team/team.go (about)

     1  package team
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  
     8  	"github.com/navikt/knorten/pkg/chart"
     9  	"github.com/navikt/knorten/pkg/database"
    10  	"github.com/navikt/knorten/pkg/database/gensql"
    11  	"github.com/navikt/knorten/pkg/k8s"
    12  	"github.com/navikt/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).Info("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).Info("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).Info("failed creating team namespace")
    79  		return true, err
    80  	}
    81  
    82  	if err := c.createK8sServiceAccount(ctx, team.ID, namespace); err != nil {
    83  		log.WithError(err).Info("failed creating k8s service account")
    84  		return true, err
    85  	}
    86  
    87  	if err := c.repo.TeamCreate(ctx, team); err != nil {
    88  		log.WithError(err).Info("failed saving team to database")
    89  		return true, err
    90  	}
    91  
    92  	return false, nil
    93  }
    94  
    95  func (c Client) Update(ctx context.Context, team gensql.Team, log logger.Logger) bool {
    96  	log.Infof("Updating team %v", team.ID)
    97  
    98  	if retry, err := c.update(ctx, team, log); err != nil {
    99  		log.Info("failed updating team")
   100  		return retry
   101  	}
   102  
   103  	return false
   104  }
   105  
   106  func (c Client) update(ctx context.Context, team gensql.Team, log logger.Logger) (bool, error) {
   107  	err := c.repo.TeamUpdate(ctx, team)
   108  	if err != nil {
   109  		log.WithError(err).Info("failed updating team in database")
   110  		return true, err
   111  	}
   112  
   113  	namespace := k8s.TeamIDToNamespace(team.ID)
   114  	namespaceExists, err := c.k8sNamespaceExists(ctx, namespace)
   115  	if err != nil {
   116  		log.WithError(err).Info("failed while checking if namespace exists")
   117  		return true, err
   118  	}
   119  
   120  	if !namespaceExists {
   121  		if err := c.createK8sNamespace(ctx, namespace); err != nil {
   122  			log.WithError(err).Info("failed creating team namespace")
   123  			return true, err
   124  		}
   125  	}
   126  
   127  	serviceAccountExists, err := c.k8sServiceAccountExists(ctx, team.ID, namespace)
   128  	if err != nil {
   129  		log.WithError(err).Info("failed while checking if service accpunt exists")
   130  		return true, err
   131  	}
   132  
   133  	if !serviceAccountExists {
   134  		if err := c.createK8sServiceAccount(ctx, team.ID, namespace); err != nil {
   135  			log.WithError(err).Info("failed creating k8s service account")
   136  			return true, err
   137  		}
   138  	}
   139  
   140  	if err := c.updateGCPTeamResources(ctx, team); err != nil {
   141  		log.WithError(err).Info("failed while updating GCP resources")
   142  		return true, err
   143  	}
   144  
   145  	apps, err := c.repo.ChartsForTeamGet(ctx, team.ID)
   146  	if err != nil {
   147  		log.WithError(err).Infof("failed getting apps for team %v", team.ID)
   148  		return true, err
   149  	}
   150  
   151  	for _, app := range apps {
   152  		switch app {
   153  		case gensql.ChartTypeJupyterhub:
   154  			log.Info("Trigger update of Jupyter")
   155  			jupyterValues := chart.JupyterConfigurableValues{
   156  				TeamID: team.ID,
   157  			}
   158  			if err := c.repo.RegisterUpdateJupyterEvent(ctx, team.ID, jupyterValues); err != nil {
   159  				log.WithError(err).Info("failed while registering Jupyter update event")
   160  				return true, err
   161  			}
   162  		case gensql.ChartTypeAirflow:
   163  			log.Info("Trigger update of Airflow")
   164  			airflowValues := chart.AirflowConfigurableValues{
   165  				TeamID: team.ID,
   166  			}
   167  			if err := c.repo.RegisterUpdateAirflowEvent(ctx, team.ID, airflowValues); err != nil {
   168  				log.WithError(err).Info("failed while registering Airflow update event")
   169  				return true, err
   170  			}
   171  		}
   172  	}
   173  
   174  	log.Infof("Successfully updated team %v", team.Slug)
   175  	return false, nil
   176  }
   177  
   178  func (c Client) Delete(ctx context.Context, teamID string, log logger.Logger) bool {
   179  	log.Infof("Deleting team %v", teamID)
   180  
   181  	if retry, err := c.delete(ctx, teamID, log); err != nil {
   182  		log.Info("failed updating team")
   183  		return retry
   184  	}
   185  
   186  	log.Infof("Successfully deleted team %v", teamID)
   187  	return false
   188  }
   189  
   190  func (c Client) delete(ctx context.Context, teamID string, log logger.Logger) (bool, error) {
   191  	team, err := c.repo.TeamGet(ctx, teamID)
   192  	if err != nil && errors.Is(err, sql.ErrNoRows) {
   193  		log.WithError(err).Info("failed retrieving team from database")
   194  		return true, err
   195  	}
   196  
   197  	if err = c.deleteGCPTeamResources(ctx, team.ID); err != nil {
   198  		log.WithError(err).Info("failed while deleting GCP resources")
   199  		return true, err
   200  	}
   201  
   202  	if err = c.deleteK8sNamespace(ctx, k8s.TeamIDToNamespace(team.ID)); err != nil {
   203  		log.WithError(err).Info("failed while deleting k8s namespace")
   204  		return true, err
   205  	}
   206  
   207  	if err = c.repo.TeamDelete(ctx, team.ID); err != nil && errors.Is(err, sql.ErrNoRows) {
   208  		log.WithError(err).Info("failed deleting team from database")
   209  		return true, err
   210  	}
   211  
   212  	log.Info("Trigger delete of Airflow")
   213  	// Kun Airflow som har ressurser utenfor clusteret
   214  	if err := c.repo.RegisterDeleteAirflowEvent(ctx, team.ID); err != nil {
   215  		log.WithError(err).Info("failed while registering Airflow delete event")
   216  		return true, err
   217  	}
   218  
   219  	log.Infof("Successfully deleted team %v", teamID)
   220  	return false, nil
   221  }