github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/types/types.go (about) 1 package types 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/docker/app/internal" 13 "github.com/docker/app/types/metadata" 14 "github.com/docker/app/types/parameters" 15 ) 16 17 // AppSourceKind represents what format the app was in when read 18 type AppSourceKind int 19 20 const ( 21 // AppSourceSplit represents an Application in multiple file format 22 AppSourceSplit AppSourceKind = iota 23 // AppSourceImage represents an Application pulled from an image 24 AppSourceImage 25 // AppSourceArchive represents an Application in an archive format 26 AppSourceArchive 27 ) 28 29 // ShouldRunInsideDirectory returns whether the package is run from a directory on disk 30 func (a AppSourceKind) ShouldRunInsideDirectory() bool { 31 return a == AppSourceSplit || a == AppSourceImage || a == AppSourceArchive 32 } 33 34 // App represents an app 35 type App struct { 36 Name string 37 Path string 38 Cleanup func() 39 Source AppSourceKind 40 41 composesContent [][]byte 42 parametersContent [][]byte 43 parameters parameters.Parameters 44 metadataContent []byte 45 metadata metadata.AppMetadata 46 attachments []Attachment 47 hasCRLF bool 48 } 49 50 // Attachment is a summary of an attachment (attached file) stored in the app definition 51 type Attachment struct { 52 path string 53 size int64 54 } 55 56 // Path returns the local file path 57 func (f *Attachment) Path() string { 58 return f.path 59 } 60 61 // Size returns the file size in bytes 62 func (f *Attachment) Size() int64 { 63 return f.size 64 } 65 66 // Composes returns compose files content 67 func (a *App) Composes() [][]byte { 68 return a.composesContent 69 } 70 71 // ParametersRaw returns parameter files content 72 func (a *App) ParametersRaw() [][]byte { 73 return a.parametersContent 74 } 75 76 // Parameters returns map of parameters 77 func (a *App) Parameters() parameters.Parameters { 78 return a.parameters 79 } 80 81 // MetadataRaw returns metadata file content 82 func (a *App) MetadataRaw() []byte { 83 return a.metadataContent 84 } 85 86 // Metadata returns the metadata struct 87 func (a *App) Metadata() metadata.AppMetadata { 88 return a.metadata 89 } 90 91 // Attachments returns the external files list 92 func (a *App) Attachments() []Attachment { 93 return a.attachments 94 } 95 96 func (a *App) HasCRLF() bool { 97 return a.hasCRLF 98 } 99 100 // Extract writes the app in the specified folder 101 func (a *App) Extract(path string) error { 102 if err := ioutil.WriteFile(filepath.Join(path, internal.MetadataFileName), a.MetadataRaw(), 0644); err != nil { 103 return err 104 } 105 if err := ioutil.WriteFile(filepath.Join(path, internal.ComposeFileName), a.Composes()[0], 0644); err != nil { 106 return err 107 } 108 if err := ioutil.WriteFile(filepath.Join(path, internal.ParametersFileName), a.ParametersRaw()[0], 0644); err != nil { 109 return err 110 } 111 return nil 112 } 113 114 func noop() {} 115 116 // NewApp creates a new docker app with the specified path and struct modifiers 117 func NewApp(path string, ops ...func(*App) error) (*App, error) { 118 app := &App{ 119 Name: path, 120 Path: path, 121 Cleanup: noop, 122 123 composesContent: [][]byte{}, 124 parametersContent: [][]byte{}, 125 metadataContent: []byte{}, 126 } 127 128 for _, op := range ops { 129 if err := op(app); err != nil { 130 return nil, err 131 } 132 } 133 134 return app, nil 135 } 136 137 // NewAppFromDefaultFiles creates a new docker app using the default files in the specified path. 138 // If one of those file doesn't exists, it will error out. 139 func NewAppFromDefaultFiles(path string, ops ...func(*App) error) (*App, error) { 140 appOps := append([]func(*App) error{ 141 MetadataFile(filepath.Join(path, internal.MetadataFileName)), 142 WithComposeFiles(filepath.Join(path, internal.ComposeFileName)), 143 WithParametersFiles(filepath.Join(path, internal.ParametersFileName)), 144 WithAttachments(path), 145 }, ops...) 146 return NewApp(path, appOps...) 147 } 148 149 // WithName sets the application name 150 func WithName(name string) func(*App) error { 151 return func(app *App) error { 152 app.Name = name 153 return nil 154 } 155 } 156 157 // WithPath sets the original path of the app 158 func WithPath(path string) func(*App) error { 159 return func(app *App) error { 160 app.Path = path 161 return nil 162 } 163 } 164 165 // WithCleanup sets the cleanup function of the app 166 func WithCleanup(f func()) func(*App) error { 167 return func(app *App) error { 168 app.Cleanup = f 169 return nil 170 } 171 } 172 173 // WithSource sets the source of the app 174 func WithSource(source AppSourceKind) func(*App) error { 175 return func(app *App) error { 176 app.Source = source 177 return nil 178 } 179 } 180 181 // WithParametersFiles adds the specified parameters files to the app 182 func WithParametersFiles(files ...string) func(*App) error { 183 return parametersLoader(func() ([][]byte, error) { return readFiles(files...) }) 184 } 185 186 // WithAttachments adds all local files (exc. main files) to the app 187 func WithAttachments(rootAppDir string) func(*App) error { 188 return func(app *App) error { 189 return filepath.Walk(rootAppDir, func(path string, info os.FileInfo, err error) error { 190 if err != nil { 191 return err 192 } 193 194 if info.IsDir() { 195 return nil 196 } 197 localFilePath, err := filepath.Rel(rootAppDir, path) 198 if err != nil { 199 return err 200 } 201 switch localFilePath { 202 case internal.ComposeFileName: 203 case internal.MetadataFileName: 204 case internal.ParametersFileName: 205 default: 206 externalFile := Attachment{ 207 // Standardise on forward slashes for windows boxes 208 path: filepath.ToSlash(localFilePath), 209 size: info.Size(), 210 } 211 app.attachments = append(app.attachments, externalFile) 212 } 213 return nil 214 }) 215 } 216 } 217 218 // WithParameters adds the specified parameters readers to the app 219 func WithParameters(readers ...io.Reader) func(*App) error { 220 return parametersLoader(func() ([][]byte, error) { return readReaders(readers...) }) 221 } 222 223 func parametersLoader(f func() ([][]byte, error)) func(*App) error { 224 return func(app *App) error { 225 parametersContent, err := f() 226 if err != nil { 227 return err 228 } 229 parametersContents := append(app.parametersContent, parametersContent...) 230 loaded, err := parameters.LoadMultiple(parametersContents) 231 if err != nil { 232 return err 233 } 234 app.parameters = loaded 235 app.parametersContent = parametersContents 236 return nil 237 } 238 } 239 240 // MetadataFile adds the specified metadata file to the app 241 func MetadataFile(file string) func(*App) error { 242 return metadataLoader(func() ([]byte, error) { return ioutil.ReadFile(file) }) 243 } 244 245 // Metadata adds the specified metadata reader to the app 246 func Metadata(r io.Reader) func(*App) error { 247 return metadataLoader(func() ([]byte, error) { return ioutil.ReadAll(r) }) 248 } 249 250 func metadataLoader(f func() ([]byte, error)) func(app *App) error { 251 return func(app *App) error { 252 d, err := f() 253 if err != nil { 254 return err 255 } 256 loaded, err := metadata.Load(d) 257 if err != nil { 258 return err 259 } 260 app.metadata = loaded 261 app.metadataContent = d 262 app.hasCRLF = bytes.Contains(d, []byte{'\r', '\n'}) 263 return nil 264 } 265 } 266 267 // WithComposeFiles adds the specified compose files to the app 268 func WithComposeFiles(files ...string) func(*App) error { 269 return composeLoader(func() ([][]byte, error) { return readFiles(files...) }) 270 } 271 272 // WithComposes adds the specified compose readers to the app 273 func WithComposes(readers ...io.Reader) func(*App) error { 274 return composeLoader(func() ([][]byte, error) { return readReaders(readers...) }) 275 } 276 277 func composeLoader(f func() ([][]byte, error)) func(app *App) error { 278 return func(app *App) error { 279 composesContent, err := f() 280 if err != nil { 281 return err 282 } 283 app.composesContent = append(app.composesContent, composesContent...) 284 return nil 285 } 286 } 287 288 func readReaders(readers ...io.Reader) ([][]byte, error) { 289 content := make([][]byte, len(readers)) 290 var errs []string 291 for i, r := range readers { 292 d, err := ioutil.ReadAll(r) 293 if err != nil { 294 errs = append(errs, err.Error()) 295 continue 296 } 297 content[i] = d 298 } 299 return content, newErrGroup(errs) 300 } 301 302 func readFiles(files ...string) ([][]byte, error) { 303 content := make([][]byte, len(files)) 304 var errs []string 305 for i, file := range files { 306 d, err := ioutil.ReadFile(file) 307 if err != nil { 308 errs = append(errs, err.Error()) 309 continue 310 } 311 content[i] = d 312 } 313 return content, newErrGroup(errs) 314 } 315 316 func newErrGroup(errs []string) error { 317 if len(errs) == 0 { 318 return nil 319 } 320 return errors.New(strings.Join(errs, "\n")) 321 }