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 }