github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/directory/backend_bolt.go (about) 1 package directory 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 11 "github.com/boltdb/bolt" 12 ) 13 14 var ( 15 boltOttoBucket = []byte("otto") 16 boltAppsBucket = []byte("apps") 17 boltBlobBucket = []byte("blob") 18 boltInfraBucket = []byte("infra") 19 boltBuckets = [][]byte{ 20 boltOttoBucket, 21 boltAppsBucket, 22 boltBlobBucket, 23 boltInfraBucket, 24 } 25 ) 26 27 var ( 28 boltDataVersion byte = 1 29 ) 30 31 // BoltBackend is a Directory backend that stores data on local disk 32 // using BoltDB. 33 // 34 // The primary use case for the BoltBackend is out-of-box experience 35 // for Otto and single developers. For team usage, BoltBackend is not 36 // recommended. 37 // 38 // This backend also implements io.Closer and should be closed. 39 type BoltBackend struct { 40 // Directory where data will be written. This directory will be 41 // created if it doesn't exist. 42 Dir string 43 } 44 45 func (b *BoltBackend) GetBlob(k string) (*BlobData, error) { 46 db, err := b.db() 47 if err != nil { 48 return nil, err 49 } 50 defer db.Close() 51 52 var data []byte 53 err = db.View(func(tx *bolt.Tx) error { 54 bucket := tx.Bucket(boltBlobBucket) 55 data = bucket.Get([]byte(k)) 56 return nil 57 }) 58 if err != nil { 59 return nil, err 60 } 61 if data == nil { 62 return nil, nil 63 } 64 65 // We have to copy the data since it isn't valid once we close the DB 66 data = append([]byte{}, data...) 67 68 return &BlobData{ 69 Key: k, 70 Data: bytes.NewReader(data), 71 }, nil 72 } 73 74 func (b *BoltBackend) PutBlob(k string, d *BlobData) error { 75 db, err := b.db() 76 if err != nil { 77 return err 78 } 79 defer db.Close() 80 81 var buf bytes.Buffer 82 if _, err := io.Copy(&buf, d.Data); err != nil { 83 return err 84 } 85 86 return db.Update(func(tx *bolt.Tx) error { 87 bucket := tx.Bucket(boltBlobBucket) 88 return bucket.Put([]byte(k), buf.Bytes()) 89 }) 90 } 91 92 func (b *BoltBackend) GetInfra(infra *Infra) (*Infra, error) { 93 db, err := b.db() 94 if err != nil { 95 return nil, err 96 } 97 defer db.Close() 98 99 var result *Infra 100 err = db.View(func(tx *bolt.Tx) error { 101 bucket := tx.Bucket(boltInfraBucket).Bucket([]byte( 102 infra.Lookup.Infra)) 103 104 // If the bucket doesn't exist, we haven't written this yet 105 if bucket == nil { 106 return nil 107 } 108 109 // Get the key for this infra 110 data := bucket.Get([]byte(b.infraKey(infra))) 111 if data == nil { 112 return nil 113 } 114 115 result = &Infra{} 116 return b.structRead(result, data) 117 }) 118 if err != nil { 119 return nil, err 120 } 121 122 return result, nil 123 } 124 125 func (b *BoltBackend) PutInfra(infra *Infra) error { 126 if infra.ID == "" { 127 infra.setId() 128 } 129 130 db, err := b.db() 131 if err != nil { 132 return err 133 } 134 defer db.Close() 135 136 return db.Update(func(tx *bolt.Tx) error { 137 data, err := b.structData(infra) 138 if err != nil { 139 return err 140 } 141 142 bucket := tx.Bucket(boltInfraBucket) 143 bucket, err = bucket.CreateBucketIfNotExists([]byte( 144 infra.Lookup.Infra)) 145 if err != nil { 146 return err 147 } 148 149 return bucket.Put([]byte(b.infraKey(infra)), data) 150 }) 151 } 152 153 func (b *BoltBackend) GetDev(dev *Dev) (*Dev, error) { 154 db, err := b.db() 155 if err != nil { 156 return nil, err 157 } 158 defer db.Close() 159 160 var result *Dev 161 err = db.View(func(tx *bolt.Tx) error { 162 // Get the app bucket 163 bucket := tx.Bucket(boltAppsBucket).Bucket([]byte( 164 dev.Lookup.AppID)) 165 if bucket == nil { 166 return nil 167 } 168 169 // Get the key for this infra 170 data := bucket.Get([]byte("dev")) 171 if data == nil { 172 return nil 173 } 174 175 result = &Dev{} 176 return b.structRead(result, data) 177 }) 178 if err != nil { 179 return nil, err 180 } 181 182 return result, nil 183 } 184 185 func (b *BoltBackend) PutDev(dev *Dev) error { 186 if dev.ID == "" { 187 dev.setId() 188 } 189 190 db, err := b.db() 191 if err != nil { 192 return err 193 } 194 defer db.Close() 195 196 return db.Update(func(tx *bolt.Tx) error { 197 data, err := b.structData(dev) 198 if err != nil { 199 return err 200 } 201 202 // Get the app bucket 203 bucket := tx.Bucket(boltAppsBucket) 204 bucket, err = bucket.CreateBucketIfNotExists([]byte( 205 dev.Lookup.AppID)) 206 if err != nil { 207 return err 208 } 209 210 return bucket.Put([]byte("dev"), data) 211 }) 212 } 213 214 func (b *BoltBackend) DeleteDev(dev *Dev) error { 215 db, err := b.db() 216 if err != nil { 217 return err 218 } 219 defer db.Close() 220 221 return db.Update(func(tx *bolt.Tx) error { 222 // Get the app bucket 223 bucket := tx.Bucket(boltAppsBucket) 224 bucket, err = bucket.CreateBucketIfNotExists([]byte( 225 dev.Lookup.AppID)) 226 if err != nil { 227 return err 228 } 229 230 return bucket.Delete([]byte("dev")) 231 }) 232 } 233 234 func (b *BoltBackend) GetBuild(build *Build) (*Build, error) { 235 db, err := b.db() 236 if err != nil { 237 return nil, err 238 } 239 defer db.Close() 240 241 var result *Build 242 err = db.View(func(tx *bolt.Tx) error { 243 // Get the app bucket 244 bucket := tx.Bucket(boltAppsBucket).Bucket([]byte( 245 build.Lookup.AppID)) 246 if bucket == nil { 247 return nil 248 } 249 250 // Get the infra bucket 251 bucket = bucket.Bucket([]byte(fmt.Sprintf( 252 "%s-%s", build.Lookup.Infra, build.Lookup.InfraFlavor))) 253 if bucket == nil { 254 return nil 255 } 256 257 // Get the key for this infra 258 data := bucket.Get([]byte("build")) 259 if data == nil { 260 return nil 261 } 262 263 result = &Build{} 264 return b.structRead(result, data) 265 }) 266 if err != nil { 267 return nil, err 268 } 269 270 return result, nil 271 } 272 273 func (b *BoltBackend) PutBuild(build *Build) error { 274 db, err := b.db() 275 if err != nil { 276 return err 277 } 278 defer db.Close() 279 280 return db.Update(func(tx *bolt.Tx) error { 281 data, err := b.structData(build) 282 if err != nil { 283 return err 284 } 285 286 // Get the app bucket 287 bucket := tx.Bucket(boltAppsBucket) 288 bucket, err = bucket.CreateBucketIfNotExists([]byte( 289 build.Lookup.AppID)) 290 if err != nil { 291 return err 292 } 293 294 // Get the infra bucket 295 bucket, err = bucket.CreateBucketIfNotExists([]byte(fmt.Sprintf( 296 "%s-%s", build.Lookup.Infra, build.Lookup.InfraFlavor))) 297 if err != nil { 298 return err 299 } 300 301 return bucket.Put([]byte("build"), data) 302 }) 303 } 304 305 func (b *BoltBackend) GetDeploy(deploy *Deploy) (*Deploy, error) { 306 db, err := b.db() 307 if err != nil { 308 return nil, err 309 } 310 defer db.Close() 311 312 var result *Deploy 313 err = db.View(func(tx *bolt.Tx) error { 314 // Get the app bucket 315 bucket := tx.Bucket(boltAppsBucket).Bucket([]byte( 316 deploy.Lookup.AppID)) 317 if bucket == nil { 318 return nil 319 } 320 321 // Get the infra bucket 322 bucket = bucket.Bucket([]byte(fmt.Sprintf( 323 "%s-%s", deploy.Lookup.Infra, deploy.Lookup.InfraFlavor))) 324 if bucket == nil { 325 return nil 326 } 327 328 // Get the key for this infra 329 data := bucket.Get([]byte("deploy")) 330 if data == nil { 331 return nil 332 } 333 334 result = &Deploy{} 335 return b.structRead(result, data) 336 }) 337 if err != nil { 338 return nil, err 339 } 340 341 return result, nil 342 } 343 344 func (b *BoltBackend) PutDeploy(deploy *Deploy) error { 345 if deploy.ID == "" { 346 deploy.setId() 347 } 348 349 db, err := b.db() 350 if err != nil { 351 return err 352 } 353 defer db.Close() 354 355 return db.Update(func(tx *bolt.Tx) error { 356 data, err := b.structData(deploy) 357 if err != nil { 358 return err 359 } 360 361 // Get the app bucket 362 bucket := tx.Bucket(boltAppsBucket) 363 bucket, err = bucket.CreateBucketIfNotExists([]byte( 364 deploy.Lookup.AppID)) 365 if err != nil { 366 return err 367 } 368 369 // Get the infra bucket 370 bucket, err = bucket.CreateBucketIfNotExists([]byte(fmt.Sprintf( 371 "%s-%s", deploy.Lookup.Infra, deploy.Lookup.InfraFlavor))) 372 if err != nil { 373 return err 374 } 375 376 return bucket.Put([]byte("deploy"), data) 377 }) 378 } 379 380 func (b *BoltBackend) infraKey(infra *Infra) string { 381 key := "root" 382 if infra.Lookup.Foundation != "" { 383 key = fmt.Sprintf("foundation-%s", infra.Lookup.Foundation) 384 } 385 386 return key 387 } 388 389 // db returns the database handle, and sets up the DB if it has never 390 // been created. 391 func (b *BoltBackend) db() (*bolt.DB, error) { 392 // Make the directory to store our DB 393 if err := os.MkdirAll(b.Dir, 0755); err != nil { 394 return nil, err 395 } 396 397 // Create/Open the DB 398 db, err := bolt.Open(filepath.Join(b.Dir, "otto.db"), 0644, nil) 399 if err != nil { 400 return nil, err 401 } 402 403 // Create the buckets 404 err = db.Update(func(tx *bolt.Tx) error { 405 for _, b := range boltBuckets { 406 if _, err := tx.CreateBucketIfNotExists(b); err != nil { 407 return err 408 } 409 } 410 411 return nil 412 }) 413 if err != nil { 414 return nil, err 415 } 416 417 // Check the Otto version 418 var version byte 419 err = db.Update(func(tx *bolt.Tx) error { 420 bucket := tx.Bucket(boltOttoBucket) 421 data := bucket.Get([]byte("version")) 422 if data == nil || len(data) == 0 { 423 version = boltDataVersion 424 return bucket.Put([]byte("version"), []byte{boltDataVersion}) 425 } 426 427 version = data[0] 428 return nil 429 }) 430 if err != nil { 431 return nil, err 432 } 433 434 if version > boltDataVersion { 435 return nil, fmt.Errorf( 436 "Data version is higher than this version of Otto knows how\n"+ 437 "to handle! This version of Otto can read up to version %d,\n"+ 438 "but version %d data file found.\n\n"+ 439 "This means that a newer version of Otto touched this data,\n"+ 440 "or the data was corrupted in some other way.", 441 boltDataVersion, version) 442 } 443 444 return db, nil 445 } 446 447 func (b *BoltBackend) structData(d interface{}) ([]byte, error) { 448 // Let's just output it in human-readable format to make it easy 449 // for debugging. Disk space won't matter that much for this data. 450 return json.MarshalIndent(d, "", "\t") 451 } 452 453 func (b *BoltBackend) structRead(d interface{}, raw []byte) error { 454 dec := json.NewDecoder(bytes.NewReader(raw)) 455 return dec.Decode(d) 456 }