예제 #1
0
// Transactional executes the given function in a transaction. If todo returns an error, the transaction is rolled back
func Transactional(db DB, todo func(f Application) error) error {
	var tx Transaction
	var err error
	if tx, err = db.BeginTransaction(); err != nil {
		log.Error(nil, map[string]interface{}{
			"err": err,
		}, "database BeginTransaction failed!")

		return errors.WithStack(err)
	}

	if err := todo(tx); err != nil {
		log.Debug(nil, map[string]interface{}{
			"pkg": "application",
		}, "Rolling back the transaction...")

		tx.Rollback()

		log.Error(nil, map[string]interface{}{
			"err": err,
		}, "database transaction failed!")
		return errors.WithStack(err)
	}

	log.Debug(nil, map[string]interface{}{
		"pkg": "application",
	}, "Commit the transaction!")

	return tx.Commit()
}
예제 #2
0
// DeleteCreatedEntities records all created entities on the gorm.DB connection
// and returns a function which can be called on defer to delete created
// entities in reverse order on function exit.
//
// In addition to that, the WIT cache is cleared as well in order to respect any
// deletions made to the db.
//
// Usage:
//
// func TestDatabaseActions(t *testing.T) {
//
// 	// setup database connection
// 	db := ....
// 	// setup auto clean up of created entities
// 	defer DeleteCreatedEntities(db)()
//
// 	repo := NewRepo(db)
// 	repo.Create(X)
// 	repo.Create(X)
// 	repo.Create(X)
// }
//
// Output:
//
// 2017/01/31 12:08:08 Deleting from x 6d143405-1232-40de-bc73-835b543cd972
// 2017/01/31 12:08:08 Deleting from x 0685068d-4934-4d9a-bac2-91eebbca9575
// 2017/01/31 12:08:08 Deleting from x 2d20944e-7952-40c1-bd15-f3fa1a70026d
func DeleteCreatedEntities(db *gorm.DB) func() {
	hookName := "mighti:record"
	type entity struct {
		table   string
		keyname string
		key     interface{}
	}
	var entires []entity
	db.Callback().Create().After("gorm:create").Register(hookName, func(scope *gorm.Scope) {
		entires = append(entires, entity{table: scope.TableName(), keyname: scope.PrimaryKey(), key: scope.PrimaryKeyValue()})
	})
	return func() {
		defer db.Callback().Create().Remove(hookName)
		tx := db.Begin()
		for i := len(entires) - 1; i >= 0; i-- {
			entry := entires[i]
			log.Debug(nil, map[string]interface{}{
				"pkg":   "cleaner",
				"table": entry.table,
				"key":   entry.key,
			}, "Deleting entities from %s with key %s", entry.table, entry.key)
			tx.Table(entry.table).Where(entry.keyname+" = ?", entry.key).Delete("")
		}

		// Delete the work item cache as well
		// NOTE: Feel free to add more cache freeing calls here as needed.
		workitem.ClearGlobalWorkItemTypeCache()

		tx.Commit()
	}
}
예제 #3
0
// Query expose an open ended Query model
func (m *GormIdentityRepository) Query(funcs ...func(*gorm.DB) *gorm.DB) ([]*Identity, error) {
	defer goa.MeasureSince([]string{"goa", "db", "identity", "query"}, time.Now())
	var objs []*Identity

	err := m.db.Scopes(funcs...).Table(m.TableName()).Find(&objs).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, errors.WithStack(err)
	}

	log.Debug(nil, map[string]interface{}{
		"pkg":          "identity",
		"identityList": objs,
	}, "Identity query executed successfully!")

	return objs, nil
}
예제 #4
0
// Save modifies a single record.
func (m *GormIdentityRepository) Save(ctx context.Context, model *Identity) error {
	defer goa.MeasureSince([]string{"goa", "db", "identity", "save"}, time.Now())

	obj, err := m.Load(ctx, model.ID)
	if err != nil {
		log.Error(ctx, map[string]interface{}{
			"identityID": model.ID,
			"ctx":        ctx,
			"err":        err,
		}, "unable to update the identity")
		return errors.WithStack(err)
	}
	err = m.db.Model(obj).Updates(model).Error

	log.Debug(ctx, map[string]interface{}{
		"pkg":        "identity",
		"identityID": model.ID,
	}, "Identity saved!")

	return errors.WithStack(err)
}
예제 #5
0
// Create creates a new record.
func (m *GormUserRepository) Create(ctx context.Context, u *User) error {
	defer goa.MeasureSince([]string{"goa", "db", "user", "create"}, time.Now())

	u.ID = uuid.NewV4()

	err := m.db.Create(u).Error
	if err != nil {
		log.Error(ctx, map[string]interface{}{
			"userID": u.ID,
			"err":    err,
		}, "unable to create the user")
		return errors.WithStack(err)
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":    "user",
		"userID": u.ID,
	}, "User created!")

	return nil
}
예제 #6
0
// Save a single comment
func (m *GormCommentRepository) Save(ctx context.Context, comment *Comment) (*Comment, error) {
	c := Comment{}
	tx := m.db.Where("id=?", comment.ID).First(&c)
	if tx.RecordNotFound() {
		log.Error(ctx, map[string]interface{}{
			"commentID": comment.ID,
		}, "comment not found!")
		// treating this as a not found error: the fact that we're using number internal is implementation detail
		return nil, errors.NewNotFoundError("comment", comment.ID.String())
	}
	if err := tx.Error; err != nil {
		log.Error(ctx, map[string]interface{}{
			"commentID": comment.ID,
			"err":       err,
		}, "comment search operation failed!")

		return nil, errors.NewInternalError(err.Error())
	}
	// make sure no comment is created with an empty 'markup' value
	if comment.Markup == "" {
		comment.Markup = rendering.SystemMarkupDefault
	}
	tx = tx.Save(comment)
	if err := tx.Error; err != nil {
		log.Error(ctx, map[string]interface{}{
			"commentID": comment.ID,
			"err":       err,
		}, "unable to save the comment!")

		return nil, errors.NewInternalError(err.Error())
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":       "comment",
		"commentID": comment.ID,
	}, "Comment updated!")

	return comment, nil
}
예제 #7
0
// Create creates a new record.
func (m *GormCommentRepository) Create(ctx context.Context, comment *Comment) error {
	defer goa.MeasureSince([]string{"goa", "db", "comment", "create"}, time.Now())
	comment.ID = uuid.NewV4()
	// make sure no comment is created with an empty 'markup' value
	if comment.Markup == "" {
		comment.Markup = rendering.SystemMarkupDefault
	}
	if err := m.db.Create(comment).Error; err != nil {
		log.Error(ctx, map[string]interface{}{
			"commentID": comment.ID,
			"err":       err,
		}, "unable to create the comment")
		return errs.WithStack(err)
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":       "comment",
		"commentID": comment.ID,
	}, "Comment created!")

	return nil
}
예제 #8
0
// List return all user identities
func (m *GormIdentityRepository) List(ctx context.Context) (*app.IdentityArray, error) {
	defer goa.MeasureSince([]string{"goa", "db", "identity", "list"}, time.Now())
	var rows []Identity

	err := m.db.Model(&Identity{}).Order("username").Find(&rows).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, errors.WithStack(err)
	}
	res := app.IdentityArray{}
	res.Data = make([]*app.IdentityData, len(rows))
	for index, value := range rows {
		ident := value.ConvertIdentityFromModel()
		res.Data[index] = ident.Data
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":          "identity",
		"identityList": &res,
	}, "Identity List executed successfully!")

	return &res, nil
}
예제 #9
0
// Delete removes a single record.
func (m *GormIdentityRepository) Delete(ctx context.Context, id uuid.UUID) error {
	defer goa.MeasureSince([]string{"goa", "db", "identity", "delete"}, time.Now())

	var obj Identity

	err := m.db.Delete(&obj, id).Error

	if err != nil {
		log.Error(ctx, map[string]interface{}{
			"identityID": id,
			"err":        err,
		}, "unable to delete the identity")
		return errors.WithStack(err)
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":        "identity",
		"identityID": id,
	}, "Identity deleted!")

	return nil
}
예제 #10
0
// Create creates a new record.
func (m *GormIdentityRepository) Create(ctx context.Context, model *Identity) error {
	defer goa.MeasureSince([]string{"goa", "db", "identity", "create"}, time.Now())

	if model.ID == uuid.Nil {
		model.ID = uuid.NewV4()
	}
	err := m.db.Create(model).Error
	if err != nil {
		log.Error(ctx, map[string]interface{}{
			"identityID": model.ID,
			"err":        err,
		}, "unable to create the identity")
		return errors.WithStack(err)
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":        "identity",
		"identityID": model.ID,
	}, "Identity created!")

	return nil
}
예제 #11
0
// Save modifies a single record
func (m *GormUserRepository) Save(ctx context.Context, model *User) error {
	defer goa.MeasureSince([]string{"goa", "db", "user", "save"}, time.Now())

	obj, err := m.Load(ctx, model.ID)
	if err != nil {
		log.Error(ctx, map[string]interface{}{
			"userID": model.ID,
			"err":    err,
		}, "unable to update user")
		return errors.WithStack(err)
	}
	err = m.db.Model(obj).Updates(model).Error
	if err != nil {
		return errors.WithStack(err)
	}

	log.Debug(ctx, map[string]interface{}{
		"pkg":    "user",
		"userID": model.ID,
	}, "User saved!")
	return nil
}
예제 #12
0
func main() {
	// --------------------------------------------------------------------
	// Parse flags
	// --------------------------------------------------------------------
	var configFilePath string
	var printConfig bool
	var migrateDB bool
	var scheduler *remoteworkitem.Scheduler
	flag.StringVar(&configFilePath, "config", "", "Path to the config file to read")
	flag.BoolVar(&printConfig, "printConfig", false, "Prints the config (including merged environment variables) and exits")
	flag.BoolVar(&migrateDB, "migrateDatabase", false, "Migrates the database to the newest version and exits.")
	flag.Parse()

	// Override default -config switch with environment variable only if -config switch was
	// not explicitly given via the command line.
	configSwitchIsSet := false
	flag.Visit(func(f *flag.Flag) {
		if f.Name == "config" {
			configSwitchIsSet = true
		}
	})
	if !configSwitchIsSet {
		if envConfigPath, ok := os.LookupEnv("ALMIGHTY_CONFIG_FILE_PATH"); ok {
			configFilePath = envConfigPath
		}
	}

	var err error
	if err = configuration.Setup(configFilePath); err != nil {
		logrus.Panic(nil, map[string]interface{}{
			"configFilePath": configFilePath,
			"err":            err,
		}, "failed to setup the configuration")
	}

	if printConfig {
		os.Exit(0)
	}

	// Initialized developer mode flag for the logger
	log.InitializeLogger(configuration.IsPostgresDeveloperModeEnabled())

	printUserInfo()

	var db *gorm.DB
	for {
		db, err = gorm.Open("postgres", configuration.GetPostgresConfigString())
		if err != nil {
			db.Close()
			log.Logger().Errorf("ERROR: Unable to open connection to database %v\n", err)
			log.Logger().Infof("Retrying to connect in %v...\n", configuration.GetPostgresConnectionRetrySleep())
			time.Sleep(configuration.GetPostgresConnectionRetrySleep())
		} else {
			defer db.Close()
			break
		}
	}

	if configuration.IsPostgresDeveloperModeEnabled() {
		db = db.Debug()
	}

	// Migrate the schema
	err = migration.Migrate(db.DB())
	if err != nil {
		log.Panic(nil, map[string]interface{}{
			"err": fmt.Sprintf("%+v", err),
		}, "failed migration")
	}

	// Nothing to here except exit, since the migration is already performed.
	if migrateDB {
		os.Exit(0)
	}

	// Make sure the database is populated with the correct types (e.g. bug etc.)
	if configuration.GetPopulateCommonTypes() {
		// set a random request ID for the context
		ctx, req_id := client.ContextWithRequestID(context.Background())
		log.Debug(ctx, nil, "Initializing the population of the database... Request ID: %v", req_id)

		if err := models.Transactional(db, func(tx *gorm.DB) error {
			return migration.PopulateCommonTypes(ctx, tx, workitem.NewWorkItemTypeRepository(tx))
		}); err != nil {
			log.Panic(ctx, map[string]interface{}{
				"err": fmt.Sprintf("%+v", err),
			}, "failed to populate common types")
		}
		if err := models.Transactional(db, func(tx *gorm.DB) error {
			return migration.BootstrapWorkItemLinking(ctx, link.NewWorkItemLinkCategoryRepository(tx), link.NewWorkItemLinkTypeRepository(tx))
		}); err != nil {
			log.Panic(ctx, map[string]interface{}{
				"err": fmt.Sprintf("%+v", err),
			}, "failed to bootstap work item linking")
		}
	}

	// Scheduler to fetch and import remote tracker items
	scheduler = remoteworkitem.NewScheduler(db)
	defer scheduler.Stop()
	scheduler.ScheduleAllQueries()

	// Create service
	service := goa.New("alm")

	// Mount middleware
	service.Use(middleware.RequestID())
	service.Use(middleware.LogRequest(configuration.IsPostgresDeveloperModeEnabled()))
	service.Use(gzip.Middleware(9))
	service.Use(jsonapi.ErrorHandler(service, true))
	service.Use(middleware.Recover())

	service.WithLogger(goalogrus.New(log.Logger()))

	publicKey, err := token.ParsePublicKey(configuration.GetTokenPublicKey())
	if err != nil {
		log.Panic(nil, map[string]interface{}{
			"err": fmt.Sprintf("%+v", err),
		}, "failed to parse public token")
	}

	// Setup Account/Login/Security
	identityRepository := account.NewIdentityRepository(db)
	userRepository := account.NewUserRepository(db)

	tokenManager := token.NewManager(publicKey)
	app.UseJWTMiddleware(service, jwt.New(publicKey, nil, app.NewJWTSecurity()))
	service.Use(login.InjectTokenManager(tokenManager))

	// Mount "login" controller
	oauth := &oauth2.Config{
		ClientID:     configuration.GetKeycloakClientID(),
		ClientSecret: configuration.GetKeycloakSecret(),
		Scopes:       []string{"user:email"},
		Endpoint: oauth2.Endpoint{
			AuthURL:  configuration.GetKeycloakEndpointAuth(),
			TokenURL: configuration.GetKeycloakEndpointToken(),
		},
	}

	appDB := gormapplication.NewGormDB(db)

	loginService := login.NewKeycloakOAuthProvider(oauth, identityRepository, userRepository, tokenManager, appDB)
	loginCtrl := NewLoginController(service, loginService, tokenManager)
	app.MountLoginController(service, loginCtrl)

	// Mount "status" controller
	statusCtrl := NewStatusController(service, db)
	app.MountStatusController(service, statusCtrl)

	// Mount "workitem" controller
	workitemCtrl := NewWorkitemController(service, appDB)
	app.MountWorkitemController(service, workitemCtrl)

	// Mount "workitemtype" controller
	workitemtypeCtrl := NewWorkitemtypeController(service, appDB)
	app.MountWorkitemtypeController(service, workitemtypeCtrl)

	// Mount "work item link category" controller
	workItemLinkCategoryCtrl := NewWorkItemLinkCategoryController(service, appDB)
	app.MountWorkItemLinkCategoryController(service, workItemLinkCategoryCtrl)

	// Mount "work item link type" controller
	workItemLinkTypeCtrl := NewWorkItemLinkTypeController(service, appDB)
	app.MountWorkItemLinkTypeController(service, workItemLinkTypeCtrl)

	// Mount "work item link" controller
	workItemLinkCtrl := NewWorkItemLinkController(service, appDB)
	app.MountWorkItemLinkController(service, workItemLinkCtrl)

	// Mount "work item comments" controller
	workItemCommentsCtrl := NewWorkItemCommentsController(service, appDB)
	app.MountWorkItemCommentsController(service, workItemCommentsCtrl)

	// Mount "work item relationships links" controller
	workItemRelationshipsLinksCtrl := NewWorkItemRelationshipsLinksController(service, appDB)
	app.MountWorkItemRelationshipsLinksController(service, workItemRelationshipsLinksCtrl)

	// Mount "comments" controller
	commentsCtrl := NewCommentsController(service, appDB)
	app.MountCommentsController(service, commentsCtrl)

	// Mount "tracker" controller
	c5 := NewTrackerController(service, appDB, scheduler)
	app.MountTrackerController(service, c5)

	// Mount "trackerquery" controller
	c6 := NewTrackerqueryController(service, appDB, scheduler)
	app.MountTrackerqueryController(service, c6)

	// Mount "space" controller
	spaceCtrl := NewSpaceController(service, appDB)
	app.MountSpaceController(service, spaceCtrl)

	// Mount "user" controller
	userCtrl := NewUserController(service, appDB, tokenManager)
	app.MountUserController(service, userCtrl)

	// Mount "search" controller
	searchCtrl := NewSearchController(service, appDB)
	app.MountSearchController(service, searchCtrl)

	// Mount "indentity" controller
	identityCtrl := NewIdentityController(service, appDB)
	app.MountIdentityController(service, identityCtrl)

	// Mount "users" controller
	usersCtrl := NewUsersController(service, appDB)
	app.MountUsersController(service, usersCtrl)

	// Mount "iterations" controller
	iterationCtrl := NewIterationController(service, appDB)
	app.MountIterationController(service, iterationCtrl)

	// Mount "spaceiterations" controller
	spaceIterationCtrl := NewSpaceIterationsController(service, appDB)
	app.MountSpaceIterationsController(service, spaceIterationCtrl)

	// Mount "userspace" controller
	userspaceCtrl := NewUserspaceController(service, db)
	app.MountUserspaceController(service, userspaceCtrl)

	// Mount "render" controller
	renderCtrl := NewRenderController(service)
	app.MountRenderController(service, renderCtrl)

	// Mount "areas" controller
	areaCtrl := NewAreaController(service, appDB)
	app.MountAreaController(service, areaCtrl)

	spaceAreaCtrl := NewSpaceAreasController(service, appDB)
	app.MountSpaceAreasController(service, spaceAreaCtrl)

	log.Logger().Infoln("Git Commit SHA: ", Commit)
	log.Logger().Infoln("UTC Build Time: ", BuildTime)
	log.Logger().Infoln("UTC Start Time: ", StartTime)
	log.Logger().Infoln("Dev mode:       ", configuration.IsPostgresDeveloperModeEnabled())

	http.Handle("/api/", service.Mux)
	http.Handle("/", http.FileServer(assetFS()))
	http.Handle("/favicon.ico", http.NotFoundHandler())

	// Start http
	if err := http.ListenAndServe(configuration.GetHTTPAddress(), nil); err != nil {
		log.Error(nil, map[string]interface{}{
			"addr": configuration.GetHTTPAddress(),
			"err":  err,
		}, "unable to connect to server")
		service.LogError("startup", "err", err)
	}

}