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  }