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