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