github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/pkg/composectl/composectl.go (about)

     1  package composectl
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  
    10  	"github.com/benchkram/errz"
    11  
    12  	"github.com/compose-spec/compose-go/types"
    13  	"github.com/docker/cli/cli/command"
    14  	"github.com/docker/cli/cli/flags"
    15  	"github.com/docker/compose/v2/pkg/api"
    16  	"github.com/docker/compose/v2/pkg/compose"
    17  )
    18  
    19  var (
    20  	ErrInvalidProject = fmt.Errorf("invalid project")
    21  )
    22  
    23  type ComposeController struct {
    24  	project *types.Project
    25  	service api.Service
    26  
    27  	stdout pipe
    28  	stderr pipe
    29  	stdin  pipe
    30  
    31  	logger *logger
    32  
    33  	running bool
    34  }
    35  
    36  type pipe struct {
    37  	r *os.File
    38  	w *os.File
    39  }
    40  
    41  func New(project *types.Project, conflicts, mappings string) (*ComposeController, error) {
    42  	if project == nil || project.Name == "" {
    43  		return nil, ErrInvalidProject
    44  	}
    45  
    46  	c := &ComposeController{
    47  		project: project,
    48  	}
    49  
    50  	// create pipes for stdout, stderr and stdin
    51  	var err error
    52  	c.stdout.r, c.stdout.w, err = os.Pipe()
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	c.stderr.r, c.stderr.w, err = os.Pipe()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	c.stdin.r, c.stdin.w, err = os.Pipe()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	if conflicts != "" {
    68  		conflicts = fmt.Sprintf("%s\n%s\n", "Conflicting ports detected:", conflicts)
    69  		_, err = c.stdout.w.Write([]byte(conflicts))
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  	}
    74  
    75  	if mappings != "" {
    76  		mappings = fmt.Sprintf("%s\n%s\n", "Resolved port mapping:", mappings)
    77  		_, err = c.stdout.w.Write([]byte(mappings))
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  	}
    82  
    83  	logger, err := NewLogConsumer(c.stdout.w)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	c.logger = logger
    88  
    89  	dockerCli, err := command.NewDockerCli(
    90  		command.WithCombinedStreams(nil),
    91  		command.WithOutputStream(nil),
    92  		command.WithErrorStream(nil),
    93  		command.WithInputStream(nil),
    94  	)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	err = dockerCli.Initialize(flags.NewClientOptions())
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	c.service = compose.NewComposeService(dockerCli)
   105  
   106  	return c, nil
   107  }
   108  
   109  func (ctl *ComposeController) Up(ctx context.Context) error {
   110  	err := ctl.service.Up(ctx, ctl.project, api.UpOptions{})
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	go func() {
   116  		err := ctl.service.Logs(ctx, ctl.project.Name, ctl.logger, api.LogOptions{
   117  			Services:   nil,
   118  			Tail:       "",
   119  			Since:      "",
   120  			Until:      "",
   121  			Follow:     true,
   122  			Timestamps: false,
   123  		})
   124  		if err != nil && !errors.Is(err, context.Canceled) {
   125  			errz.Log(err)
   126  		}
   127  	}()
   128  
   129  	ctl.running = true
   130  
   131  	return nil
   132  }
   133  
   134  func (ctl *ComposeController) Down(ctx context.Context) error {
   135  	if ctl.project == nil {
   136  		return ErrInvalidProject
   137  	}
   138  
   139  	if !ctl.running {
   140  		return nil
   141  	}
   142  
   143  	ctl.running = false
   144  
   145  	err := ctl.service.Down(ctx, ctl.project.Name, api.DownOptions{
   146  		Project: ctl.project,
   147  	})
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	// if ctl.stderr.String() != "" {
   153  	//	return ErrComposeError
   154  	// }
   155  
   156  	return nil
   157  }
   158  
   159  func (ctl *ComposeController) Stdout() io.Reader {
   160  	return ctl.stdout.r
   161  }
   162  
   163  func (ctl *ComposeController) Stderr() io.Reader {
   164  	return ctl.stderr.r
   165  }
   166  
   167  func (ctl *ComposeController) Stdin() io.Writer {
   168  	return ctl.stdin.w
   169  }