drone/app/bootstrap/bootstrap.go

226 lines
6.1 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bootstrap
import (
"context"
"errors"
"fmt"
"github.com/harness/gitness/app/api/controller/service"
"github.com/harness/gitness/app/api/controller/user"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
// systemServicePrincipal is the principal representing gitness.
// It is used for all operations executed by gitness itself.
var systemServicePrincipal *types.Principal
func NewSystemServiceSession() *auth.Session {
return &auth.Session{
Principal: *systemServicePrincipal,
Metadata: &auth.EmptyMetadata{},
}
}
// pipelineServicePrincipal is the principal that is used during
// pipeline executions for calling gitness APIs.
var pipelineServicePrincipal *types.Principal
func NewPipelineServiceSession() *auth.Session {
return &auth.Session{
Principal: *pipelineServicePrincipal,
Metadata: &auth.EmptyMetadata{},
}
}
// Bootstrap is an abstraction of a function that bootstraps a system.
type Bootstrap func(context.Context) error
func System(config *types.Config, userCtrl *user.Controller,
serviceCtrl *service.Controller) func(context.Context) error {
return func(ctx context.Context) error {
if err := SystemService(ctx, config, serviceCtrl); err != nil {
return fmt.Errorf("failed to setup system service: %w", err)
}
if err := PipelineService(ctx, config, serviceCtrl); err != nil {
return fmt.Errorf("failed to setup pipeline service: %w", err)
}
if err := AdminUser(ctx, config, userCtrl); err != nil {
return fmt.Errorf("failed to setup admin user: %w", err)
}
return nil
}
}
// AdminUser sets up the admin user based on the config (if provided).
func AdminUser(ctx context.Context, config *types.Config, userCtrl *user.Controller) error {
if config.Principal.Admin.Password == "" {
return nil
}
usr, err := userCtrl.FindNoAuth(ctx, config.Principal.Admin.UID)
if errors.Is(err, store.ErrResourceNotFound) {
usr, err = createAdminUser(ctx, config, userCtrl)
}
if err != nil {
return fmt.Errorf("failed to setup admin user: %w", err)
}
if !usr.Admin {
return fmt.Errorf("user with uid '%s' exists but is no admin (ID: %d)", usr.UID, usr.ID)
}
log.Ctx(ctx).Info().Msgf("Completed setup of admin user '%s' (id: %d).", usr.UID, usr.ID)
return nil
}
func createAdminUser(
ctx context.Context,
config *types.Config,
userCtrl *user.Controller,
) (*types.User, error) {
in := &user.CreateInput{
UID: config.Principal.Admin.UID,
DisplayName: config.Principal.Admin.DisplayName,
Email: config.Principal.Admin.Email,
Password: config.Principal.Admin.Password,
}
usr, createErr := userCtrl.CreateNoAuth(ctx, in, true)
if createErr == nil || !errors.Is(createErr, store.ErrDuplicate) {
return usr, createErr
}
// user might've been created by another instance in which case we should find it now.
var findErr error
usr, findErr = userCtrl.FindNoAuth(ctx, config.Principal.Admin.UID)
if findErr != nil {
return nil, fmt.Errorf(
"failed to find user with uid '%s' (%w) after duplicate error: %w",
config.Principal.Admin.UID,
findErr,
createErr,
)
}
return usr, nil
}
// SystemService sets up the gitness service principal that is used for
// resources that are automatically created by the system.
func SystemService(
ctx context.Context,
config *types.Config,
serviceCtrl *service.Controller,
) error {
svc, err := serviceCtrl.FindNoAuth(ctx, config.Principal.System.UID)
if errors.Is(err, store.ErrResourceNotFound) {
svc, err = createServicePrincipal(
ctx,
serviceCtrl,
config.Principal.System.UID,
config.Principal.System.Email,
config.Principal.System.DisplayName,
true,
)
}
if err != nil {
return fmt.Errorf("failed to setup system service: %w", err)
}
if !svc.Admin {
return fmt.Errorf("service with uid '%s' exists but is no admin (ID: %d)", svc.UID, svc.ID)
}
systemServicePrincipal = svc.ToPrincipal()
log.Ctx(ctx).Info().Msgf("Completed setup of system service '%s' (id: %d).", svc.UID, svc.ID)
return nil
}
// PipelineService sets up the pipeline service principal that is used during
// pipeline executions for calling gitness APIs.
func PipelineService(
ctx context.Context,
config *types.Config,
serviceCtrl *service.Controller,
) error {
svc, err := serviceCtrl.FindNoAuth(ctx, config.Principal.Pipeline.UID)
if errors.Is(err, store.ErrResourceNotFound) {
svc, err = createServicePrincipal(
ctx,
serviceCtrl,
config.Principal.Pipeline.UID,
config.Principal.Pipeline.Email,
config.Principal.Pipeline.DisplayName,
false,
)
}
if err != nil {
return fmt.Errorf("failed to setup pipeline service: %w", err)
}
pipelineServicePrincipal = svc.ToPrincipal()
log.Ctx(ctx).Info().Msgf("Completed setup of pipeline service '%s' (id: %d).", svc.UID, svc.ID)
return nil
}
func createServicePrincipal(
ctx context.Context,
serviceCtrl *service.Controller,
uid string,
email string,
displayName string,
admin bool,
) (*types.Service, error) {
in := &service.CreateInput{
UID: uid,
Email: email,
DisplayName: displayName,
}
svc, createErr := serviceCtrl.CreateNoAuth(ctx, in, admin)
if createErr == nil || !errors.Is(createErr, store.ErrDuplicate) {
return svc, createErr
}
// service might've been created by another instance in which case we should find it now.
var findErr error
svc, findErr = serviceCtrl.FindNoAuth(ctx, uid)
if findErr != nil {
return nil, fmt.Errorf(
"failed to find service with uid '%s' (%w) after duplicate error: %w",
uid,
findErr,
createErr,
)
}
return svc, nil
}