github.com/mgoltzsche/ctnr@v0.7.1-alpha/cmd/common.go (about) 1 // Copyright © 2017 Max Goltzsche 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "path/filepath" 22 "runtime/debug" 23 "strings" 24 25 "github.com/mgoltzsche/ctnr/bundle" 26 "github.com/mgoltzsche/ctnr/bundle/builder" 27 "github.com/mgoltzsche/ctnr/image" 28 "github.com/mgoltzsche/ctnr/model" 29 "github.com/mgoltzsche/ctnr/model/oci" 30 exterrors "github.com/mgoltzsche/ctnr/pkg/errors" 31 "github.com/mgoltzsche/ctnr/run" 32 "github.com/mgoltzsche/ctnr/run/factory" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 "github.com/spf13/cobra" 36 ) 37 38 func wrapRun(cf func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) { 39 return func(cmd *cobra.Command, args []string) { 40 defer func() { 41 if err := recover(); err != nil { 42 msg := "\n OUPS, THIS SEEMS TO BE A BUG!" 43 msg += "\n Please report it at" 44 msg += "\n https://github.com/mgoltzsche/ctnr/issues/new" 45 msg += "\n with a description of what you did and the stacktrace" 46 msg += "\n below if you cannot find an already existing issue at" 47 msg += "\n https://github.com/mgoltzsche/ctnr/issues\n" 48 stackTrace := strings.Replace(string(debug.Stack()), "\n", "\n ", -1) 49 // TODO: Add version 50 logrus.Fatalf("%+v\n%s\n PANIC: %s\n %s", err, msg, err, stackTrace) 51 os.Exit(255) 52 } 53 }() 54 err := cf(cmd, args) 55 closeLockedImageStore() 56 exitOnError(cmd, err) 57 } 58 } 59 60 func exitOnError(cmd *cobra.Command, err error) { 61 if err == nil { 62 return 63 } 64 65 // Usage error - print help text and exit 66 if _, ok := err.(UsageError); ok { 67 logger.Errorf("%s\n%s\n%s", err, cmd.UsageString(), err) 68 os.Exit(1) 69 } 70 71 // Handle exit error 72 exitCode := 255 73 errLog := loggers.Error 74 cause := errors.Cause(err) 75 if exitErr, ok := cause.(*run.ExitError); ok { 76 exitCode = exitErr.Code() 77 errLog = errLog.WithField("id", exitErr.ContainerID()).WithField("code", exitCode) 78 err = errors.New("container process terminated with error") 79 } 80 81 // Log stacktrace 82 errStr := err.Error() 83 errDetails, _ := errorCausesString(err, nil) 84 if errDetails != errStr { 85 loggers.Debug.Println(errDetails) 86 } 87 88 // Print error & exit 89 errLog.Println(errStr) 90 os.Exit(exitCode) 91 } 92 93 func errorCausesString(err error, lastTraceLines []string) (debug string, lastTracedCause error) { 94 type causer interface { 95 error 96 Cause() error 97 } 98 type tracer interface { 99 error 100 StackTrace() errors.StackTrace 101 } 102 str := "" 103 if traced, ok := err.(tracer); ok { 104 st := traced.StackTrace() 105 traceLines := make([]string, len(st)) 106 for i, t := range st { 107 traceLines[i] = fmt.Sprintf("%+v", t) 108 } 109 truncate := len(traceLines) 110 offset := len(traceLines) - len(lastTraceLines) 111 for i := len(traceLines) - 1; i >= 0; i-- { 112 j := i - offset 113 if j < 0 || len(lastTraceLines) <= j || lastTraceLines[j] != traceLines[i] { 114 truncate = i + 1 115 break 116 } 117 } 118 for _, t := range traceLines[0:truncate] { 119 str += "\n " + strings.Replace(t, "\n", "\n ", -1) 120 } 121 if truncate < len(traceLines)-1 { 122 str += "\n ... (truncated)" 123 } 124 lastTraceLines = traceLines 125 } 126 errMsg := err.Error() 127 wrapper, ok := err.(causer) 128 if ok && wrapper.Cause() != nil { 129 cause := wrapper.Cause() 130 causeStr, lastTracedCause := errorCausesString(cause, lastTraceLines) 131 if str == "" { 132 return causeStr, lastTracedCause 133 } 134 causeMsg := "" 135 if lastTracedCause != nil { 136 causeMsg = lastTracedCause.Error() 137 } 138 pos := len(errMsg) - len(causeMsg) 139 if strings.HasSuffix(errMsg, ": "+causeMsg) && pos > 0 { 140 str = errMsg[0:pos-2] + str 141 } else { 142 str = errMsg + str 143 } 144 return str + "\n " + causeStr, err 145 } 146 return errMsg + str, err 147 } 148 149 func usageError(msg string) UsageError { 150 return UsageError(msg) 151 } 152 153 type UsageError string 154 155 func (err UsageError) Error() string { 156 return string(err) 157 } 158 159 func exitError(exitCode int, frmt string, values ...interface{}) { 160 loggers.Error.Printf(frmt, values...) 161 os.Exit(exitCode) 162 } 163 164 func openImageStore() (image.ImageStoreRW, error) { 165 if lockedImageStore == nil { 166 s, err := store.OpenLockedImageStore() 167 if err != nil { 168 return nil, err 169 } 170 lockedImageStore = s 171 } 172 return lockedImageStore, nil 173 } 174 175 func closeLockedImageStore() { 176 if lockedImageStore != nil { 177 lockedImageStore.Close() 178 } 179 } 180 181 func newContainerManager() (run.ContainerManager, error) { 182 return factory.NewContainerManager(filepath.Join(flagStateDir, "containers"), flagRootless, loggers) 183 } 184 185 func resourceResolver(baseDir string, volumes map[string]model.Volume) model.ResourceResolver { 186 paths := model.NewPathResolver(baseDir) 187 return model.NewResourceResolver(paths, volumes) 188 } 189 190 func runServices(services []model.Service, res model.ResourceResolver) (err error) { 191 manager, err := newContainerManager() 192 if err != nil { 193 return 194 } 195 196 containers := run.NewContainerGroup(loggers.Debug) 197 defer func() { 198 err = exterrors.Append(err, containers.Close()) 199 }() 200 201 for _, s := range services { 202 var c run.Container 203 loggers.Debug.Println(s.JSON()) 204 if c, err = createContainer(&s, res, manager, true); err != nil { 205 return 206 } 207 containers.Add(c) 208 } 209 210 closeLockedImageStore() 211 containers.Start() 212 containers.Wait() 213 return 214 } 215 216 func createContainer(model *model.Service, res model.ResourceResolver, manager run.ContainerManager, destroyOnClose bool) (c run.Container, err error) { 217 var bundle *bundle.LockedBundle 218 if bundle, err = createRuntimeBundle(model, res); err != nil { 219 return 220 } 221 defer func() { 222 err = exterrors.Append(err, bundle.Close()) 223 }() 224 225 ioe := run.NewStdContainerIO() 226 if model.StdinOpen { 227 ioe.Stdin = os.Stdin 228 } 229 230 return manager.NewContainer(&run.ContainerConfig{ 231 Id: "", 232 Bundle: bundle, 233 Io: ioe, 234 NoNewKeyring: model.NoNewKeyring, 235 NoPivotRoot: model.NoPivot, 236 DestroyOnClose: destroyOnClose, 237 }) 238 } 239 240 func createRuntimeBundle(service *model.Service, res model.ResourceResolver) (b *bundle.LockedBundle, err error) { 241 if service.Image == "" { 242 return nil, errors.Errorf("service %q has no image", service.Name) 243 } 244 245 istore, err := openImageStore() 246 if err != nil { 247 return 248 } 249 250 bundleId := service.Bundle 251 bundleDir := "" 252 if isFile(bundleId) { 253 bundleDir = bundleId 254 bundleId = "" 255 } 256 257 // Create bundle 258 if bundleDir != "" { 259 b, err = bundle.CreateLockedBundle(bundleDir, service.BundleUpdate) 260 } else { 261 b, err = store.CreateBundle(bundleId, service.BundleUpdate) 262 } 263 if err != nil { 264 return 265 } 266 defer func() { 267 if err != nil { 268 b.Delete() 269 } 270 }() 271 272 // Apply image 273 builder := builder.Builder(b.ID()) 274 if service.Image != "" { 275 var img image.Image 276 if img, err = image.GetImage(istore, service.Image); err != nil { 277 return b, err 278 } 279 builder.SetImage(image.NewUnpackableImage(&img, istore)) 280 } 281 282 // Apply config.json 283 netDataDir := filepath.Join(flagStateDir, "networks") 284 if err = oci.ToSpec(service, res, flagRootless, netDataDir, flagPRootPath, builder); err != nil { 285 return b, err 286 } 287 288 return b, builder.Build(b) 289 } 290 291 func isFile(file string) bool { 292 return file != "" && (filepath.IsAbs(file) || file == "." || file == ".." || len(file) > 1 && file[0:2] == "./" || len(file) > 2 && file[0:3] == "../" || file == "~" || len(file) > 1 && file[0:2] == "~/") 293 } 294 295 func checkNonEmpty(s string) (err error) { 296 if len(bytes.TrimSpace([]byte(s))) == 0 { 297 err = usageError("empty value") 298 } 299 return 300 }