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 `