github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/db/start/start.go (about) 1 package start 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/Redstoneguy129/cli/internal/db/diff" 15 "github.com/Redstoneguy129/cli/internal/db/reset" 16 "github.com/Redstoneguy129/cli/internal/utils" 17 "github.com/docker/docker/api/types/container" 18 "github.com/docker/docker/client" 19 "github.com/docker/go-connections/nat" 20 "github.com/jackc/pgx/v4" 21 "github.com/spf13/afero" 22 ) 23 24 func Run(ctx context.Context, fsys afero.Fs) error { 25 if err := utils.LoadConfigFS(fsys); err != nil { 26 return err 27 } 28 if err := utils.AssertDockerIsRunning(ctx); err != nil { 29 return err 30 } 31 if _, err := utils.Docker.ContainerInspect(ctx, utils.DbId); err == nil { 32 fmt.Fprintln(os.Stderr, "Postgres database is already running.") 33 return nil 34 } 35 err := StartDatabase(ctx, fsys, os.Stderr) 36 if err != nil { 37 utils.DockerRemoveAll(context.Background()) 38 } 39 return err 40 } 41 42 func StartDatabase(ctx context.Context, fsys afero.Fs, w io.Writer, options ...func(*pgx.ConnConfig)) error { 43 config := container.Config{ 44 Image: utils.DbImage, 45 Env: []string{ 46 "POSTGRES_PASSWORD=postgres", 47 "POSTGRES_HOST=/var/run/postgresql", 48 "LC_ALL=C.UTF-8", 49 }, 50 Healthcheck: &container.HealthConfig{ 51 Test: []string{"CMD", "pg_isready", "-U", "postgres", "-h", utils.Config.Hostname, "-p", "5432"}, 52 Interval: 2 * time.Second, 53 Timeout: 2 * time.Second, 54 Retries: 10, 55 }, 56 } 57 if utils.Config.Db.MajorVersion >= 14 { 58 config.Cmd = []string{"postgres", 59 "-c", "config_file=/etc/postgresql/postgresql.conf", 60 // One log file per hour, 24 hours retention 61 "-c", "log_destination=csvlog", 62 "-c", "logging_collector=on", 63 "-c", "log_directory=/var/log/postgresql", 64 "-c", "log_filename=server_%H00_UTC.log", 65 "-c", "log_file_mode=0640", 66 "-c", "log_rotation_age=60", 67 "-c", "log_rotation_size=0", 68 "-c", "log_truncate_on_rotation=on", 69 // Ref: https://postgrespro.com/list/thread-id/2448092 70 "-c", `search_path="$user",public,extensions`, 71 } 72 } 73 hostPort := strconv.FormatUint(uint64(utils.Config.Db.Port), 10) 74 hostConfig := container.HostConfig{ 75 PortBindings: nat.PortMap{"5432/tcp": []nat.PortBinding{{HostPort: hostPort}}}, 76 RestartPolicy: container.RestartPolicy{Name: "always"}, 77 Binds: []string{ 78 utils.DbId + ":/var/lib/postgresql/data", 79 "/dev/null:/docker-entrypoint-initdb.d/migrate.sh:ro", 80 }, 81 } 82 fmt.Fprintln(w, "Starting database...") 83 // Creating volume will not override existing volume, so we must inspect explicitly 84 _, err := utils.Docker.VolumeInspect(ctx, utils.DbId) 85 if _, err := utils.DockerStart(ctx, config, hostConfig, utils.DbId); err != nil { 86 return err 87 } 88 if !reset.WaitForHealthyService(ctx, utils.DbId, 20*time.Second) { 89 fmt.Fprintln(os.Stderr, "Database is not healthy.") 90 } 91 if !client.IsErrNotFound(err) { 92 return initCurrentBranch(fsys) 93 } 94 return initDatabase(ctx, fsys, w, options...) 95 } 96 97 func initCurrentBranch(fsys afero.Fs) error { 98 // Create _current_branch file to avoid breaking db branch commands 99 if _, err := fsys.Stat(utils.CurrBranchPath); err == nil || !errors.Is(err, os.ErrNotExist) { 100 return err 101 } 102 branchDir := filepath.Dir(utils.CurrBranchPath) 103 if err := utils.MkdirIfNotExistFS(fsys, branchDir); err != nil { 104 return err 105 } 106 return afero.WriteFile(fsys, utils.CurrBranchPath, []byte("main"), 0644) 107 } 108 109 func initDatabase(ctx context.Context, fsys afero.Fs, w io.Writer, options ...func(*pgx.ConnConfig)) error { 110 if err := initCurrentBranch(fsys); err != nil { 111 return err 112 } 113 // Initialise globals 114 conn, err := utils.ConnectLocalPostgres(ctx, utils.Config.Hostname, utils.Config.Db.Port, "postgres", options...) 115 if err != nil { 116 return err 117 } 118 defer conn.Close(context.Background()) 119 if err := diff.BatchExecDDL(ctx, conn, strings.NewReader(utils.GlobalsSql)); err != nil { 120 return err 121 } 122 fmt.Fprintln(w, "Setting up initial schema...") 123 return reset.InitialiseDatabase(ctx, conn, fsys) 124 }