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 }