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  }