github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/sqlstore/migrations/migrations.go (about)

     1  package migrations
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/go-gormigrate/gormigrate/v2"
     7  	"gorm.io/gorm"
     8  
     9  	"github.com/pyroscope-io/pyroscope/pkg/config"
    10  	"github.com/pyroscope-io/pyroscope/pkg/model"
    11  )
    12  
    13  // Migrate executes all migrations UP that did not run yet.
    14  //
    15  //  1. Migrations must be backward compatible and only extend the schema.
    16  //  2. Migration ID must be a unix epoch time in seconds (use 'date +%s').
    17  //  3. Although in the current shape schema downgrades are not supported,
    18  //     Rollback function must be also provided, and migrations must not import
    19  //     any models in order to avoid side effects: instead, the type should be
    20  //     explicitly defined within the migration body.
    21  //  4. Migration code must be tested within the corresponding service package.
    22  //
    23  // A note on schema downgrades (not supported yet):
    24  //
    25  // As long as migrations are backward compatible, support for schema downgrade
    26  // is not required. Once we introduce a breaking change (e.g, change column
    27  // name or modify the data in any way), downgrades must be supported.
    28  //
    29  // The problem is that this requires migrator to know the future schema:
    30  // v0.4.4 can't know which actions should be taken to revert the database from
    31  // v0.4.5, but not vice-versa (unless they use the same source of migration
    32  // scripts). There are some options:
    33  //   - Changes can be reverted with the later version (that is installed before
    34  //     the application downgrade) or with an independent tool – in containerized
    35  //     deployments this can cause significant difficulties.
    36  //   - Store migrations (as raw SQL scripts) in the database itself or elsewhere
    37  //     locally/remotely.
    38  //
    39  // Before we have a very strong reason to perform a schema downgrade or violate
    40  // the schema backward compatibility guaranties, we should follow the basic
    41  // principle: "... to maintain backwards compatibility between the DB and all
    42  // versions of the code currently deployed in production."
    43  // (https://flywaydb.org/documentation/concepts/migrations#important-notes).
    44  // On the other hand, "the lack of an effective rollback script can be a gating
    45  // factor in the integration and deployment process" (Database Reliability
    46  // Engineering by Laine Campbell & Charity Majors).
    47  func Migrate(db *gorm.DB, c *config.Server) error {
    48  	return gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
    49  		createUserTableMigration(c.Auth.Internal.AdminUser),
    50  		createAPIKeyTableMigration(),
    51  		createAnnotationsTableMigration(),
    52  		addIndexesUniqueTableMigration(),
    53  		createApplicationMetadataTableMigration(),
    54  	}).Migrate()
    55  }
    56  
    57  func createUserTableMigration(adminUser config.AdminUser) *gormigrate.Migration {
    58  	type user struct {
    59  		ID                uint       `gorm:"primarykey"`
    60  		Name              string     `gorm:"type:varchar(255);not null;default:null;index:,unique"`
    61  		Email             *string    `gorm:"type:varchar(255);default:null;index:,unique"`
    62  		FullName          *string    `gorm:"type:varchar(255);default:null"`
    63  		PasswordHash      []byte     `gorm:"type:varchar(255);not null;default:null"`
    64  		Role              int        `gorm:"not null;default:null"`
    65  		IsDisabled        *bool      `gorm:"not null;default:false"`
    66  		IsExternal        *bool      `gorm:"not null;default:false"`
    67  		LastSeenAt        *time.Time `gorm:"default:null"`
    68  		PasswordChangedAt time.Time
    69  		CreatedAt         time.Time
    70  		UpdatedAt         time.Time
    71  	}
    72  	return &gormigrate.Migration{
    73  		ID: "1638496809",
    74  		Migrate: func(tx *gorm.DB) error {
    75  			if err := tx.AutoMigrate(&user{}); err != nil {
    76  				return err
    77  			}
    78  			if adminUser.Create {
    79  				return tx.Create(&user{
    80  					Name:              adminUser.Name,
    81  					Email:             model.String(adminUser.Email),
    82  					PasswordHash:      model.MustPasswordHash(adminUser.Password),
    83  					PasswordChangedAt: time.Now(),
    84  					Role:              int(model.AdminRole),
    85  				}).Error
    86  			}
    87  			return nil
    88  		},
    89  		Rollback: func(tx *gorm.DB) error {
    90  			return tx.Migrator().DropTable(&user{})
    91  		},
    92  	}
    93  }
    94  
    95  func createAPIKeyTableMigration() *gormigrate.Migration {
    96  	type apiKey struct {
    97  		ID         uint       `gorm:"primarykey"`
    98  		Name       string     `gorm:"type:varchar(255);not null;default:null;index:,unique"`
    99  		Hash       []byte     `gorm:"type:varchar(255);not null;default:null"`
   100  		Role       int        `gorm:"not null;default:null"`
   101  		ExpiresAt  *time.Time `gorm:"default:null"`
   102  		LastSeenAt *time.Time `gorm:"default:null"`
   103  		CreatedAt  time.Time
   104  	}
   105  
   106  	return &gormigrate.Migration{
   107  		ID: "1641917891",
   108  		Migrate: func(tx *gorm.DB) error {
   109  			return tx.AutoMigrate(&apiKey{})
   110  		},
   111  		Rollback: func(tx *gorm.DB) error {
   112  			return tx.Migrator().DropTable(&apiKey{})
   113  		},
   114  	}
   115  }
   116  
   117  func createAnnotationsTableMigration() *gormigrate.Migration {
   118  	type annotation struct {
   119  		ID        uint      `gorm:"primarykey"`
   120  		AppName   string    `gorm:"not null;default:null"`
   121  		Timestamp time.Time `gorm:"not null;default:null"`
   122  		Content   string    `gorm:"not null;default:null"`
   123  		CreatedAt time.Time
   124  		UpdatedAt time.Time
   125  	}
   126  
   127  	return &gormigrate.Migration{
   128  		ID: "1661975049",
   129  		Migrate: func(tx *gorm.DB) error {
   130  			return tx.AutoMigrate(&annotation{})
   131  		},
   132  		Rollback: func(tx *gorm.DB) error {
   133  			return tx.Migrator().DropTable(&annotation{})
   134  		},
   135  	}
   136  }
   137  
   138  func addIndexesUniqueTableMigration() *gormigrate.Migration {
   139  	type annotation struct {
   140  		AppName   string    `gorm:"index:idx_appname_timestamp,unique;not null;default:null"`
   141  		Timestamp time.Time `gorm:"index:idx_appname_timestamp,unique;not null;default:null"`
   142  	}
   143  
   144  	return &gormigrate.Migration{
   145  		ID: "1663269650",
   146  		Migrate: func(tx *gorm.DB) error {
   147  			return tx.AutoMigrate(&annotation{})
   148  		},
   149  		Rollback: func(tx *gorm.DB) error {
   150  			return tx.Migrator().DropIndex(&annotation{}, "idx_appname_timestamp")
   151  		},
   152  	}
   153  }
   154  
   155  func createApplicationMetadataTableMigration() *gormigrate.Migration {
   156  	type applicationMetadata struct {
   157  		ID              uint   `gorm:"primarykey"`
   158  		FQName          string `gorm:"uniqueIndex;not null;default:null"`
   159  		SpyName         string
   160  		SampleRate      uint32
   161  		Units           string
   162  		AggregationType string
   163  		CreatedAt       time.Time
   164  		UpdatedAt       time.Time
   165  	}
   166  
   167  	return &gormigrate.Migration{
   168  		ID: "1667213046",
   169  		Migrate: func(tx *gorm.DB) error {
   170  			return tx.AutoMigrate(&applicationMetadata{})
   171  		},
   172  		Rollback: func(tx *gorm.DB) error {
   173  			return tx.Migrator().DropTable(&applicationMetadata{})
   174  		},
   175  	}
   176  }