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  }