github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/appfile/file.go (about)

     1  package appfile
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/otto/helper/oneline"
    11  	"github.com/hashicorp/otto/helper/uuid"
    12  )
    13  
    14  const (
    15  	IDFile = ".ottoid"
    16  )
    17  
    18  // File is the structure of a single Appfile.
    19  type File struct {
    20  	// ID is a unique UUID that represents this file. It is generated the
    21  	// first time on compile. This will be blank until the Appfile is
    22  	// compiled with Compile.
    23  	ID string
    24  
    25  	// Path is the path to the root file that was loaded. This might be
    26  	// empty if the appfile was parsed from an io.Reader.
    27  	Path string
    28  
    29  	// Source is non-empty for dependencies and will be the raw source
    30  	// value. This can be used for debugging.
    31  	Source string
    32  
    33  	Application    *Application
    34  	Project        *Project
    35  	Infrastructure []*Infrastructure
    36  	Customization  *CustomizationSet
    37  
    38  	// Imports is the list of imports that this File made. The imports
    39  	// are realized during compilation, but this list won't be cleared
    40  	// in case it wants to be inspected later.
    41  	Imports []*Import
    42  }
    43  
    44  // Application is the structure of an application definition.
    45  type Application struct {
    46  	Name         string
    47  	Type         string
    48  	Detect       bool
    49  	Dependencies []*Dependency `mapstructure:"dependency"`
    50  }
    51  
    52  // Customization is the structure of customization stanzas within
    53  // the Appfile.
    54  type Customization struct {
    55  	Type   string
    56  	Config map[string]interface{}
    57  }
    58  
    59  // Dependency is another Appfile that an App depends on
    60  type Dependency struct {
    61  	Source string
    62  }
    63  
    64  // Project is the structure of a project that many applications
    65  // can belong to.
    66  type Project struct {
    67  	Name           string
    68  	Infrastructure string
    69  }
    70  
    71  // Infrastructure is the structure of defining the infrastructure
    72  // that an application must run on.
    73  type Infrastructure struct {
    74  	Name   string
    75  	Type   string
    76  	Flavor string
    77  
    78  	Foundations []*Foundation
    79  }
    80  
    81  // Foundation is the configuration for the fundamental building blocks
    82  // of the infrastructure.
    83  type Foundation struct {
    84  	Name   string
    85  	Config map[string]interface{}
    86  }
    87  
    88  // Import is an import request of another Appfile into this one
    89  type Import struct {
    90  	Source string
    91  }
    92  
    93  //-------------------------------------------------------------------
    94  // Merging
    95  //-------------------------------------------------------------------
    96  
    97  // Merge will merge the other File onto this one, modifying this
    98  // File with the merged contents.
    99  func (f *File) Merge(other *File) error {
   100  	if other.ID != "" {
   101  		f.ID = other.ID
   102  	}
   103  	if other.Path != "" {
   104  		f.Path = other.Path
   105  	}
   106  
   107  	// Application
   108  	if f.Application == nil {
   109  		f.Application = other.Application
   110  	} else if other.Application != nil {
   111  		// Note this won't copy dependencies properly
   112  		f.Application.Merge(other.Application)
   113  	}
   114  
   115  	// Project
   116  	if f.Project == nil {
   117  		f.Project = other.Project
   118  	} else if other.Project != nil {
   119  		// Note this won't copy dependencies properly
   120  		*f.Project = *other.Project
   121  	}
   122  
   123  	// Infrastructure
   124  	infraMap := make(map[string]int)
   125  	for i, infra := range f.Infrastructure {
   126  		infraMap[infra.Name] = i
   127  	}
   128  	for _, i := range other.Infrastructure {
   129  		idx, ok := infraMap[i.Name]
   130  		if !ok {
   131  			f.Infrastructure = append(f.Infrastructure, i)
   132  			continue
   133  		}
   134  
   135  		old := f.Infrastructure[idx]
   136  		if len(i.Foundations) == 0 {
   137  			i.Foundations = old.Foundations
   138  		}
   139  
   140  		f.Infrastructure[idx] = i
   141  	}
   142  
   143  	// TODO: customizations
   144  	f.Customization = other.Customization
   145  
   146  	return nil
   147  }
   148  
   149  func (app *Application) Merge(other *Application) {
   150  	if other.Name != "" {
   151  		app.Name = other.Name
   152  	}
   153  	if other.Type != "" {
   154  		app.Type = other.Type
   155  	}
   156  	if len(other.Dependencies) > 0 {
   157  		app.Dependencies = other.Dependencies
   158  	}
   159  	if !other.Detect {
   160  		app.Detect = false
   161  	}
   162  }
   163  
   164  //-------------------------------------------------------------------
   165  // Helper Methods
   166  //-------------------------------------------------------------------
   167  
   168  // ActiveInfrastructure returns the Infrastructure that is being
   169  // used for this Appfile.
   170  func (f *File) ActiveInfrastructure() *Infrastructure {
   171  	for _, i := range f.Infrastructure {
   172  		if i.Name == f.Project.Infrastructure {
   173  			return i
   174  		}
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  // resetID deletes the ID associated with this file.
   181  func (f *File) resetID() error {
   182  	return os.Remove(filepath.Join(filepath.Dir(f.Path), IDFile))
   183  }
   184  
   185  // hasID checks whether we have an ID file. This can return an error
   186  // for filesystem errors.
   187  func (f *File) hasID() (bool, error) {
   188  	path := filepath.Join(filepath.Dir(f.Path), IDFile)
   189  	_, err := os.Stat(path)
   190  	if err != nil && !os.IsNotExist(err) {
   191  		return false, err
   192  	}
   193  
   194  	return err == nil, nil
   195  }
   196  
   197  // initID creates a new UUID and writes the file. This will overwrite
   198  // any prior ID file.
   199  func (f *File) initID() error {
   200  	path := filepath.Join(filepath.Dir(f.Path), IDFile)
   201  	uuid := uuid.GenerateUUID()
   202  	data := strings.TrimSpace(fmt.Sprintf(idFileTemplate, uuid)) + "\n"
   203  	return ioutil.WriteFile(path, []byte(data), 0644)
   204  }
   205  
   206  // loadID loads the ID for this File.
   207  func (appF *File) loadID() error {
   208  	hasID, err := appF.hasID()
   209  	if err != nil {
   210  		return err
   211  	}
   212  	if !hasID {
   213  		appF.ID = ""
   214  		return nil
   215  	}
   216  
   217  	path := filepath.Join(filepath.Dir(appF.Path), IDFile)
   218  	uuid, err := oneline.Read(path)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	appF.ID = uuid
   224  	return nil
   225  }
   226  
   227  //-------------------------------------------------------------------
   228  // GoStringer
   229  //-------------------------------------------------------------------
   230  
   231  func (v *Application) GoString() string {
   232  	return fmt.Sprintf("*%#v", *v)
   233  }
   234  
   235  func (v *Customization) GoString() string {
   236  	return fmt.Sprintf("*%#v", *v)
   237  }
   238  
   239  func (v *Foundation) GoString() string {
   240  	return fmt.Sprintf("*%#v", *v)
   241  }
   242  
   243  func (v *Infrastructure) GoString() string {
   244  	return fmt.Sprintf("*%#v", *v)
   245  }
   246  
   247  func (v *Project) GoString() string {
   248  	return fmt.Sprintf("*%#v", *v)
   249  }
   250  
   251  const idFileTemplate = `
   252  %s
   253  
   254  DO NOT MODIFY OR DELETE THIS FILE!
   255  
   256  This file should be checked in to version control. Do not ignore this file.
   257  
   258  The first line is a unique UUID that represents the Appfile in this directory.
   259  This UUID is used globally across your projects to identify this specific
   260  Appfile. This UUID allows you to modify the name of an application, or have
   261  duplicate application names without conflicting.
   262  
   263  If you delete this file, then deploys may duplicate this application since
   264  Otto will be unable to tell that the application is deployed.
   265  `