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  }