<commit_before>package lmdb

import (
	"errors"
	"github.com/szferi/gomdb"
)

// Thread Safety
// 1) NOTLS mode is used exclusively, which allows read txns to freely migrate across
//    threads and for a single thread to maintain multiple read txns. This enables mostly
//    care-free use of read txns, for example when using gevent.
// 2) Most objects can be safely called by a single caller from a single thread, and usually it
//    only makes sense to have a single caller, except in the case of Context.
// 3) Most Context methods are thread-safe, and may be called concurrently, except for
//    Context.Close().
// 4) A write txn may only be used from the thread it was created on.
// 5) A read-only txn can move across threads, but it cannot be used concurrently from multiple
//    threads.
// 6) Iterator is not thread-safe, but it does not make sense to use it on any thread except the
//    thread that currently owns its associated txn.
//
//-------------------------------------------------------------------------------------------------
//
// Best practice:
// 1) Use iterators only in the txn that they are created
// 2) DO NOT modify the memory slice from GetNoCopy
// 3) Make sure all read/write txns are finished before Context.Close().

const (
	// There is no penalty for making this huge.
	// If you are on a 32-bit system, use Open2 and specify a smaller map size.
	MAP_SIZE_DEFAULT uint64 = 64 * 1024 * 1024 * 1024 * 1024 // 64TB
)

type Context struct {
	env *mdb.Env
	// In this package, a DBI is obtained only through Open/Open2, and is never closed until
	// Context.Close(), in which all dbis are closed automatically.
	buckets map[string]mdb.DBI
	txn     *mdb.Txn
	// Cached iterators in the current transaction, will be closed when txn finishes.
	itrs []*Iterator
}

type Stat mdb.Stat
type Info mdb.Info

//--------------------------------- DB ------------------------------------------------------------

func Version() string {
	return mdb.Version()
}

func Open(path string, buckets []string) (Context, error) {
	return Open2(path, buckets, MAP_SIZE_DEFAULT)
}

func Open2(path string, buckets []string, maxMapSize uint64) (ctx Context, err error) {
	env, err := mdb.NewEnv()
	if err != nil {
		return
	}

	err = env.SetMapSize(maxMapSize)
	if err != nil {
		return
	}

	// http://www.openldap.org/lists/openldap-technical/201305/msg00176.html
	err = env.SetMaxDBs((mdb.DBI)(len(buckets)))
	if err != nil {
		return
	}

	MDB_NOTLS := uint(0x200000)
	MDB_NORDAHEAD := uint(0x800000)
	err = env.Open(path, MDB_NOTLS|MDB_NORDAHEAD, 0664)
	if err != nil {
		return
	}

	bucketCache := make(map[string]mdb.DBI)
	ctx = Context{env, nil, nil, nil}

	err = ctx.Transactional(true, func(ctx *Context) error {
		for _, name := range buckets {
			if name == "" {
				return errors.New("Bucket name is empty")
			}
			dbi, err := ctx.txn.DBIOpen(&name, mdb.CREATE)
			if err != nil {
				return err
			} else {
				bucketCache[name] = dbi
			}
		}
		return nil
	})

	if err != nil {
		return
	} else {
		ctx.buckets = bucketCache
	}

	return
}

func (ctx *Context) Close() {
	if ctx.txn != nil {
		panic("Closing database inside a transaction")
	}
	if ctx.env != nil {
		ctx.env.Close() // all opened dbis are closed during this process
	}
}

func (ctx *Context) DBStat() *Stat {
	stat, err := ctx.env.Stat()
	if err != nil { // Possible errors: EINVAL
		panic(err)
	}
	return (*Stat)(stat)
}

func (ctx *Context) Info() *Info {
	info, err := ctx.env.Info()
	if err != nil { // error when env == nil, so panic
		panic(err)
	}
	return (*Info)(info)
}

func (ctx *Context) Transactional(write bool, f func(ctx *Context) error) (err error) {
	var flag uint = 0
	if !write {
		flag = mdb.RDONLY
	}
	txn, err := ctx.env.BeginTxn(ctx.txn, flag)
	if err != nil { // Possible Errors: MDB_PANIC, MDB_MAP_RESIZED, MDB_READERS_FULL, ENOMEM
		panic(err)
	}
	var panicF interface{} // panic from f
	newCtx := Context{ctx.env, ctx.buckets, txn, nil}

	defer func() {
		for _, itr := range newCtx.itrs {
			itr.Close() // no panic
		}
		newCtx.itrs = nil

		if err == nil && panicF == nil && write {
			e := txn.Commit()
			if e != nil { // Possible errors: EINVAL, ENOSPEC, EIO, ENOMEM
				panic(e)
			}
		} else {
			txn.Abort()
			if panicF != nil {
				panic(panicF) // re-panic
			}
		}
	}()

	err = func() (err error) {
		defer func() {
			panicF = recover()
		}()
		err = f(&newCtx)
		return
	}()

	return
}

// panic if {bucket} does not exist, internal use
func (ctx *Context) getBucketId(bucket string) mdb.DBI {
	id, b := ctx.buckets[bucket]
	if !b {
		panic("bucket does not exist")
	} else {
		return id
	}
}

func (ctx *Context) ClearBucket(bucket string) {
	err := ctx.txn.Drop(ctx.getBucketId(bucket), 0)
	if err != nil { // Possible errors: EINVAL, EACCES, MDB_BAD_DBI
		panic(err)
	}
}

func (ctx *Context) TxStat(bucket string) *Stat {
	stat, err := ctx.txn.Stat(ctx.getBucketId(bucket))
	if err != nil { // Possible errors: EINVAL, MDB_BAD_TXN
		panic(err)
	}
	return (*Stat)(stat)
}

// Return {nil, false} if {key} does not exist, {val, true} if {key} exist
func (ctx *Context) Get(bucket string, key []byte) ([]byte, bool) {
	v, err := ctx.txn.GetVal(ctx.getBucketId(bucket), key)
	if err != nil {
		if err == mdb.NotFound {
			return nil, false
		} else { // Possible errors: EINVAL, MDB_BAD_TXN, MDB_BAD_VALSIZE, etc
			panic(err)
		}
	}
	return v.Bytes(), true
}

// 1) Return {nil, false} if {key} does not exist, {val, true} if {key} exist
func (ctx *Context) GetNoCopy(bucket string, key []byte) ([]byte, bool) {
	v, err := ctx.txn.GetVal(ctx.getBucketId(bucket), key)
	if err != nil {
		if err == mdb.NotFound {
			return nil, false
		} else { // Possible errors: EINVAL, MDB_BAD_TXN, MDB_BAD_VALSIZE, etc
			panic(err)
		}
	}
	return v.BytesNoCopy(), true
}

func (ctx *Context) Put(bucket string, key, val []byte) {
	err := ctx.txn.Put(ctx.getBucketId(bucket), key, val, 0)
	if err != nil { // Possible errors: MDB_MAP_FULL, MDB_TXN_FULL, EACCES, EINVAL
		panic(err)
	}
}

func (ctx *Context) Delete(bucket string, key []byte) {
	err := ctx.txn.Del(ctx.getBucketId(bucket), key, nil)
	if err != nil && err != mdb.NotFound { // Possible errors: EINVAL, EACCES, MDB_BAD_TXN
		panic(err)
	}
}

// Return an iterator pointing to the first item in the bucket.
// If the bucket is empty, nil is returned.
func (ctx *Context) Iterate(bucket string) *Iterator {
	cur, err := ctx.txn.CursorOpen(ctx.getBucketId(bucket))
	if err != nil {
		panic(err)
	}

	itr := (*Iterator)(cur)

	if itr.SeekFirst() {
		ctx.itrs = append(ctx.itrs, itr)
		return itr
	} else {
		itr.Close()
		return nil
	}
}
<commit_msg>rename TxStat to BucketStat<commit_after>package lmdb

import (
	"errors"
	"github.com/szferi/gomdb"
)

// Thread Safety
// 1) NOTLS mode is used exclusively, which allows read txns to freely migrate across
//    threads and for a single thread to maintain multiple read txns. This enables mostly
//    care-free use of read txns, for example when using gevent.
// 2) Most objects can be safely called by a single caller from a single thread, and usually it
//    only makes sense to have a single caller, except in the case of Context.
// 3) Most Context methods are thread-safe, and may be called concurrently, except for
//    Context.Close().
// 4) A write txn may only be used from the thread it was created on.
// 5) A read-only txn can move across threads, but it cannot be used concurrently from multiple
//    threads.
// 6) Iterator is not thread-safe, but it does not make sense to use it on any thread except the
//    thread that currently owns its associated txn.
//
//-------------------------------------------------------------------------------------------------
//
// Best practice:
// 1) Use iterators only in the txn that they are created
// 2) DO NOT modify the memory slice from GetNoCopy
// 3) Make sure all read/write txns are finished before Context.Close().

const (
	// There is no penalty for making this huge.
	// If you are on a 32-bit system, use Open2 and specify a smaller map size.
	MAP_SIZE_DEFAULT uint64 = 64 * 1024 * 1024 * 1024 * 1024 // 64TB
)

type Context struct {
	env *mdb.Env
	// In this package, a DBI is obtained only through Open/Open2, and is never closed until
	// Context.Close(), in which all dbis are closed automatically.
	buckets map[string]mdb.DBI
	txn     *mdb.Txn
	// Cached iterators in the current transaction, will be closed when txn finishes.
	itrs []*Iterator
}

type Stat mdb.Stat
type Info mdb.Info

//--------------------------------- DB ------------------------------------------------------------

func Version() string {
	return mdb.Version()
}

func Open(path string, buckets []string) (Context, error) {
	return Open2(path, buckets, MAP_SIZE_DEFAULT)
}

func Open2(path string, buckets []string, maxMapSize uint64) (ctx Context, err error) {
	env, err := mdb.NewEnv()
	if err != nil {
		return
	}

	err = env.SetMapSize(maxMapSize)
	if err != nil {
		return
	}

	// http://www.openldap.org/lists/openldap-technical/201305/msg00176.html
	err = env.SetMaxDBs((mdb.DBI)(len(buckets)))
	if err != nil {
		return
	}

	MDB_NOTLS := uint(0x200000)
	MDB_NORDAHEAD := uint(0x800000)
	err = env.Open(path, MDB_NOTLS|MDB_NORDAHEAD, 0664)
	if err != nil {
		return
	}

	bucketCache := make(map[string]mdb.DBI)
	ctx = Context{env, nil, nil, nil}

	err = ctx.Transactional(true, func(ctx *Context) error {
		for _, name := range buckets {
			if name == "" {
				return errors.New("Bucket name is empty")
			}
			dbi, err := ctx.txn.DBIOpen(&name, mdb.CREATE)
			if err != nil {
				return err
			} else {
				bucketCache[name] = dbi
			}
		}
		return nil
	})

	if err != nil {
		return
	} else {
		ctx.buckets = bucketCache
	}

	return
}

func (ctx *Context) Close() {
	if ctx.txn != nil {
		panic("Closing database inside a transaction")
	}
	if ctx.env != nil {
		ctx.env.Close() // all opened dbis are closed during this process
	}
}

func (ctx *Context) DBStat() *Stat {
	stat, err := ctx.env.Stat()
	if err != nil { // Possible errors: EINVAL
		panic(err)
	}
	return (*Stat)(stat)
}

func (ctx *Context) Info() *Info {
	info, err := ctx.env.Info()
	if err != nil { // error when env == nil, so panic
		panic(err)
	}
	return (*Info)(info)
}

func (ctx *Context) Transactional(write bool, f func(ctx *Context) error) (err error) {
	var flag uint = 0
	if !write {
		flag = mdb.RDONLY
	}
	txn, err := ctx.env.BeginTxn(ctx.txn, flag)
	if err != nil { // Possible Errors: MDB_PANIC, MDB_MAP_RESIZED, MDB_READERS_FULL, ENOMEM
		panic(err)
	}
	var panicF interface{} // panic from f
	newCtx := Context{ctx.env, ctx.buckets, txn, nil}

	defer func() {
		for _, itr := range newCtx.itrs {
			itr.Close() // no panic
		}
		newCtx.itrs = nil

		if err == nil && panicF == nil && write {
			e := txn.Commit()
			if e != nil { // Possible errors: EINVAL, ENOSPEC, EIO, ENOMEM
				panic(e)
			}
		} else {
			txn.Abort()
			if panicF != nil {
				panic(panicF) // re-panic
			}
		}
	}()

	err = func() (err error) {
		defer func() {
			panicF = recover()
		}()
		err = f(&newCtx)
		return
	}()

	return
}

// panic if {bucket} does not exist, internal use
func (ctx *Context) getBucketId(bucket string) mdb.DBI {
	id, b := ctx.buckets[bucket]
	if !b {
		panic("bucket does not exist")
	} else {
		return id
	}
}

func (ctx *Context) ClearBucket(bucket string) {
	err := ctx.txn.Drop(ctx.getBucketId(bucket), 0)
	if err != nil { // Possible errors: EINVAL, EACCES, MDB_BAD_DBI
		panic(err)
	}
}

func (ctx *Context) BucketStat(bucket string) *Stat {
	stat, err := ctx.txn.Stat(ctx.getBucketId(bucket))
	if err != nil { // Possible errors: EINVAL, MDB_BAD_TXN
		panic(err)
	}
	return (*Stat)(stat)
}

// Return {nil, false} if {key} does not exist, {val, true} if {key} exist
func (ctx *Context) Get(bucket string, key []byte) ([]byte, bool) {
	v, err := ctx.txn.GetVal(ctx.getBucketId(bucket), key)
	if err != nil {
		if err == mdb.NotFound {
			return nil, false
		} else { // Possible errors: EINVAL, MDB_BAD_TXN, MDB_BAD_VALSIZE, etc
			panic(err)
		}
	}
	return v.Bytes(), true
}

// 1) Return {nil, false} if {key} does not exist, {val, true} if {key} exist
func (ctx *Context) GetNoCopy(bucket string, key []byte) ([]byte, bool) {
	v, err := ctx.txn.GetVal(ctx.getBucketId(bucket), key)
	if err != nil {
		if err == mdb.NotFound {
			return nil, false
		} else { // Possible errors: EINVAL, MDB_BAD_TXN, MDB_BAD_VALSIZE, etc
			panic(err)
		}
	}
	return v.BytesNoCopy(), true
}

func (ctx *Context) Put(bucket string, key, val []byte) {
	err := ctx.txn.Put(ctx.getBucketId(bucket), key, val, 0)
	if err != nil { // Possible errors: MDB_MAP_FULL, MDB_TXN_FULL, EACCES, EINVAL
		panic(err)
	}
}

func (ctx *Context) Delete(bucket string, key []byte) {
	err := ctx.txn.Del(ctx.getBucketId(bucket), key, nil)
	if err != nil && err != mdb.NotFound { // Possible errors: EINVAL, EACCES, MDB_BAD_TXN
		panic(err)
	}
}

// Return an iterator pointing to the first item in the bucket.
// If the bucket is empty, nil is returned.
func (ctx *Context) Iterate(bucket string) *Iterator {
	cur, err := ctx.txn.CursorOpen(ctx.getBucketId(bucket))
	if err != nil {
		panic(err)
	}

	itr := (*Iterator)(cur)

	if itr.SeekFirst() {
		ctx.itrs = append(ctx.itrs, itr)
		return itr
	} else {
		itr.Close()
		return nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/gonuts/logger"
	"github.com/lhcb-org/pkr/yum"
)

type External struct {
	cmd string
	err error
}
type FixFct func(*Context) error

type Context struct {
	msg       *logger.Logger
	cfg       Config
	siteroot  string // where to install software, binaries, ...
	repourl   string
	rpmprefix string
	dbpath    string
	etcdir    string
	yumconf   string
	yumreposd string
	yum       *yum.Client
	tmpdir    string
	bindir    string
	libdir    string
	initfile  string

	extstatus map[string]External
	reqext    []string
	extfix    map[string]FixFct
}

func New(cfg Config, dbg bool) (*Context, error) {
	var err error
	siteroot := cfg.Siteroot()
	if siteroot == "" {
		siteroot = "/opt/cern-sw"
	}

	ctx := Context{
		cfg:       cfg,
		msg:       logger.NewLogger("pkr", logger.WARNING, os.Stdout),
		siteroot:  siteroot,
		repourl:   cfg.RepoUrl(),
		rpmprefix: cfg.Prefix(),
		dbpath:    filepath.Join(siteroot, "var", "lib", "rpm"),
		etcdir:    filepath.Join(siteroot, "etc"),
		yumconf:   filepath.Join(siteroot, "etc", "yum.conf"),
		yumreposd: filepath.Join(siteroot, "etc", "yum.repos.d"),
		tmpdir:    filepath.Join(siteroot, "tmp"),
		bindir:    filepath.Join(siteroot, "usr", "bin"),
		libdir:    filepath.Join(siteroot, "lib"),
		initfile:  filepath.Join(siteroot, "etc", "repoinit"),
	}
	if dbg {
		ctx.msg.SetLevel(logger.DEBUG)
	}
	for _, dir := range []string{
		ctx.tmpdir,
		ctx.bindir,
		ctx.libdir,
	} {
		err = os.MkdirAll(dir, 0644)
		if err != nil {
			ctx.msg.Errorf("could not create directory %q: %v\n", dir, err)
			return nil, err
		}
	}
	os.Setenv("PATH", os.Getenv("PATH")+string(os.PathListSeparator)+ctx.bindir)

	// make sure the db is initialized
	err = ctx.initRpmDb()
	if err != nil {
		return nil, err
	}

	// yum
	err = ctx.initYum()
	if err != nil {
		return nil, err
	}

	ctx.yum, err = yum.New(ctx.siteroot)
	if err != nil {
		return nil, err
	}
	if dbg {
		ctx.yum.SetLevel(logger.DEBUG)
	}

	// defining structures and checking if all needed tools are available
	ctx.extstatus = make(map[string]External)
	ctx.reqext = []string{"rpm"}
	ctx.extfix = make(map[string]FixFct)
	err = ctx.checkPreRequisites()
	if err != nil {
		return nil, err
	}

	err = ctx.checkRepository()
	if err != nil {
		return nil, err
	}

	return &ctx, err
}

func (ctx *Context) Exit(rc int) {
	os.Exit(rc)
}

// initRpmDb initializes the RPM database
func (ctx *Context) initRpmDb() error {
	var err error
	msg := ctx.msg
	msg.Infof("RPM DB in %q\n", ctx.dbpath)
	err = os.MkdirAll(ctx.dbpath, 0644)
	if err != nil {
		msg.Errorf(
			"could not create directory %q for RPM DB: %v\n",
			ctx.dbpath,
			err,
		)
		return err
	}

	pkgdir := filepath.Join(ctx.dbpath, "Packages")
	if !path_exists(pkgdir) {
		msg.Infof("Initializing RPM db\n")
		cmd := exec.Command(
			"rpm",
			"--dbpath", ctx.dbpath,
			"--initdb",
		)
		out, err := cmd.CombinedOutput()
		msg.Debugf(string(out))
		if err != nil {
			return fmt.Errorf("error initializing RPM DB: %v", err)
		}
	}
	return err
}

func (ctx *Context) initYum() error {
	var err error
	err = os.MkdirAll(ctx.etcdir, 0644)
	if err != nil {
		return fmt.Errorf("could not create dir %q: %v", ctx.etcdir, err)
	}

	if !path_exists(ctx.yumconf) {
		yum, err := os.Create(ctx.yumconf)
		if err != nil {
			return err
		}
		defer yum.Close()
		err = ctx.writeYumConf(yum)
		if err != nil {
			return err
		}
		err = yum.Sync()
		if err != nil {
			return err
		}
		err = yum.Close()
		if err != nil {
			return err
		}
	}
	err = ctx.cfg.InitYum(ctx)
	return err
}

// checkPreRequisites makes sure that all external tools required by
// this tool to perform the installation are present
func (ctx *Context) checkPreRequisites() error {
	var err error
	extmissing := false
	missing := make([]string, 0)

	for _, ext := range ctx.reqext {
		cmd, err := exec.LookPath(ext)
		ctx.extstatus[ext] = External{
			cmd: cmd,
			err: err,
		}
	}

	for k, ext := range ctx.extstatus {
		if ext.err == nil {
			ctx.msg.Infof("%s: Found %q\n", k, ext.cmd)
			continue
		}
		ctx.msg.Infof("%s: Missing - trying compensatory measure\n", k)
		fix, ok := ctx.extfix[k]
		if !ok {
			extmissing = true
			missing = append(missing, k)
			continue
		}

		err = fix(ctx)
		if err != nil {
			return err
		}

		cmd, err := exec.LookPath(k)
		ctx.extstatus[k] = External{
			cmd: cmd,
			err: err,
		}
		if err == nil {
			ctx.msg.Infof("%s: Found %q\n", k, cmd)
			continue
		}
		ctx.msg.Infof("%s: Missing\n", k)
		extmissing = true
		missing = append(missing, k)
	}

	if extmissing {
		err = fmt.Errorf("missing external(s): %v", missing)
	}
	return err
}

func (ctx *Context) checkRepository() error {
	var err error
	if !path_exists(ctx.initfile) {
		fini, err := os.Create(ctx.initfile)
		if err != nil {
			return err
		}
		defer fini.Close()
		_, err = fini.WriteString(time.Now().Format(time.RFC3339) + "\n")
		if err != nil {
			return err
		}
		err = fini.Sync()
		if err != nil {
			return err
		}
		return fini.Close()
	}
	err = ctx.checkUpdates()
	return err
}

func (ctx *Context) writeYumConf(w io.Writer) error {
	var err error
	const tmpl = `
[main]
#CONFVERSION 0001
cachedir=/var/cache/yum
debuglevel=2
logfile=/var/log/yum.log
pkgpolicy=newest
distroverpkg=redhat-release
tolerant=1
exactarch=1
obsoletes=1
plugins=1
gpgcheck=0
installroot=%s
reposdir=/etc/yum.repos.d
`
	_, err = fmt.Fprintf(w, tmpl, ctx.siteroot)
	return err
}

func (ctx *Context) writeYumRepo(w io.Writer, data map[string]string) error {
	var err error
	const tmpl = `
[%s]
#REPOVERSION 0001
name=%s
baseurl=%s
enabled=1
`
	_, err = fmt.Fprintf(w, tmpl,
		data["name"],
		data["name"],
		data["url"],
	)
	return err
}

// checkUpdates checks whether packages could be updated in the repository
func (ctx *Context) checkUpdates() error {
	var err error
	return err
}

// install performs the whole download/install procedure (eq. yum install)
func (ctx *Context) install(project, version, cmtconfig string) error {
	var err error
	ctx.msg.Infof("Installing %s/%s/%s\n", project, version, cmtconfig)
	return err
}

// InstallRPM installs a RPM by name
func (ctx *Context) InstallRPM(name, version, release string, forceInstall, update bool) error {
	var err error
	pkg, err := ctx.yum.FindLatestMatchingName(name, version, release)
	if err != nil {
		return err
	}
	err = ctx.InstallPackage(pkg, forceInstall, update)
	return err
}

// InstallPackage installs a specific RPM, checking if not already installed
func (ctx *Context) InstallPackage(pkg *yum.Package, forceInstall, update bool) error {
	var err error
	return err
}

// ListPackages lists all packages satisfying pattern (a regexp)
func (ctx *Context) ListPackages(name, version, release string) error {
	var err error
	total := 0
	pkgs, err := ctx.yum.ListPackages(name, version, release)
	if err != nil {
		return err
	}

	for _, pkg := range pkgs {
		fmt.Printf("%s\n", pkg.RpmName())
		total += 1
	}
	ctx.msg.Infof("Total matching: %d\n", total)
	return err
}

// EOF
<commit_msg>context: add rpm convenience function<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/gonuts/logger"
	"github.com/lhcb-org/pkr/yum"
)

type External struct {
	cmd string
	err error
}
type FixFct func(*Context) error

type Context struct {
	msg       *logger.Logger
	cfg       Config
	siteroot  string // where to install software, binaries, ...
	repourl   string
	rpmprefix string
	dbpath    string
	etcdir    string
	yumconf   string
	yumreposd string
	yum       *yum.Client
	tmpdir    string
	bindir    string
	libdir    string
	initfile  string

	extstatus map[string]External
	reqext    []string
	extfix    map[string]FixFct
}

func New(cfg Config, dbg bool) (*Context, error) {
	var err error
	siteroot := cfg.Siteroot()
	if siteroot == "" {
		siteroot = "/opt/cern-sw"
	}

	ctx := Context{
		cfg:       cfg,
		msg:       logger.NewLogger("pkr", logger.WARNING, os.Stdout),
		siteroot:  siteroot,
		repourl:   cfg.RepoUrl(),
		rpmprefix: cfg.Prefix(),
		dbpath:    filepath.Join(siteroot, "var", "lib", "rpm"),
		etcdir:    filepath.Join(siteroot, "etc"),
		yumconf:   filepath.Join(siteroot, "etc", "yum.conf"),
		yumreposd: filepath.Join(siteroot, "etc", "yum.repos.d"),
		tmpdir:    filepath.Join(siteroot, "tmp"),
		bindir:    filepath.Join(siteroot, "usr", "bin"),
		libdir:    filepath.Join(siteroot, "lib"),
		initfile:  filepath.Join(siteroot, "etc", "repoinit"),
	}
	if dbg {
		ctx.msg.SetLevel(logger.DEBUG)
	}
	for _, dir := range []string{
		ctx.tmpdir,
		ctx.bindir,
		ctx.libdir,
	} {
		err = os.MkdirAll(dir, 0644)
		if err != nil {
			ctx.msg.Errorf("could not create directory %q: %v\n", dir, err)
			return nil, err
		}
	}
	os.Setenv("PATH", os.Getenv("PATH")+string(os.PathListSeparator)+ctx.bindir)

	// make sure the db is initialized
	err = ctx.initRpmDb()
	if err != nil {
		return nil, err
	}

	// yum
	err = ctx.initYum()
	if err != nil {
		return nil, err
	}

	ctx.yum, err = yum.New(ctx.siteroot)
	if err != nil {
		return nil, err
	}
	if dbg {
		ctx.yum.SetLevel(logger.DEBUG)
	}

	// defining structures and checking if all needed tools are available
	ctx.extstatus = make(map[string]External)
	ctx.reqext = []string{"rpm"}
	ctx.extfix = make(map[string]FixFct)
	err = ctx.checkPreRequisites()
	if err != nil {
		return nil, err
	}

	err = ctx.checkRepository()
	if err != nil {
		return nil, err
	}

	return &ctx, err
}

func (ctx *Context) Exit(rc int) {
	os.Exit(rc)
}

// initRpmDb initializes the RPM database
func (ctx *Context) initRpmDb() error {
	var err error
	msg := ctx.msg
	msg.Infof("RPM DB in %q\n", ctx.dbpath)
	err = os.MkdirAll(ctx.dbpath, 0644)
	if err != nil {
		msg.Errorf(
			"could not create directory %q for RPM DB: %v\n",
			ctx.dbpath,
			err,
		)
		return err
	}

	pkgdir := filepath.Join(ctx.dbpath, "Packages")
	if !path_exists(pkgdir) {
		msg.Infof("Initializing RPM db\n")
		cmd := exec.Command(
			"rpm",
			"--dbpath", ctx.dbpath,
			"--initdb",
		)
		out, err := cmd.CombinedOutput()
		msg.Debugf(string(out))
		if err != nil {
			return fmt.Errorf("error initializing RPM DB: %v", err)
		}
	}
	return err
}

func (ctx *Context) initYum() error {
	var err error
	err = os.MkdirAll(ctx.etcdir, 0644)
	if err != nil {
		return fmt.Errorf("could not create dir %q: %v", ctx.etcdir, err)
	}

	if !path_exists(ctx.yumconf) {
		yum, err := os.Create(ctx.yumconf)
		if err != nil {
			return err
		}
		defer yum.Close()
		err = ctx.writeYumConf(yum)
		if err != nil {
			return err
		}
		err = yum.Sync()
		if err != nil {
			return err
		}
		err = yum.Close()
		if err != nil {
			return err
		}
	}
	err = ctx.cfg.InitYum(ctx)
	return err
}

// checkPreRequisites makes sure that all external tools required by
// this tool to perform the installation are present
func (ctx *Context) checkPreRequisites() error {
	var err error
	extmissing := false
	missing := make([]string, 0)

	for _, ext := range ctx.reqext {
		cmd, err := exec.LookPath(ext)
		ctx.extstatus[ext] = External{
			cmd: cmd,
			err: err,
		}
	}

	for k, ext := range ctx.extstatus {
		if ext.err == nil {
			ctx.msg.Infof("%s: Found %q\n", k, ext.cmd)
			continue
		}
		ctx.msg.Infof("%s: Missing - trying compensatory measure\n", k)
		fix, ok := ctx.extfix[k]
		if !ok {
			extmissing = true
			missing = append(missing, k)
			continue
		}

		err = fix(ctx)
		if err != nil {
			return err
		}

		cmd, err := exec.LookPath(k)
		ctx.extstatus[k] = External{
			cmd: cmd,
			err: err,
		}
		if err == nil {
			ctx.msg.Infof("%s: Found %q\n", k, cmd)
			continue
		}
		ctx.msg.Infof("%s: Missing\n", k)
		extmissing = true
		missing = append(missing, k)
	}

	if extmissing {
		err = fmt.Errorf("missing external(s): %v", missing)
	}
	return err
}

func (ctx *Context) checkRepository() error {
	var err error
	if !path_exists(ctx.initfile) {
		fini, err := os.Create(ctx.initfile)
		if err != nil {
			return err
		}
		defer fini.Close()
		_, err = fini.WriteString(time.Now().Format(time.RFC3339) + "\n")
		if err != nil {
			return err
		}
		err = fini.Sync()
		if err != nil {
			return err
		}
		return fini.Close()
	}
	err = ctx.checkUpdates()
	return err
}

func (ctx *Context) writeYumConf(w io.Writer) error {
	var err error
	const tmpl = `
[main]
#CONFVERSION 0001
cachedir=/var/cache/yum
debuglevel=2
logfile=/var/log/yum.log
pkgpolicy=newest
distroverpkg=redhat-release
tolerant=1
exactarch=1
obsoletes=1
plugins=1
gpgcheck=0
installroot=%s
reposdir=/etc/yum.repos.d
`
	_, err = fmt.Fprintf(w, tmpl, ctx.siteroot)
	return err
}

func (ctx *Context) writeYumRepo(w io.Writer, data map[string]string) error {
	var err error
	const tmpl = `
[%s]
#REPOVERSION 0001
name=%s
baseurl=%s
enabled=1
`
	_, err = fmt.Fprintf(w, tmpl,
		data["name"],
		data["name"],
		data["url"],
	)
	return err
}

// checkUpdates checks whether packages could be updated in the repository
func (ctx *Context) checkUpdates() error {
	var err error
	return err
}

// install performs the whole download/install procedure (eq. yum install)
func (ctx *Context) install(project, version, cmtconfig string) error {
	var err error
	ctx.msg.Infof("Installing %s/%s/%s\n", project, version, cmtconfig)
	return err
}

// InstallRPM installs a RPM by name
func (ctx *Context) InstallRPM(name, version, release string, forceInstall, update bool) error {
	var err error
	pkg, err := ctx.yum.FindLatestMatchingName(name, version, release)
	if err != nil {
		return err
	}
	err = ctx.InstallPackage(pkg, forceInstall, update)
	return err
}

// InstallPackage installs a specific RPM, checking if not already installed
func (ctx *Context) InstallPackage(pkg *yum.Package, forceInstall, update bool) error {
	var err error
	return err
}

// ListPackages lists all packages satisfying pattern (a regexp)
func (ctx *Context) ListPackages(name, version, release string) error {
	var err error
	total := 0
	pkgs, err := ctx.yum.ListPackages(name, version, release)
	if err != nil {
		return err
	}

	for _, pkg := range pkgs {
		fmt.Printf("%s\n", pkg.RpmName())
		total += 1
	}
	ctx.msg.Infof("Total matching: %d\n", total)
	return err
}

// rpm wraps the invocation of the rpm command
func (ctx *Context) rpm(args []string) error {
	install_mode := false
	query_mode := false
	for _, arg := range args {
		if len(arg) < 2 {
			continue
		}
		if arg[:2] == "-i" || arg[:2] == "-U" {
			install_mode = true
			continue
		}
		if arg[:2] == "-q" {
			query_mode = true
		}
	}

	rpmargs := []string{"--dbpath", ctx.dbpath}
	if !query_mode && install_mode {
		rpmargs = append(rpmargs, "--prefix", ctx.siteroot)
	}
	rpmargs = append(rpmargs, args...)

	ctx.msg.Infof("RPM command: rpm %v\n", rpmargs)
	cmd := exec.Command("rpm", rpmargs...)
	out, err := cmd.CombinedOutput()
	ctx.msg.Debugf(string(out))
	return err
}

// EOF
<|endoftext|>


<commit_before>package picoweb

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"github.com/googollee/go-socket.io"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"

	mgo "gopkg.in/mgo.v2"
)

type Context struct {
	w         http.ResponseWriter
	r         *http.Request
	params    map[string]string
	SessionId string
}

func (c *Context) Body() ([]byte, error) {
	bts, err := ioutil.ReadAll(c.r.Body)
	return bts, err
}

func (c *Context) Bytes() []byte {
	bts, _ := ioutil.ReadAll(c.r.Body)
	return bts
}

func (c *Context) Query(key string) string {
	return c.r.URL.Query().Get(key)
}

func (c *Context) QueryInt(key string) (int, error) {
	v := c.Query(key)
	if len(v) == 0 {
		return 0, errors.New("key not found in path")
	}
	i, err := strconv.Atoi(v)
	if err != nil {
		return 0, errors.New("Could not parse as Int")
	}
	return i, nil
}

func (c *Context) QueryBool(key string) (bool, error) {
	v := c.Query(key)
	if len(v) == 0 {
		return false, errors.New("key not found in path")
	}
	b, err := strconv.ParseBool(key)
	if err != nil {
		return false, errors.New("Could not parse as Bool")
	}
	return b, nil
}

func (c *Context) Form(key string) string {
	return c.r.FormValue(key)
}

func (c *Context) Method() string {
	return c.r.Method
}

func (c *Context) Header(key string) string {
	return c.r.Header.Get(key)
}

func (c *Context) RemoteIP() string {
	return c.r.RemoteAddr
}

func (c *Context) R() *http.Request {
	return c.r
}

func (c *Context) BasicAuth() (string, string, bool) {
	return c.r.BasicAuth()
}

func (c *Context) SetHeader(key string, value string) {
	c.w.Header().Set(key, value)
}

func (c *Context) File(filePath string, mimeType string) {
	c.w.Header().Set("content-type", mimeType)
	http.ServeFile(c.w, c.r, filePath)
}

func (c *Context) FileHTML(filePath string) {
	c.w.Header().Set("content-type", "text/html; charset=utf-8")
	http.ServeFile(c.w, c.r, filePath)
}

func (c *Context) String(str string) {
	fmt.Fprint(c, str)
}

func (c *Context) Status(statusCode int) {
	c.w.WriteHeader(statusCode)
}

func (c *Context) Json(data interface{}) {
	jsoned, _ := json.Marshal(data)
	c.ResponseHeader().Add("content-type", "application/json")
	fmt.Fprint(c, string(jsoned))
}

func (c *Context) View(filePath string, data interface{}) {
	tmpl, err := template.ParseFiles(filePath)
	if err != nil {
		fmt.Fprint(c, err.Error())
		return
	}
	err = tmpl.Execute(c.w, data)
}

func (c *Context) Params(name string) string {
	v, _ := c.params[name]
	return v
}

func (c *Context) ResponseHeader() http.Header {
	return c.w.Header()
}

func (c *Context) WriteHeader(n int) {
	c.w.WriteHeader(n)
}

func (c *Context) Write(b []byte) (int, error) {
	return c.w.Write(b)
}

func (c *Context) SetCookie(name, value string, expireIn time.Duration) {
	cookie := &http.Cookie{
		Name:     name,
		Value:    value,
		MaxAge:   0,
		HttpOnly: true,
		Secure:   false,
		Expires:  time.Now().Add(expireIn),
		Path:     "/",
		Raw:      value,
		Unparsed: []string{value},
	}
	http.SetCookie(c.w, cookie)
}

func (c *Context) GetCookie(name string) string {
	cookie, err := c.r.Cookie(name)
	if err != nil {
		return ""
	}

	val := cookie.Value
	if len(val) == 0 {
		for _, ck := range c.r.Cookies() {
			if ck.Name == name {
				return ck.Value
			}
		}
	}
	return ""
}

func (c *Context) Mongo() (*mgo.Session, error) {
	return getSession()
}

func (c *Context) Upgrade() (*websocket.Conn, error) {
	conn, err := upgrader.Upgrade(c.w, c.r, nil)
	return conn, err
}

func (c *Context) RemoveCookie(name string) {
	c.SetCookie(name, "", -(time.Hour * 36))
}

type Socket struct {
	socketio.Socket
}
<commit_msg>Redirect<commit_after>package picoweb

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"github.com/googollee/go-socket.io"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"

	mgo "gopkg.in/mgo.v2"
)

type Context struct {
	w         http.ResponseWriter
	r         *http.Request
	params    map[string]string
	SessionId string
}

func (c *Context) Body() ([]byte, error) {
	bts, err := ioutil.ReadAll(c.r.Body)
	return bts, err
}

func (c *Context) Bytes() []byte {
	bts, _ := ioutil.ReadAll(c.r.Body)
	return bts
}

func (c *Context) Query(key string) string {
	return c.r.URL.Query().Get(key)
}

func (c *Context) QueryInt(key string) (int, error) {
	v := c.Query(key)
	if len(v) == 0 {
		return 0, errors.New("key not found in path")
	}
	i, err := strconv.Atoi(v)
	if err != nil {
		return 0, errors.New("Could not parse as Int")
	}
	return i, nil
}

func (c *Context) QueryBool(key string) (bool, error) {
	v := c.Query(key)
	if len(v) == 0 {
		return false, errors.New("key not found in path")
	}
	b, err := strconv.ParseBool(key)
	if err != nil {
		return false, errors.New("Could not parse as Bool")
	}
	return b, nil
}

func (c *Context) Form(key string) string {
	return c.r.FormValue(key)
}

func (c *Context) Method() string {
	return c.r.Method
}

func (c *Context) Header(key string) string {
	return c.r.Header.Get(key)
}

func (c *Context) RemoteIP() string {
	return c.r.RemoteAddr
}

func (c *Context) R() *http.Request {
	return c.r
}

func (c *Context) BasicAuth() (string, string, bool) {
	return c.r.BasicAuth()
}

func (c *Context) SetHeader(key string, value string) {
	c.w.Header().Set(key, value)
}

func (c *Context) File(filePath string, mimeType string) {
	c.w.Header().Set("content-type", mimeType)
	http.ServeFile(c.w, c.r, filePath)
}

func (c *Context) FileHTML(filePath string) {
	c.w.Header().Set("content-type", "text/html; charset=utf-8")
	http.ServeFile(c.w, c.r, filePath)
}

func (c *Context) String(str string) {
	fmt.Fprint(c, str)
}

func (c *Context) Status(statusCode int) {
	c.w.WriteHeader(statusCode)
}

func (c *Context) Json(data interface{}) {
	jsoned, _ := json.Marshal(data)
	c.ResponseHeader().Add("content-type", "application/json")
	fmt.Fprint(c, string(jsoned))
}

func (c *Context) View(filePath string, data interface{}) {
	tmpl, err := template.ParseFiles(filePath)
	if err != nil {
		fmt.Fprint(c, err.Error())
		return
	}
	err = tmpl.Execute(c.w, data)
}

func (c *Context) Params(name string) string {
	v, _ := c.params[name]
	return v
}

func (c *Context) ResponseHeader() http.Header {
	return c.w.Header()
}

func (c *Context) WriteHeader(n int) {
	c.w.WriteHeader(n)
}

func (c *Context) Write(b []byte) (int, error) {
	return c.w.Write(b)
}

func (c *Context) SetCookie(name, value string, expireIn time.Duration) {
	cookie := &http.Cookie{
		Name:     name,
		Value:    value,
		MaxAge:   0,
		HttpOnly: true,
		Secure:   false,
		Expires:  time.Now().Add(expireIn),
		Path:     "/",
		Raw:      value,
		Unparsed: []string{value},
	}
	http.SetCookie(c.w, cookie)
}

func (c *Context) GetCookie(name string) string {
	cookie, err := c.r.Cookie(name)
	if err != nil {
		return ""
	}

	val := cookie.Value
	if len(val) == 0 {
		for _, ck := range c.r.Cookies() {
			if ck.Name == name {
				return ck.Value
			}
		}
	}
	return ""
}

func (c *Context) Mongo() (*mgo.Session, error) {
	return getSession()
}

func (c *Context) Upgrade() (*websocket.Conn, error) {
	conn, err := upgrader.Upgrade(c.w, c.r, nil)
	return conn, err
}

func (c *Context) RemoveCookie(name string) {
	c.SetCookie(name, "", -(time.Hour * 36))
}

func (c *Context) Redirect(url string, code int) {
	http.Redirect(c.w, c.r, url, code)
}

type Socket struct {
	socketio.Socket
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/mattn/anko/vm"
	"reflect"
	"regexp"
)

type Context struct {
	Env        *vm.Env
	Build      *Build
	Properties []string
	Error      error
}

func NewContext(build *Build, object Object) (*Context, error) {
	context := &Context{
		Env:   vm.NewEnv(),
		Build: build,
	}
	properties, err := context.SetProperties(object)
	if err != nil {
		return nil, err
	}
	context.Properties = properties
	return context, nil
}

func (context *Context) SetProperty(name string, value interface{}) {
	context.Env.Define(name, value)
}

func (context *Context) SetProperties(object Object) ([]string, error) {
	properties := object.Fields()
	todo, _ := NewObject(object)
	length := len(todo)
	list := make([]string, len(todo)+1)
	for length < len(list) && len(todo) > 0 {
		list = todo.Fields()
		for _, field := range list {
			value := todo[field]
			str, ok := value.(string)
			if ok {
				replaced, err := context.ReplaceProperties(str)
				if err != nil {
					continue
				} else {
					context.SetProperty(field, replaced)
					delete(todo, field)
				}
			} else {
				context.SetProperty(field, value)
				delete(todo, field)
			}
		}
		length = len(todo)
	}
	return properties, nil
}

func (context *Context) GetProperty(name string) (interface{}, error) {
	value, err := context.Env.Get(name)
	if err != nil {
		return nil, err
	}
	return value.Interface(), nil
}

func (context *Context) replaceProperty(expression string) string {
	name := expression[2 : len(expression)-1]
	value, err := context.GetProperty(name)
	context.Error = err
	return reflect.ValueOf(value).String()
}

func (context *Context) ReplaceProperties(command string) (string, error) {
	r := regexp.MustCompile("#{.*?}")
	replaced := r.ReplaceAllStringFunc(command, context.replaceProperty)
	err := context.Error
	context.Error = nil
	return replaced, err
}
<commit_msg>Refactoring<commit_after>package main

import (
	"github.com/mattn/anko/vm"
	"reflect"
	"regexp"
)

type Context struct {
	Env        *vm.Env
	Build      *Build
	Properties []string
	Error      error
}

func NewContext(build *Build, object Object) (*Context, error) {
	context := &Context{
		Env:        vm.NewEnv(),
		Build:      build,
		Properties: object.Fields(),
	}
	err := context.SetProperties(object)
	if err != nil {
		return nil, err
	}
	return context, nil
}

func (context *Context) SetProperty(name string, value interface{}) {
	context.Env.Define(name, value)
}

func (context *Context) SetProperties(object Object) error {
	todo, _ := NewObject(object)
	length := len(todo)
	list := make([]string, len(todo)+1)
	var err error
	for length < len(list) && len(todo) > 0 {
		list = todo.Fields()
		for _, field := range list {
			value := todo[field]
			str, ok := value.(string)
			if ok {
				replaced, err := context.ReplaceProperties(str)
				if err != nil {
					continue
				} else {
					context.SetProperty(field, replaced)
					delete(todo, field)
				}
			} else {
				context.SetProperty(field, value)
				delete(todo, field)
			}
		}
		length = len(todo)
	}
	if len(todo) > 0 {
		return err
	}
	return nil
}

func (context *Context) GetProperty(name string) (interface{}, error) {
	value, err := context.Env.Get(name)
	if err != nil {
		return nil, err
	}
	return value.Interface(), nil
}

func (context *Context) replaceProperty(expression string) string {
	name := expression[2 : len(expression)-1]
	value, err := context.GetProperty(name)
	context.Error = err
	return reflect.ValueOf(value).String()
}

func (context *Context) ReplaceProperties(command string) (string, error) {
	r := regexp.MustCompile("#{.*?}")
	replaced := r.ReplaceAllStringFunc(command, context.replaceProperty)
	err := context.Error
	context.Error = nil
	return replaced, err
}
<|endoftext|>


<commit_before>package immortal

import (
	"fmt"
	"os"
	"sync/atomic"
	"time"
)

type controlPid struct {
	ch chan int
}

type controlOnce struct{}

type controlSignal struct {
	signal os.Signal
}

type controlKill struct{}

func (d *Daemon) Pid() int {
	ch := make(chan int, 1)
	d.ctrl <- controlPid{ch}
	return <-ch
}

func (d *Daemon) control(p *process) {
	for {
		select {
		case err := <-p.errch:
			fmt.Printf("d.process.sTime = %+v\n", time.Since(p.sTime))
			println(p.eTime.Sub(p.sTime))
			// lock_once defaults to 0, 1 to run only once/down (don't restart)
			atomic.StoreUint32(&d.lock, d.lock_once)
			fmt.Printf("err = %+v\n", err)
			d.done <- err
			return
		case ctrl := <-d.ctrl:
			switch c := ctrl.(type) {
			case controlPid:
				c.ch <- p.Pid()
				println(d.lock, d.lock_once)
			case controlOnce:
				d.lock_once = 1
			case controlSignal:
				p.Signal(c.signal)
			case controlKill:
				p.Kill()
			}
		}
	}
}
<commit_msg>	modified:   control.go<commit_after>package immortal

import (
	"fmt"
	"os"
	"sync/atomic"
	"time"
)

type controlPid struct {
	ch chan int
}

type controlOnce struct{}

type controlSignal struct {
	signal os.Signal
}

type controlKill struct{}

func (d *Daemon) Pid() (pid int) {
	ch := make(chan int, 1)
	d.ctrl <- controlPid{ch}
	pid = <-ch
	close(ch)
	return
}

func (d *Daemon) control(p *process) {
	for {
		select {
		case err := <-p.errch:
			fmt.Printf("d.process.sTime = %+v\n", time.Since(p.sTime))
			println(p.eTime.Sub(p.sTime))
			// lock_once defaults to 0, 1 to run only once/down (don't restart)
			atomic.StoreUint32(&d.lock, d.lock_once)
			fmt.Printf("err = %+v\n", err)
			d.done <- err
			return
		case ctrl := <-d.ctrl:
			switch c := ctrl.(type) {
			case controlPid:
				c.ch <- p.Pid()
				println(d.lock, d.lock_once)
			case controlOnce:
				d.lock_once = 1
			case controlSignal:
				p.Signal(c.signal)
			case controlKill:
				p.Kill()
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"os"

	"github.com/coreos/fleet/job"
)

var (
	cmdStartUnit = &Command{
		Name:    "start",
		Summary: "Instruct systemd to start one or more units in the cluster, first submitting and loading if necessary.",
		Usage:   "[--no-block|--block-attempts=N] UNIT...",
		Description: `Start one or many units on the cluster. Select units to start by glob matching
for units in the current working directory or matching names of previously
submitted units.

For units which are not global, start operations are performed synchronously,
which means fleetctl will block until it detects that the unit(s) have
transitioned to a started state. This behaviour can be configured with the
respective --block-attempts and --no-block options. Start operations on global
units are always non-blocking.

Start a single unit:
	fleetctl start foo.service

Start an entire directory of units with glob matching:
	fleetctl start myservice/*

You may filter suitable hosts based on metadata provided by the machine.
Machine metadata is located in the fleet configuration file.`,
		Run: runStartUnit,
	}
)

func init() {
	cmdStartUnit.Flags.BoolVar(&sharedFlags.Sign, "sign", false, "DEPRECATED - this option cannot be used")
	cmdStartUnit.Flags.IntVar(&sharedFlags.BlockAttempts, "block-attempts", 0, "Wait until the units are launched, performing up to N attempts before giving up. A value of 0 indicates no limit. Does not apply to global units.")
	cmdStartUnit.Flags.BoolVar(&sharedFlags.NoBlock, "no-block", false, "Do not wait until the units have launched before exiting. Always the case for global units.")
}

func runStartUnit(args []string) (exit int) {
	if len(args) == 0 {
		stderr("No units given")
		return 0
	}

	if err := lazyCreateUnits(args); err != nil {
		stderr("Error creating units: %v", err)
		return 1
	}

	triggered, err := lazyStartUnits(args)
	if err != nil {
		stderr("Error starting units: %v", err)
		return 1
	}

	var starting []string
	for _, u := range triggered {
		if suToGlobal(*u) {
			stdout("Triggered global unit %s start", u.Name)
		} else {
			starting = append(starting, u.Name)
		}
	}

	exit = tryWaitForUnitStates(starting, "start", job.JobStateLaunched, getBlockAttempts(), os.Stdout)

	return
}
<commit_msg>fleetctl: add an option -replace to the start command<commit_after>// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"os"

	"github.com/coreos/fleet/job"
)

var (
	cmdStartUnit = &Command{
		Name:    "start",
		Summary: "Instruct systemd to start one or more units in the cluster, first submitting and loading if necessary.",
		Usage:   "[--no-block|--block-attempts=N] UNIT...",
		Description: `Start one or many units on the cluster. Select units to start by glob matching
for units in the current working directory or matching names of previously
submitted units.

For units which are not global, start operations are performed synchronously,
which means fleetctl will block until it detects that the unit(s) have
transitioned to a started state. This behaviour can be configured with the
respective --block-attempts and --no-block options. Start operations on global
units are always non-blocking.

Start a single unit:
	fleetctl start foo.service

Start an entire directory of units with glob matching:
	fleetctl start myservice/*

You may filter suitable hosts based on metadata provided by the machine.
Machine metadata is located in the fleet configuration file.`,
		Run: runStartUnit,
	}
)

func init() {
	cmdStartUnit.Flags.BoolVar(&sharedFlags.Sign, "sign", false, "DEPRECATED - this option cannot be used")
	cmdStartUnit.Flags.IntVar(&sharedFlags.BlockAttempts, "block-attempts", 0, "Wait until the units are launched, performing up to N attempts before giving up. A value of 0 indicates no limit. Does not apply to global units.")
	cmdStartUnit.Flags.BoolVar(&sharedFlags.NoBlock, "no-block", false, "Do not wait until the units have launched before exiting. Always the case for global units.")
	cmdStartUnit.Flags.BoolVar(&sharedFlags.Replace, "replace", false, "Replace the already started units in the cluster with new versions.")
}

func runStartUnit(args []string) (exit int) {
	if len(args) == 0 {
		stderr("No units given")
		return 0
	}

	if err := lazyCreateUnits(args); err != nil {
		stderr("Error creating units: %v", err)
		return 1
	}

	triggered, err := lazyStartUnits(args)
	if err != nil {
		stderr("Error starting units: %v", err)
		return 1
	}

	var starting []string
	for _, u := range triggered {
		if suToGlobal(*u) {
			stdout("Triggered global unit %s start", u.Name)
		} else {
			starting = append(starting, u.Name)
		}
	}

	exit = tryWaitForUnitStates(starting, "start", job.JobStateLaunched, getBlockAttempts(), os.Stdout)

	return
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"strconv"
	"time"

	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/ciao-project/ciao/ciao-storage"
	"github.com/ciao-project/ciao/openstack/block"
	"github.com/ciao-project/ciao/payloads"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
)

// Implement the Block Service interface
func (c *controller) GetAbsoluteLimits(tenant string) (block.AbsoluteLimits, error) {
	err := c.confirmTenant(tenant)
	if err != nil {
		return block.AbsoluteLimits{}, err
	}

	return block.AbsoluteLimits{}, nil
}

// CreateVolume will create a new block device and store it in the datastore.
func (c *controller) CreateVolume(tenant string, req block.RequestedVolume) (block.Volume, error) {
	err := c.confirmTenant(tenant)
	if err != nil {
		return block.Volume{}, err
	}

	var bd storage.BlockDevice

	// no limits checking for now.
	if req.ImageRef != "" {
		// create bootable volume
		bd, err = c.CreateBlockDeviceFromSnapshot(req.ImageRef, "ciao-image")
		bd.Bootable = true
	} else if req.SourceVolID != "" {
		// copy existing volume
		bd, err = c.CopyBlockDevice(req.SourceVolID)
	} else {
		// create empty volume
		bd, err = c.CreateBlockDevice("", "", req.Size)
	}

	if err == nil && req.Size > bd.Size {
		bd.Size, err = c.Resize(bd.ID, req.Size)
	}

	if err != nil {
		return block.Volume{}, err
	}

	// store block device data in datastore
	// TBD - do we really need to do this, or can we associate
	// the block device data with the device itself?
	// you should modify BlockData to include a "bootable" flag.
	data := types.BlockData{
		BlockDevice: bd,
		CreateTime:  time.Now(),
		TenantID:    tenant,
		State:       types.Available,
		Name:        req.Name,
		Description: req.Description,
	}

	// It's best to make the quota request here as we don't know the volume
	// size earlier. If the ceph cluster is full then it might error out
	// earlier.
	res := <-c.qs.Consume(tenant,
		payloads.RequestedResource{Type: payloads.Volume, Value: 1},
		payloads.RequestedResource{Type: payloads.SharedDiskGiB, Value: bd.Size})

	if !res.Allowed() {
		c.DeleteBlockDevice(bd.ID)
		c.qs.Release(tenant, res.Resources()...)
		return block.Volume{}, block.ErrQuota
	}

	err = c.ds.AddBlockDevice(data)
	if err != nil {
		c.DeleteBlockDevice(bd.ID)
		c.qs.Release(tenant, res.Resources()...)
		return block.Volume{}, err
	}

	// convert our volume info into the openstack desired format.
	return block.Volume{
		Status:      block.Available,
		UserID:      tenant,
		Attachments: make([]block.Attachment, 0),
		Links:       make([]block.Link, 0),
		CreatedAt:   &data.CreateTime,
		ID:          bd.ID,
		Size:        data.Size,
		Bootable:    strconv.FormatBool(data.Bootable),
	}, nil
}

func (c *controller) DeleteVolume(tenant string, volume string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the block device is available.
	if info.State != types.Available {
		return block.ErrVolumeNotAvailable
	}

	// remove the block data from our datastore.
	err = c.ds.DeleteBlockDevice(volume)
	if err != nil {
		return err
	}

	// tell the underlying storage media to remove.
	err = c.DeleteBlockDevice(volume)
	if err != nil {
		return err
	}

	// release quota associated with this volume
	c.qs.Release(info.TenantID,
		payloads.RequestedResource{Type: payloads.Volume, Value: 1},
		payloads.RequestedResource{Type: payloads.SharedDiskGiB, Value: info.Size})

	return nil
}

func (c *controller) AttachVolume(tenant string, volume string, instance string, mountpoint string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is available.
	if info.State != types.Available {
		return block.ErrVolumeNotAvailable
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the instance is owned by the tenant.
	i, err := c.ds.GetTenantInstance(tenant, instance)
	if err != nil {
		return block.ErrInstanceNotFound
	}

	// update volume state to attaching
	info.State = types.Attaching

	err = c.ds.UpdateBlockDevice(info)
	if err != nil {
		return err
	}

	// create an attachment object
	a := payloads.StorageResource{
		ID:        info.ID,
		Ephemeral: false,
		Bootable:  false,
	}
	_, err = c.ds.CreateStorageAttachment(i.ID, a)
	if err != nil {
		info.State = types.Available
		dsErr := c.ds.UpdateBlockDevice(info)
		if dsErr != nil {
			glog.Error(dsErr)
		}
		return err
	}

	// send command to attach volume.
	err = c.client.attachVolume(volume, instance, i.NodeID)
	if err != nil {
		info.State = types.Available
		dsErr := c.ds.UpdateBlockDevice(info)
		if dsErr != nil {
			glog.Error(dsErr)
		}
		return err
	}

	return nil
}

func (c *controller) DetachVolume(tenant string, volume string, attachment string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// we don't support detaching by attachment ID yet.
	if attachment != "" {
		return errors.New("Detaching by attachment ID not implemented")
	}

	// get attachment info
	attachments, err := c.ds.GetVolumeAttachments(volume)
	if err != nil {
		return err
	}

	if len(attachments) == 0 {
		return block.ErrVolumeNotAttached
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the block device is in use
	if info.State != types.InUse {
		return block.ErrVolumeNotAttached
	}

	// we cannot detach a boot device - these aren't
	// like regular attachments and shouldn't be treated
	// as such.
	for _, a := range attachments {
		if a.Boot == true {
			return block.ErrVolumeNotAttached
		}
	}

	// update volume state to detaching
	info.State = types.Detaching

	err = c.ds.UpdateBlockDevice(info)
	if err != nil {
		return err
	}

	var retval error

	// detach everything for this volume
	for _, a := range attachments {
		// get instance info
		i, err := c.ds.GetTenantInstance(tenant, a.InstanceID)
		if err != nil {
			glog.Error(block.ErrInstanceNotFound)
			// keep going
			retval = err
			continue
		}

		// send command to attach volume.
		err = c.client.detachVolume(a.BlockID, a.InstanceID, i.NodeID)
		if err != nil {
			retval = err
			glog.Errorf("Can't detach volume %s from instance %s\n", a.BlockID, a.InstanceID)
		}
	}

	return retval
}

func (c *controller) ListVolumes(tenant string) ([]block.ListVolume, error) {
	var vols []block.ListVolume

	err := c.confirmTenant(tenant)
	if err != nil {
		return vols, err
	}

	data, err := c.ds.GetBlockDevices(tenant)
	if err != nil {
		return vols, err
	}

	for _, bd := range data {
		if bd.Internal {
			continue
		}

		// TBD create links
		vol := block.ListVolume{
			ID: bd.ID,
		}
		vols = append(vols, vol)
	}

	return vols, nil
}

func (c *controller) ListVolumesDetail(tenant string) ([]block.VolumeDetail, error) {
	vols := []block.VolumeDetail{}

	err := c.confirmTenant(tenant)
	if err != nil {
		return vols, err
	}

	devs, err := c.ds.GetBlockDevices(tenant)
	if err != nil {
		return vols, err
	}

	for i := range devs {
		data := &devs[i]

		if data.Internal {
			continue
		}

		var vol block.VolumeDetail

		vol.ID = data.ID
		vol.Size = data.Size
		vol.OSVolTenantAttr = data.TenantID
		vol.Bootable = strconv.FormatBool(data.Bootable)
		vol.Name = data.Name
		vol.Description = data.Description

		switch data.State {
		case types.Attaching:
			vol.Status = block.Attaching
		case types.InUse:
			vol.Status = block.InUse
		case types.Available:
			vol.Status = block.Available
		default:
			vol.Status = block.VolumeStatus(data.State)
		}

		vols = append(vols, vol)
	}

	return vols, nil
}

func (c *controller) ShowVolumeDetails(tenant string, volume string) (block.VolumeDetail, error) {
	var vol block.VolumeDetail

	err := c.confirmTenant(tenant)
	if err != nil {
		return vol, err
	}

	data, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return vol, err
	}

	if data.TenantID != tenant {
		return vol, block.ErrVolumeOwner
	}

	vol.ID = data.ID
	vol.Size = data.Size
	vol.OSVolTenantAttr = data.TenantID
	vol.CreatedAt = &data.CreateTime
	vol.Bootable = strconv.FormatBool(data.Bootable)
	vol.Name = data.Name
	vol.Description = data.Description

	switch data.State {
	case types.Attaching:
		vol.Status = block.Attaching
	case types.InUse:
		vol.Status = block.InUse
	case types.Available:
		vol.Status = block.Available
	default:
		vol.Status = block.VolumeStatus(data.State)
	}

	return vol, nil
}

func (c *controller) createVolumeRoutes(r *mux.Router) error {
	config := block.APIConfig{VolService: c}

	block.Routes(config, r)

	return nil
}
<commit_msg>ciao-controller: Only update volume to detaching before client send<commit_after>// Copyright (c) 2016 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"strconv"
	"time"

	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/ciao-project/ciao/ciao-storage"
	"github.com/ciao-project/ciao/openstack/block"
	"github.com/ciao-project/ciao/payloads"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
)

// Implement the Block Service interface
func (c *controller) GetAbsoluteLimits(tenant string) (block.AbsoluteLimits, error) {
	err := c.confirmTenant(tenant)
	if err != nil {
		return block.AbsoluteLimits{}, err
	}

	return block.AbsoluteLimits{}, nil
}

// CreateVolume will create a new block device and store it in the datastore.
func (c *controller) CreateVolume(tenant string, req block.RequestedVolume) (block.Volume, error) {
	err := c.confirmTenant(tenant)
	if err != nil {
		return block.Volume{}, err
	}

	var bd storage.BlockDevice

	// no limits checking for now.
	if req.ImageRef != "" {
		// create bootable volume
		bd, err = c.CreateBlockDeviceFromSnapshot(req.ImageRef, "ciao-image")
		bd.Bootable = true
	} else if req.SourceVolID != "" {
		// copy existing volume
		bd, err = c.CopyBlockDevice(req.SourceVolID)
	} else {
		// create empty volume
		bd, err = c.CreateBlockDevice("", "", req.Size)
	}

	if err == nil && req.Size > bd.Size {
		bd.Size, err = c.Resize(bd.ID, req.Size)
	}

	if err != nil {
		return block.Volume{}, err
	}

	// store block device data in datastore
	// TBD - do we really need to do this, or can we associate
	// the block device data with the device itself?
	// you should modify BlockData to include a "bootable" flag.
	data := types.BlockData{
		BlockDevice: bd,
		CreateTime:  time.Now(),
		TenantID:    tenant,
		State:       types.Available,
		Name:        req.Name,
		Description: req.Description,
	}

	// It's best to make the quota request here as we don't know the volume
	// size earlier. If the ceph cluster is full then it might error out
	// earlier.
	res := <-c.qs.Consume(tenant,
		payloads.RequestedResource{Type: payloads.Volume, Value: 1},
		payloads.RequestedResource{Type: payloads.SharedDiskGiB, Value: bd.Size})

	if !res.Allowed() {
		c.DeleteBlockDevice(bd.ID)
		c.qs.Release(tenant, res.Resources()...)
		return block.Volume{}, block.ErrQuota
	}

	err = c.ds.AddBlockDevice(data)
	if err != nil {
		c.DeleteBlockDevice(bd.ID)
		c.qs.Release(tenant, res.Resources()...)
		return block.Volume{}, err
	}

	// convert our volume info into the openstack desired format.
	return block.Volume{
		Status:      block.Available,
		UserID:      tenant,
		Attachments: make([]block.Attachment, 0),
		Links:       make([]block.Link, 0),
		CreatedAt:   &data.CreateTime,
		ID:          bd.ID,
		Size:        data.Size,
		Bootable:    strconv.FormatBool(data.Bootable),
	}, nil
}

func (c *controller) DeleteVolume(tenant string, volume string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the block device is available.
	if info.State != types.Available {
		return block.ErrVolumeNotAvailable
	}

	// remove the block data from our datastore.
	err = c.ds.DeleteBlockDevice(volume)
	if err != nil {
		return err
	}

	// tell the underlying storage media to remove.
	err = c.DeleteBlockDevice(volume)
	if err != nil {
		return err
	}

	// release quota associated with this volume
	c.qs.Release(info.TenantID,
		payloads.RequestedResource{Type: payloads.Volume, Value: 1},
		payloads.RequestedResource{Type: payloads.SharedDiskGiB, Value: info.Size})

	return nil
}

func (c *controller) AttachVolume(tenant string, volume string, instance string, mountpoint string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is available.
	if info.State != types.Available {
		return block.ErrVolumeNotAvailable
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the instance is owned by the tenant.
	i, err := c.ds.GetTenantInstance(tenant, instance)
	if err != nil {
		return block.ErrInstanceNotFound
	}

	// update volume state to attaching
	info.State = types.Attaching

	err = c.ds.UpdateBlockDevice(info)
	if err != nil {
		return err
	}

	// create an attachment object
	a := payloads.StorageResource{
		ID:        info.ID,
		Ephemeral: false,
		Bootable:  false,
	}
	_, err = c.ds.CreateStorageAttachment(i.ID, a)
	if err != nil {
		info.State = types.Available
		dsErr := c.ds.UpdateBlockDevice(info)
		if dsErr != nil {
			glog.Error(dsErr)
		}
		return err
	}

	// send command to attach volume.
	err = c.client.attachVolume(volume, instance, i.NodeID)
	if err != nil {
		info.State = types.Available
		dsErr := c.ds.UpdateBlockDevice(info)
		if dsErr != nil {
			glog.Error(dsErr)
		}
		return err
	}

	return nil
}

func (c *controller) DetachVolume(tenant string, volume string, attachment string) error {
	err := c.confirmTenant(tenant)
	if err != nil {
		return err
	}

	// we don't support detaching by attachment ID yet.
	if attachment != "" {
		return errors.New("Detaching by attachment ID not implemented")
	}

	// get attachment info
	attachments, err := c.ds.GetVolumeAttachments(volume)
	if err != nil {
		return err
	}

	if len(attachments) == 0 {
		return block.ErrVolumeNotAttached
	}

	// get the block device information
	info, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return err
	}

	// check that the block device is owned by the tenant.
	if info.TenantID != tenant {
		return block.ErrVolumeOwner
	}

	// check that the block device is in use
	if info.State != types.InUse {
		return block.ErrVolumeNotAttached
	}

	// we cannot detach a boot device - these aren't
	// like regular attachments and shouldn't be treated
	// as such.
	for _, a := range attachments {
		if a.Boot == true {
			return block.ErrVolumeNotAttached
		}
	}

	var retval error

	// detach everything for this volume
	for _, a := range attachments {
		// get instance info
		i, err := c.ds.GetTenantInstance(tenant, a.InstanceID)
		if err != nil {
			glog.Error(block.ErrInstanceNotFound)
			// keep going
			retval = err
			continue
		}

		// update volume state to detaching
		info.State = types.Detaching

		err = c.ds.UpdateBlockDevice(info)
		if err != nil {
			return err
		}

		// send command to attach volume.
		err = c.client.detachVolume(a.BlockID, a.InstanceID, i.NodeID)
		if err != nil {
			retval = err
			glog.Errorf("Can't detach volume %s from instance %s\n", a.BlockID, a.InstanceID)
		}
	}

	return retval
}

func (c *controller) ListVolumes(tenant string) ([]block.ListVolume, error) {
	var vols []block.ListVolume

	err := c.confirmTenant(tenant)
	if err != nil {
		return vols, err
	}

	data, err := c.ds.GetBlockDevices(tenant)
	if err != nil {
		return vols, err
	}

	for _, bd := range data {
		if bd.Internal {
			continue
		}

		// TBD create links
		vol := block.ListVolume{
			ID: bd.ID,
		}
		vols = append(vols, vol)
	}

	return vols, nil
}

func (c *controller) ListVolumesDetail(tenant string) ([]block.VolumeDetail, error) {
	vols := []block.VolumeDetail{}

	err := c.confirmTenant(tenant)
	if err != nil {
		return vols, err
	}

	devs, err := c.ds.GetBlockDevices(tenant)
	if err != nil {
		return vols, err
	}

	for i := range devs {
		data := &devs[i]

		if data.Internal {
			continue
		}

		var vol block.VolumeDetail

		vol.ID = data.ID
		vol.Size = data.Size
		vol.OSVolTenantAttr = data.TenantID
		vol.Bootable = strconv.FormatBool(data.Bootable)
		vol.Name = data.Name
		vol.Description = data.Description

		switch data.State {
		case types.Attaching:
			vol.Status = block.Attaching
		case types.InUse:
			vol.Status = block.InUse
		case types.Available:
			vol.Status = block.Available
		default:
			vol.Status = block.VolumeStatus(data.State)
		}

		vols = append(vols, vol)
	}

	return vols, nil
}

func (c *controller) ShowVolumeDetails(tenant string, volume string) (block.VolumeDetail, error) {
	var vol block.VolumeDetail

	err := c.confirmTenant(tenant)
	if err != nil {
		return vol, err
	}

	data, err := c.ds.GetBlockDevice(volume)
	if err != nil {
		return vol, err
	}

	if data.TenantID != tenant {
		return vol, block.ErrVolumeOwner
	}

	vol.ID = data.ID
	vol.Size = data.Size
	vol.OSVolTenantAttr = data.TenantID
	vol.CreatedAt = &data.CreateTime
	vol.Bootable = strconv.FormatBool(data.Bootable)
	vol.Name = data.Name
	vol.Description = data.Description

	switch data.State {
	case types.Attaching:
		vol.Status = block.Attaching
	case types.InUse:
		vol.Status = block.InUse
	case types.Available:
		vol.Status = block.Available
	default:
		vol.Status = block.VolumeStatus(data.State)
	}

	return vol, nil
}

func (c *controller) createVolumeRoutes(r *mux.Router) error {
	config := block.APIConfig{VolService: c}

	block.Routes(config, r)

	return nil
}
<|endoftext|>


<commit_before>package lib

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/travis-ci/worker/lib/metrics"
	"golang.org/x/net/context"
)

// A BuildScriptGeneratorError is sometimes used by the Generate method on a
// BuildScriptGenerator to return more metadata about an error.
type BuildScriptGeneratorError struct {
	error

	// true when this error can be recovered by retrying later
	Recover bool
}

// A BuildScriptGenerator generates a build script for a given job payload.
type BuildScriptGenerator interface {
	Generate(context.Context, *simplejson.Json) ([]byte, error)
}

type webBuildScriptGenerator struct {
	URL               string
	aptCacheHost      string
	npmCacheHost      string
	paranoid          bool
	fixResolvConf     bool
	fixEtcHosts       bool
	cacheType         string
	cacheFetchTimeout int
	cachePushTimeout  int
	s3CacheOptions    s3BuildCacheOptions
}

type s3BuildCacheOptions struct {
	scheme          string
	region          string
	bucket          string
	accessKeyId     string
	secretAccessKey string
}

// NewBuildScriptGenerator creates a generator backed by an HTTP API.
func NewBuildScriptGenerator(URL string) BuildScriptGenerator {
	cacheFetchTimeout, _ := strconv.Atoi(os.Getenv("TRAVIS_WORKER_BUILD_CACHE_FETCH_TIMEOUT"))
	cachePushTimeout, _ := strconv.Atoi(os.Getenv("TRAVIS_WORKER_BUILD_CACHE_PUSH_TIMEOUT"))

	return &webBuildScriptGenerator{
		URL:               URL,
		aptCacheHost:      os.Getenv("TRAVIS_WORKER_BUILD_APT_CACHE"),
		npmCacheHost:      os.Getenv("TRAVIS_WORKER_BUILD_NPM_CACHE"),
		paranoid:          os.Getenv("TRAVIS_WORKER_BUILD_PARANOID") == "true",
		fixResolvConf:     os.Getenv("TRAVIS_WORKER_BUILD_FIX_RESOLV_CONF") == "true",
		fixEtcHosts:       os.Getenv("TRAVIS_WORKER_BUILD_FIX_ETC_HOSTS") == "true",
		cacheType:         os.Getenv("TRAVIS_WORKER_BUILD_CACHE_TYPE"),
		cacheFetchTimeout: cacheFetchTimeout,
		cachePushTimeout:  cachePushTimeout,
		s3CacheOptions: s3BuildCacheOptions{
			scheme:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_SCHEME"),
			region:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_REGION"),
			bucket:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_BUCKET"),
			accessKeyId:     os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_ACCESS_KEY_ID"),
			secretAccessKey: os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_SECRET_ACCESS_KEY"),
		},
	}
}

func (g *webBuildScriptGenerator) Generate(ctx context.Context, payload *simplejson.Json) ([]byte, error) {
	if g.aptCacheHost != "" {
		payload.SetPath([]string{"hosts", "apt_cache"}, g.aptCacheHost)
	}
	if g.npmCacheHost != "" {
		payload.SetPath([]string{"hosts", "npm_cache"}, g.npmCacheHost)
	}

	payload.Set("paranoid", g.paranoid)
	payload.Set("fix_resolv_conf", g.fixResolvConf)
	payload.Set("fix_etc_hosts", g.fixEtcHosts)
	payload.SetPath([]string{"cache_options", "type"}, g.cacheType)
	payload.SetPath([]string{"cache_options", "fetch_timeout"}, g.cacheFetchTimeout)
	payload.SetPath([]string{"cache_options", "push_timeout"}, g.cachePushTimeout)
	payload.SetPath([]string{"cache_options", "s3", "scheme"}, g.s3CacheOptions.scheme)
	payload.SetPath([]string{"cache_options", "s3", "region"}, g.s3CacheOptions.region)
	payload.SetPath([]string{"cache_options", "s3", "bucket"}, g.s3CacheOptions.bucket)
	payload.SetPath([]string{"cache_options", "s3", "access_key_id"}, g.s3CacheOptions.accessKeyId)
	payload.SetPath([]string{"cache_options", "s3", "secret_access_key"}, g.s3CacheOptions.secretAccessKey)

	b, err := payload.Encode()
	if err != nil {
		return nil, err
	}

	var token string
	u, err := url.Parse(g.URL)
	if err != nil {
		return nil, err
	}
	if u.User != nil {
		token = u.User.Username()
		u.User = nil
	}

	buf := bytes.NewBuffer(b)
	req, err := http.NewRequest("POST", u.String(), buf)
	if err != nil {
		return nil, err
	}
	if token != "" {
		req.Header.Set("Authorization", "token "+token)
	}
	req.Header.Set("User-Agent", fmt.Sprintf("worker-go v=%v rev=%v d=%v", VersionString, RevisionString, GeneratedString))
	req.Header.Set("Content-Type", "application/json")

	startRequest := time.Now()

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	metrics.TimeSince("worker.job.script.api", startRequest)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 500 {
		return nil, BuildScriptGeneratorError{error: fmt.Errorf("server error: %q", string(body)), Recover: true}
	} else if resp.StatusCode >= 400 {
		return nil, BuildScriptGeneratorError{error: fmt.Errorf("client error: %q", string(body)), Recover: false}
	}

	return body, nil
}
<commit_msg>build-script: only send cache options if cache type is set<commit_after>package lib

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/travis-ci/worker/lib/metrics"
	"golang.org/x/net/context"
)

// A BuildScriptGeneratorError is sometimes used by the Generate method on a
// BuildScriptGenerator to return more metadata about an error.
type BuildScriptGeneratorError struct {
	error

	// true when this error can be recovered by retrying later
	Recover bool
}

// A BuildScriptGenerator generates a build script for a given job payload.
type BuildScriptGenerator interface {
	Generate(context.Context, *simplejson.Json) ([]byte, error)
}

type webBuildScriptGenerator struct {
	URL               string
	aptCacheHost      string
	npmCacheHost      string
	paranoid          bool
	fixResolvConf     bool
	fixEtcHosts       bool
	cacheType         string
	cacheFetchTimeout int
	cachePushTimeout  int
	s3CacheOptions    s3BuildCacheOptions
}

type s3BuildCacheOptions struct {
	scheme          string
	region          string
	bucket          string
	accessKeyId     string
	secretAccessKey string
}

// NewBuildScriptGenerator creates a generator backed by an HTTP API.
func NewBuildScriptGenerator(URL string) BuildScriptGenerator {
	cacheFetchTimeout, _ := strconv.Atoi(os.Getenv("TRAVIS_WORKER_BUILD_CACHE_FETCH_TIMEOUT"))
	cachePushTimeout, _ := strconv.Atoi(os.Getenv("TRAVIS_WORKER_BUILD_CACHE_PUSH_TIMEOUT"))

	return &webBuildScriptGenerator{
		URL:               URL,
		aptCacheHost:      os.Getenv("TRAVIS_WORKER_BUILD_APT_CACHE"),
		npmCacheHost:      os.Getenv("TRAVIS_WORKER_BUILD_NPM_CACHE"),
		paranoid:          os.Getenv("TRAVIS_WORKER_BUILD_PARANOID") == "true",
		fixResolvConf:     os.Getenv("TRAVIS_WORKER_BUILD_FIX_RESOLV_CONF") == "true",
		fixEtcHosts:       os.Getenv("TRAVIS_WORKER_BUILD_FIX_ETC_HOSTS") == "true",
		cacheType:         os.Getenv("TRAVIS_WORKER_BUILD_CACHE_TYPE"),
		cacheFetchTimeout: cacheFetchTimeout,
		cachePushTimeout:  cachePushTimeout,
		s3CacheOptions: s3BuildCacheOptions{
			scheme:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_SCHEME"),
			region:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_REGION"),
			bucket:          os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_BUCKET"),
			accessKeyId:     os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_ACCESS_KEY_ID"),
			secretAccessKey: os.Getenv("TRAVIS_WORKER_BUILD_CACHE_S3_SECRET_ACCESS_KEY"),
		},
	}
}

func (g *webBuildScriptGenerator) Generate(ctx context.Context, payload *simplejson.Json) ([]byte, error) {
	if g.aptCacheHost != "" {
		payload.SetPath([]string{"hosts", "apt_cache"}, g.aptCacheHost)
	}
	if g.npmCacheHost != "" {
		payload.SetPath([]string{"hosts", "npm_cache"}, g.npmCacheHost)
	}

	payload.Set("paranoid", g.paranoid)
	payload.Set("fix_resolv_conf", g.fixResolvConf)
	payload.Set("fix_etc_hosts", g.fixEtcHosts)
	if g.cacheType != "" {
		payload.SetPath([]string{"cache_options", "type"}, g.cacheType)
		payload.SetPath([]string{"cache_options", "fetch_timeout"}, g.cacheFetchTimeout)
		payload.SetPath([]string{"cache_options", "push_timeout"}, g.cachePushTimeout)
		payload.SetPath([]string{"cache_options", "s3", "scheme"}, g.s3CacheOptions.scheme)
		payload.SetPath([]string{"cache_options", "s3", "region"}, g.s3CacheOptions.region)
		payload.SetPath([]string{"cache_options", "s3", "bucket"}, g.s3CacheOptions.bucket)
		payload.SetPath([]string{"cache_options", "s3", "access_key_id"}, g.s3CacheOptions.accessKeyId)
		payload.SetPath([]string{"cache_options", "s3", "secret_access_key"}, g.s3CacheOptions.secretAccessKey)
	}

	b, err := payload.Encode()
	if err != nil {
		return nil, err
	}

	var token string
	u, err := url.Parse(g.URL)
	if err != nil {
		return nil, err
	}
	if u.User != nil {
		token = u.User.Username()
		u.User = nil
	}

	buf := bytes.NewBuffer(b)
	req, err := http.NewRequest("POST", u.String(), buf)
	if err != nil {
		return nil, err
	}
	if token != "" {
		req.Header.Set("Authorization", "token "+token)
	}
	req.Header.Set("User-Agent", fmt.Sprintf("worker-go v=%v rev=%v d=%v", VersionString, RevisionString, GeneratedString))
	req.Header.Set("Content-Type", "application/json")

	startRequest := time.Now()

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	metrics.TimeSince("worker.job.script.api", startRequest)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 500 {
		return nil, BuildScriptGeneratorError{error: fmt.Errorf("server error: %q", string(body)), Recover: true}
	} else if resp.StatusCode >= 400 {
		return nil, BuildScriptGeneratorError{error: fmt.Errorf("client error: %q", string(body)), Recover: false}
	}

	return body, nil
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package license

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/admpub/license_gen/lib"
	"github.com/admpub/log"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/webx-top/com"
	"github.com/webx-top/echo"

	"github.com/admpub/nging/v3/application/library/restclient"
)

var (
	trackerURL      = `//www.webx.top/product/script/nging/tracker.js`
	productURL      = `http://www.webx.top/product/detail/nging`
	licenseURL      = `http://www.webx.top/product/license/nging`
	versionURL      = `http://www.webx.top/product/version/nging`
	licenseFileName = `license.key`
	licenseFile     = filepath.Join(echo.Wd(), licenseFileName)
	licenseExists   bool
	licenseError    = lib.UnlicensedVersion
	licenseData     *lib.LicenseData
	licenseVersion  string
	licensePackage  string
	machineID       string
	domain          string
	emptyLicense    = lib.LicenseData{}
	// ErrLicenseNotFound 授权证书不存在
	ErrLicenseNotFound = errors.New(`License does not exist`)
	// SkipLicenseCheck 跳过授权检测
	SkipLicenseCheck = true
)

type ServerURL struct {
	Tracker         string //用于统计分析的js地址
	Product         string //该产品的详情介绍页面网址
	License         string //许可证验证和许可证下载API网址
	Version         string //该产品最新版本信息API网址
	LicenseFileName string //许可证文件名称
}

func (s *ServerURL) Apply() {
	if len(s.Tracker) > 0 {
		trackerURL = s.Tracker
	}
	if len(s.Product) > 0 {
		productURL = s.Product
	}
	if len(s.License) > 0 {
		licenseURL = s.License
	}
	if len(s.Version) > 0 {
		versionURL = s.Version
	}
	if len(s.LicenseFileName) > 0 {
		licenseFileName = s.LicenseFileName
		licenseFile = filepath.Join(echo.Wd(), licenseFileName)
	}
}

func SetServerURL(s *ServerURL) {
	if s != nil {
		s.Apply()
	}
}

func SetVersion(version string) {
	licenseVersion = version
}

func SetPackage(pkg string) {
	licensePackage = pkg
}

func Version() string {
	return licenseVersion
}

func Package() string {
	return licensePackage
}

func ProductURL() string {
	return productURL
}

func TrackerURL() string {
	return trackerURL + `?version=` + Version() + `&package=` + Package() + `&os=` + runtime.GOOS + `&arch=` + runtime.GOARCH
}

func FilePath() string {
	return licenseFile
}

func FileName() string {
	return licenseFileName
}

func Exists() bool {
	return licenseExists
}

func Error() error {
	return licenseError
}

func License() lib.LicenseData {
	if licenseData == nil {
		return emptyLicense
	}
	return *licenseData
}

// Check 检查权限
func Check(machineID string, ctx echo.Context) error {
	if SkipLicenseCheck {
		return nil
	}
	if len(machineID) == 0 {
		var err error
		machineID, err = MachineID()
		if err != nil {
			return err
		}
	}
	licenseError = validateFromOfficial(machineID, ctx)
	if licenseError != ErrConnectionFailed {
		return licenseError
	}
	//当官方服务器不可用时才验证本地许可证
	licenseError = Validate()
	return licenseError
}

func Ok(ctx echo.Context) bool {
	if SkipLicenseCheck {
		return true
	}
	switch licenseError {
	case nil:
		if licenseData == nil {
			licenseError = lib.UnlicensedVersion
			return false
		}
		if !licenseData.Info.Expiration.IsZero() && time.Now().After(licenseData.Info.Expiration) {
			licenseError = lib.ExpiredLicense
			return false
		}
		return true
	default:
		machineID, _ := MachineID()
		err := Check(machineID, ctx)
		if err == nil {
			licenseError = nil
			return true
		}
		log.Warn(err)
	}
	return false
}

// Validation 定义验证器
type Validation struct {
	NowVersions []string
}

// Validate 参数验证器
func (v *Validation) Validate(data *lib.LicenseData) error {
	if err := data.CheckExpiration(); err != nil {
		return err
	}
	if err := data.CheckVersion(v.NowVersions...); err != nil {
		return err
	}
	mid, err := MachineID()
	if err != nil {
		return err
	}
	if data.Info.MachineID != mid {
		return lib.InvalidMachineID
	}
	return nil
}

// Validate 验证授权
func Validate() error {
	licenseExists = com.FileExists(FilePath())
	if !licenseExists {
		licenseError = ErrLicenseNotFound
		return licenseError
	}
	b, err := ioutil.ReadFile(FilePath())
	if err != nil {
		return err
	}
	validator := &Validation{
		NowVersions: []string{licenseVersion},
	}
	licenseData, err = lib.CheckLicenseStringAndReturning(string(b), PublicKey(), validator)
	return err
}

// Save 保存授权文件
func Save(b []byte) error {
	return ioutil.WriteFile(licenseFile, b, os.ModePerm)
}

// Generate 生成演示版证书
func Generate(privBytes []byte, pemSaveDirs ...string) error {
	var err error
	if privBytes == nil {
		var pubBytes []byte
		pubBytes, privBytes, err = lib.GenerateCertificateData(2048)
		if err != nil {
			return err
		}
		publicKey = string(pubBytes)
		var pemSaveDir string
		if len(pemSaveDirs) > 0 {
			pemSaveDir = pemSaveDirs[0]
		} else {
			pemSaveDir = filepath.Join(echo.Wd(), `data`)
		}
		if len(pemSaveDir) > 0 {
			err = ioutil.WriteFile(filepath.Join(pemSaveDir, `nging.pem.pub`), pubBytes, os.ModePerm)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(filepath.Join(pemSaveDir, `nging.pem`), privBytes, os.ModePerm)
			if err != nil {
				return err
			}
		}
	}
	info := &lib.LicenseInfo{
		Name:       `demo`,
		LicenseID:  `0`,
		Version:    licenseVersion,
		Package:    licensePackage,
		Expiration: time.Now().Add(30 * 24 * time.Hour),
	}
	info.MachineID, err = MachineID()
	if err != nil {
		return err
	}
	licBytes, err := lib.GenerateLicense(info, string(privBytes))
	if err != nil {
		return err
	}
	return Save(licBytes)
}

// MachineID 生成当前机器的机器码
func MachineID() (string, error) {
	if len(machineID) > 0 {
		return machineID, nil
	}
	addrs, err := lib.MACAddresses(false)
	if err != nil {
		return ``, err
	}
	if len(addrs) < 1 {
		return ``, lib.ErrorMachineID
	}
	cpuInfo, err := cpu.Info()
	if err != nil {
		return ``, err
	}
	var cpuID string
	if len(cpuInfo) > 0 {
		cpuID = cpuInfo[0].PhysicalID
		if len(cpuID) == 0 {
			cpuID = com.Md5(com.Dump(cpuInfo, false))
		}
	}
	machineID = com.MakePassword(lib.Hash(addrs[0])+`#`+cpuID, `coscms`, 3, 8, 19)
	return machineID, err
}

// FullLicenseURL 包含完整参数的授权网址
func FullLicenseURL(machineID string, ctx echo.Context) string {
	return licenseURL + `?` + URLValues(machineID, ctx).Encode()
}

// URLValues 组装网址参数
func URLValues(machineID string, ctx echo.Context) url.Values {
	if len(machineID) == 0 {
		machineID, _ = MachineID()
	}
	v := url.Values{}
	v.Set(`os`, runtime.GOOS)
	v.Set(`arch`, runtime.GOARCH)
	v.Set(`version`, licenseVersion)
	v.Set(`package`, licensePackage)
	v.Set(`machineID`, machineID)
	if ctx != nil {
		v.Set(`domain`, ctx.Domain())
		v.Set(`source`, ctx.RequestURI())
	}
	v.Set(`time`, time.Now().Local().Format(`20060102-150405`))
	return v
}

// Download 从官方服务器重新下载许可证
func Download(machineID string, ctx echo.Context) error {
	operation := `获取授权证书失败：%v`
	client := restclient.Resty()
	client.SetHeader("Accept", "application/json")
	officialResp := &OfficialResp{}
	client.SetResult(officialResp)
	fullURL := FullLicenseURL(machineID, ctx) + `&pipe=download`
	response, err := client.Get(fullURL)
	if err != nil {
		return fmt.Errorf(operation, err)
	}
	if response == nil {
		return ErrConnectionFailed
	}
	if response.IsError() {
		return fmt.Errorf(operation, string(response.Body()))
	}
	if officialResp.Code != 1 {
		return fmt.Errorf(`%v`, officialResp.Info)
	}
	if officialResp.Data == nil {
		return ErrLicenseDownloadFailed
	}
	if com.FileExists(licenseFile) {
		err = os.Rename(licenseFile, licenseFile+`.`+time.Now().Format(`20060102150405`))
		if err != nil {
			return err
		}
	}
	licenseData = &officialResp.Data.LicenseData
	b, err := com.JSONEncode(licenseData, `  `)
	if err != nil {
		b = []byte(err.Error())
	}
	err = ioutil.WriteFile(licenseFile, b, os.ModePerm)
	if err != nil {
		return fmt.Errorf(`保存授权证书失败：%v`, err)
	}
	return err
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package license

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/admpub/license_gen/lib"
	"github.com/admpub/log"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/webx-top/com"
	"github.com/webx-top/echo"

	"github.com/admpub/nging/v3/application/library/restclient"
)

var (
	trackerURL      = `//www.webx.top/product/script/nging/tracker.js`
	productURL      = `http://www.webx.top/product/detail/nging`
	licenseURL      = `http://www.webx.top/product/license/nging`
	versionURL      = `http://www.webx.top/product/version/nging`
	licenseFileName = `license.key`
	licenseFile     = filepath.Join(echo.Wd(), licenseFileName)
	licenseExists   bool
	licenseError    = lib.UnlicensedVersion
	licenseData     *lib.LicenseData
	licenseVersion  string
	licensePackage  string
	machineID       string
	domain          string
	emptyLicense    = lib.LicenseData{}
	// ErrLicenseNotFound 授权证书不存在
	ErrLicenseNotFound = errors.New(`License does not exist`)
	// SkipLicenseCheck 跳过授权检测
	SkipLicenseCheck = true
)

type ServerURL struct {
	Tracker         string //用于统计分析的js地址
	Product         string //该产品的详情介绍页面网址
	License         string //许可证验证和许可证下载API网址
	Version         string //该产品最新版本信息API网址
	LicenseFileName string //许可证文件名称
}

func (s *ServerURL) Apply() {
	if len(s.Tracker) > 0 {
		trackerURL = s.Tracker
	}
	if len(s.Product) > 0 {
		productURL = s.Product
	}
	if len(s.License) > 0 {
		licenseURL = s.License
	}
	if len(s.Version) > 0 {
		versionURL = s.Version
	}
	if len(s.LicenseFileName) > 0 {
		licenseFileName = s.LicenseFileName
		licenseFile = filepath.Join(echo.Wd(), licenseFileName)
	}
}

func SetServerURL(s *ServerURL) {
	if s != nil {
		s.Apply()
	}
}

func SetVersion(version string) {
	licenseVersion = version
}

func SetPackage(pkg string) {
	licensePackage = pkg
}

func Version() string {
	return licenseVersion
}

func Package() string {
	return licensePackage
}

func ProductURL() string {
	return productURL
}

func TrackerURL() string {
	return trackerURL + `?ver=` + Version() + `&pkg=` + Package() + `&os=` + runtime.GOOS + `&arch=` + runtime.GOARCH
}

func FilePath() string {
	return licenseFile
}

func FileName() string {
	return licenseFileName
}

func Exists() bool {
	return licenseExists
}

func Error() error {
	return licenseError
}

func License() lib.LicenseData {
	if licenseData == nil {
		return emptyLicense
	}
	return *licenseData
}

// Check 检查权限
func Check(machineID string, ctx echo.Context) error {
	if SkipLicenseCheck {
		return nil
	}
	if len(machineID) == 0 {
		var err error
		machineID, err = MachineID()
		if err != nil {
			return err
		}
	}
	licenseError = validateFromOfficial(machineID, ctx)
	if licenseError != ErrConnectionFailed {
		return licenseError
	}
	//当官方服务器不可用时才验证本地许可证
	licenseError = Validate()
	return licenseError
}

func Ok(ctx echo.Context) bool {
	if SkipLicenseCheck {
		return true
	}
	switch licenseError {
	case nil:
		if licenseData == nil {
			licenseError = lib.UnlicensedVersion
			return false
		}
		if !licenseData.Info.Expiration.IsZero() && time.Now().After(licenseData.Info.Expiration) {
			licenseError = lib.ExpiredLicense
			return false
		}
		return true
	default:
		machineID, _ := MachineID()
		err := Check(machineID, ctx)
		if err == nil {
			licenseError = nil
			return true
		}
		log.Warn(err)
	}
	return false
}

// Validation 定义验证器
type Validation struct {
	NowVersions []string
}

// Validate 参数验证器
func (v *Validation) Validate(data *lib.LicenseData) error {
	if err := data.CheckExpiration(); err != nil {
		return err
	}
	if err := data.CheckVersion(v.NowVersions...); err != nil {
		return err
	}
	mid, err := MachineID()
	if err != nil {
		return err
	}
	if data.Info.MachineID != mid {
		return lib.InvalidMachineID
	}
	return nil
}

// Validate 验证授权
func Validate() error {
	licenseExists = com.FileExists(FilePath())
	if !licenseExists {
		licenseError = ErrLicenseNotFound
		return licenseError
	}
	b, err := ioutil.ReadFile(FilePath())
	if err != nil {
		return err
	}
	validator := &Validation{
		NowVersions: []string{licenseVersion},
	}
	licenseData, err = lib.CheckLicenseStringAndReturning(string(b), PublicKey(), validator)
	return err
}

// Save 保存授权文件
func Save(b []byte) error {
	return ioutil.WriteFile(licenseFile, b, os.ModePerm)
}

// Generate 生成演示版证书
func Generate(privBytes []byte, pemSaveDirs ...string) error {
	var err error
	if privBytes == nil {
		var pubBytes []byte
		pubBytes, privBytes, err = lib.GenerateCertificateData(2048)
		if err != nil {
			return err
		}
		publicKey = string(pubBytes)
		var pemSaveDir string
		if len(pemSaveDirs) > 0 {
			pemSaveDir = pemSaveDirs[0]
		} else {
			pemSaveDir = filepath.Join(echo.Wd(), `data`)
		}
		if len(pemSaveDir) > 0 {
			err = ioutil.WriteFile(filepath.Join(pemSaveDir, `nging.pem.pub`), pubBytes, os.ModePerm)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(filepath.Join(pemSaveDir, `nging.pem`), privBytes, os.ModePerm)
			if err != nil {
				return err
			}
		}
	}
	info := &lib.LicenseInfo{
		Name:       `demo`,
		LicenseID:  `0`,
		Version:    licenseVersion,
		Package:    licensePackage,
		Expiration: time.Now().Add(30 * 24 * time.Hour),
	}
	info.MachineID, err = MachineID()
	if err != nil {
		return err
	}
	licBytes, err := lib.GenerateLicense(info, string(privBytes))
	if err != nil {
		return err
	}
	return Save(licBytes)
}

// MachineID 生成当前机器的机器码
func MachineID() (string, error) {
	if len(machineID) > 0 {
		return machineID, nil
	}
	addrs, err := lib.MACAddresses(false)
	if err != nil {
		return ``, err
	}
	if len(addrs) < 1 {
		return ``, lib.ErrorMachineID
	}
	cpuInfo, err := cpu.Info()
	if err != nil {
		return ``, err
	}
	var cpuID string
	if len(cpuInfo) > 0 {
		cpuID = cpuInfo[0].PhysicalID
		if len(cpuID) == 0 {
			cpuID = com.Md5(com.Dump(cpuInfo, false))
		}
	}
	machineID = com.MakePassword(lib.Hash(addrs[0])+`#`+cpuID, `coscms`, 3, 8, 19)
	return machineID, err
}

// FullLicenseURL 包含完整参数的授权网址
func FullLicenseURL(machineID string, ctx echo.Context) string {
	return licenseURL + `?` + URLValues(machineID, ctx).Encode()
}

// URLValues 组装网址参数
func URLValues(machineID string, ctx echo.Context) url.Values {
	if len(machineID) == 0 {
		machineID, _ = MachineID()
	}
	v := url.Values{}
	v.Set(`os`, runtime.GOOS)
	v.Set(`arch`, runtime.GOARCH)
	v.Set(`version`, licenseVersion)
	v.Set(`package`, licensePackage)
	v.Set(`machineID`, machineID)
	if ctx != nil {
		v.Set(`domain`, ctx.Domain())
		v.Set(`source`, ctx.RequestURI())
	}
	v.Set(`time`, time.Now().Local().Format(`20060102-150405`))
	return v
}

// Download 从官方服务器重新下载许可证
func Download(machineID string, ctx echo.Context) error {
	operation := `获取授权证书失败：%v`
	client := restclient.Resty()
	client.SetHeader("Accept", "application/json")
	officialResp := &OfficialResp{}
	client.SetResult(officialResp)
	fullURL := FullLicenseURL(machineID, ctx) + `&pipe=download`
	response, err := client.Get(fullURL)
	if err != nil {
		return fmt.Errorf(operation, err)
	}
	if response == nil {
		return ErrConnectionFailed
	}
	if response.IsError() {
		return fmt.Errorf(operation, string(response.Body()))
	}
	if officialResp.Code != 1 {
		return fmt.Errorf(`%v`, officialResp.Info)
	}
	if officialResp.Data == nil {
		return ErrLicenseDownloadFailed
	}
	if com.FileExists(licenseFile) {
		err = os.Rename(licenseFile, licenseFile+`.`+time.Now().Format(`20060102150405`))
		if err != nil {
			return err
		}
	}
	licenseData = &officialResp.Data.LicenseData
	b, err := com.JSONEncode(licenseData, `  `)
	if err != nil {
		b = []byte(err.Error())
	}
	err = ioutil.WriteFile(licenseFile, b, os.ModePerm)
	if err != nil {
		return fmt.Errorf(`保存授权证书失败：%v`, err)
	}
	return err
}
<|endoftext|>