github.com/supabase/cli@v1.168.1/internal/db/branch/create/create.go (about)

     1  package create
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	_ "embed"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/pkg/stdcopy"
    14  	"github.com/go-errors/errors"
    15  	"github.com/spf13/afero"
    16  	"github.com/supabase/cli/internal/utils"
    17  )
    18  
    19  var (
    20  	//go:embed templates/clone.sh
    21  	cloneScript string
    22  )
    23  
    24  func Run(branch string, fsys afero.Fs) error {
    25  	if err := utils.LoadConfigFS(fsys); err != nil {
    26  		return err
    27  	}
    28  	if err := utils.AssertSupabaseDbIsRunning(); err != nil {
    29  		return err
    30  	}
    31  
    32  	branchPath := filepath.Join(filepath.Dir(utils.CurrBranchPath), branch)
    33  	if err := assertNewBranchIsValid(branchPath, fsys); err != nil {
    34  		return nil
    35  	}
    36  
    37  	var ctx = context.Background()
    38  	if err := createBranch(ctx, branch); err != nil {
    39  		return err
    40  	}
    41  
    42  	if err := fsys.MkdirAll(branchPath, 0755); err != nil {
    43  		return err
    44  	}
    45  
    46  	fmt.Println("Created branch " + utils.Aqua(branch) + ".")
    47  	return nil
    48  }
    49  
    50  func assertNewBranchIsValid(branchPath string, fsys afero.Fs) error {
    51  	branch := filepath.Base(branchPath)
    52  
    53  	if utils.IsBranchNameReserved(branch) {
    54  		return errors.New("Cannot create branch " + utils.Aqua(branch) + ": branch name is reserved.")
    55  	}
    56  
    57  	if !utils.BranchNamePattern.MatchString(branch) {
    58  		return errors.New("Branch name " + utils.Aqua(branch) + " is invalid. Must match [0-9A-Za-z_-]+.")
    59  	}
    60  
    61  	if _, err := afero.ReadDir(fsys, branchPath); errors.Is(err, os.ErrNotExist) {
    62  		// skip
    63  	} else if err != nil {
    64  		return err
    65  	} else {
    66  		return errors.New("Branch " + utils.Aqua(branch) + " already exists.")
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func createBranch(ctx context.Context, branch string) error {
    73  	exec, err := utils.Docker.ContainerExecCreate(ctx, utils.DbId, types.ExecConfig{
    74  		Cmd:          []string{"/bin/bash", "-c", cloneScript},
    75  		Env:          []string{"DB_NAME=" + branch},
    76  		AttachStderr: true,
    77  		AttachStdout: true,
    78  	})
    79  	if err != nil {
    80  		return err
    81  	}
    82  	// Read exec output
    83  	resp, err := utils.Docker.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{})
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer resp.Close()
    88  	// Capture error details
    89  	var errBuf bytes.Buffer
    90  	if _, err := stdcopy.StdCopy(io.Discard, &errBuf, resp.Reader); err != nil {
    91  		return err
    92  	}
    93  	// Get the exit code
    94  	iresp, err := utils.Docker.ContainerExecInspect(ctx, exec.ID)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	if iresp.ExitCode > 0 {
    99  		return errors.New("Error creating branch: " + errBuf.String())
   100  	}
   101  	return nil
   102  }