<commit_before>// +build linux freebsd solaris

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	zfs "github.com/mistifyio/go-zfs"
	"github.com/opencontainers/runc/libcontainer/label"
)

type zfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

// Logger returns a zfs logger implementation.
type Logger struct{}

// Log wraps log message from ZFS driver with a prefix '[zfs]'.
func (*Logger) Log(cmd []string) {
	logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

// Init returns a new ZFS driver.
// It takes base mount path and an array of options which are represented as key value pairs.
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
	var err error

	if _, err := exec.LookPath("zfs"); err != nil {
		logrus.Debugf("[zfs] zfs command is not available: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}
	defer file.Close()

	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	if err := mount.MakePrivate(base); err != nil {
		return nil, err
	}
	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
		uidMaps:          uidMaps,
		gidMaps:          gidMaps,
		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
	}
	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}

func parseOptions(opt []string) (zfsOptions, error) {
	var options zfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

// Driver holds information about the driver, such as zfs dataset, options and cache.
type Driver struct {
	dataset          *zfs.Dataset
	options          zfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
	uidMaps          []idtools.IDMap
	gidMaps          []idtools.IDMap
	ctr              *graphdriver.RefCounter
}

func (d *Driver) String() string {
	return "zfs"
}

// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
func (d *Driver) Cleanup() error {
	return nil
}

// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
// such as pool name, dataset name, disk usage, parent quota and compression used.
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
// 'Space Available', 'Parent Quota' and 'Compression'.
func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

// GetMetadata returns image/container metadata related to graph driver
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
	return nil, nil
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) zfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) mountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
}

// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
	return d.Create(id, parent, mountLabel, storageOpt)
}

// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error {
	err := d.create(id, parent, storageOpt)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.zfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent, storageOpt)
}

func (d *Driver) create(id, parent string, storageOpt map[string]string) error {
	name := d.zfsPath(id)
	quota, err := parseStorageOpt(storageOpt)
	if err != nil {
		return err
	}
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			err = setQuota(name, quota)
			if err == nil {
				d.Lock()
				d.filesystemsCache[fs.Name] = true
				d.Unlock()
			}
		}
		return err
	}
	err = d.cloneFilesystem(name, d.zfsPath(parent))
	if err == nil {
		err = setQuota(name, quota)
	}
	return err
}

func parseStorageOpt(storageOpt map[string]string) (string, error) {
	// Read size to change the disk quota per container
	for k, v := range storageOpt {
		key := strings.ToLower(k)
		switch key {
		case "size":
			return v, nil
		default:
			return "0", fmt.Errorf("Unknown option %s", key)
		}
	}
	return "0", nil
}

func setQuota(name string, quota string) error {
	if quota == "0" {
		return nil
	}
	fs, err := zfs.GetDataset(name)
	if err != nil {
		return err
	}
	return fs.SetProperty("quota", quota)
}

// Remove deletes the dataset, filesystem and the cache for the given id.
func (d *Driver) Remove(id string) error {
	name := d.zfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

// Get returns the mountpoint for the given id after creating the target directories if necessary.
func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Increment(mountpoint); count > 1 {
		return mountpoint, nil
	}

	filesystem := d.zfsPath(id)
	options := label.FormatMountLabel("", mountLabel)
	logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)

	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
	if err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}
	// Create the target directories if they don't exist
	if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}

	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	// this could be our first mount after creation of the filesystem, and the root dir may still have root
	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
		mount.Unmount(mountpoint)
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
	}

	return mountpoint, nil
}

// Put removes the existing mountpoint for the given id if it exists.
func (d *Driver) Put(id string) error {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Decrement(mountpoint); count > 0 {
		return nil
	}
	mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
	if err != nil || !mounted {
		return err
	}

	logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

// Exists checks to see if the cache entry exists for the given id.
func (d *Driver) Exists(id string) bool {
	d.Lock()
	defer d.Unlock()
	return d.filesystemsCache[d.zfsPath(id)] == true
}
<commit_msg>Create zfs directory if it doesn't exist at startup.<commit_after>// +build linux freebsd solaris

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	zfs "github.com/mistifyio/go-zfs"
	"github.com/opencontainers/runc/libcontainer/label"
)

type zfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

// Logger returns a zfs logger implementation.
type Logger struct{}

// Log wraps log message from ZFS driver with a prefix '[zfs]'.
func (*Logger) Log(cmd []string) {
	logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

// Init returns a new ZFS driver.
// It takes base mount path and an array of options which are represented as key value pairs.
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
	var err error

	if _, err := exec.LookPath("zfs"); err != nil {
		logrus.Debugf("[zfs] zfs command is not available: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}
	defer file.Close()

	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
	if err != nil {
		return nil, fmt.Errorf("Failed to get root uid/guid: %v", err)
	}
	if err := idtools.MkdirAllAs(base, 0700, rootUID, rootGID); err != nil {
		return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
	}

	if err := mount.MakePrivate(base); err != nil {
		return nil, err
	}
	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
		uidMaps:          uidMaps,
		gidMaps:          gidMaps,
		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
	}
	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}

func parseOptions(opt []string) (zfsOptions, error) {
	var options zfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

// Driver holds information about the driver, such as zfs dataset, options and cache.
type Driver struct {
	dataset          *zfs.Dataset
	options          zfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
	uidMaps          []idtools.IDMap
	gidMaps          []idtools.IDMap
	ctr              *graphdriver.RefCounter
}

func (d *Driver) String() string {
	return "zfs"
}

// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
func (d *Driver) Cleanup() error {
	return nil
}

// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
// such as pool name, dataset name, disk usage, parent quota and compression used.
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
// 'Space Available', 'Parent Quota' and 'Compression'.
func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

// GetMetadata returns image/container metadata related to graph driver
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
	return nil, nil
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) zfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) mountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
}

// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
	return d.Create(id, parent, mountLabel, storageOpt)
}

// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error {
	err := d.create(id, parent, storageOpt)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.zfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent, storageOpt)
}

func (d *Driver) create(id, parent string, storageOpt map[string]string) error {
	name := d.zfsPath(id)
	quota, err := parseStorageOpt(storageOpt)
	if err != nil {
		return err
	}
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			err = setQuota(name, quota)
			if err == nil {
				d.Lock()
				d.filesystemsCache[fs.Name] = true
				d.Unlock()
			}
		}
		return err
	}
	err = d.cloneFilesystem(name, d.zfsPath(parent))
	if err == nil {
		err = setQuota(name, quota)
	}
	return err
}

func parseStorageOpt(storageOpt map[string]string) (string, error) {
	// Read size to change the disk quota per container
	for k, v := range storageOpt {
		key := strings.ToLower(k)
		switch key {
		case "size":
			return v, nil
		default:
			return "0", fmt.Errorf("Unknown option %s", key)
		}
	}
	return "0", nil
}

func setQuota(name string, quota string) error {
	if quota == "0" {
		return nil
	}
	fs, err := zfs.GetDataset(name)
	if err != nil {
		return err
	}
	return fs.SetProperty("quota", quota)
}

// Remove deletes the dataset, filesystem and the cache for the given id.
func (d *Driver) Remove(id string) error {
	name := d.zfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

// Get returns the mountpoint for the given id after creating the target directories if necessary.
func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Increment(mountpoint); count > 1 {
		return mountpoint, nil
	}

	filesystem := d.zfsPath(id)
	options := label.FormatMountLabel("", mountLabel)
	logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)

	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
	if err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}
	// Create the target directories if they don't exist
	if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}

	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	// this could be our first mount after creation of the filesystem, and the root dir may still have root
	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
		mount.Unmount(mountpoint)
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
	}

	return mountpoint, nil
}

// Put removes the existing mountpoint for the given id if it exists.
func (d *Driver) Put(id string) error {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Decrement(mountpoint); count > 0 {
		return nil
	}
	mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
	if err != nil || !mounted {
		return err
	}

	logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

// Exists checks to see if the cache entry exists for the given id.
func (d *Driver) Exists(id string) bool {
	d.Lock()
	defer d.Unlock()
	return d.filesystemsCache[d.zfsPath(id)] == true
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/hut8/tumblr-go"
	"os"
	"regexp"
	"strconv"
)

var (
	tumblrDataRegexp *regexp.Regexp = regexp.MustCompile("http://([^/]+)/post/(\\d+)(?:/.+)?")
	tumblrClient *tumblr.Tumblr = &tumblr.Tumblr{
		Credentials: getCredentials(),
	}
)

// http://lacecard.tumblr.com/post/76803575816/emacs-in-tron -> (lacecard.tumblr.com, 76803575816)
func extractPostId(u string) (string, int64, error) {
	m := tumblrDataRegexp.FindStringSubmatch(u)
	if m == nil {
		return "", 0, fmt.Errorf("Could not extract base hostname and post ID")
	}
	bh := m[1]
	pid, err := strconv.ParseInt(m[2], 10, 64)
	ensureNil(err) // For 2^64+
	return bh, pid, nil
}

// Return a slice of blog names who like the given post
// FIXME: The stupid API only returns 20 of these friggin things
func blogsLikingPost(baseHostname string, postId int64) ([]string, error) {
	// Get the post from the API (specifying ID guarantees only one)
	blog := tumblrClient.NewBlog(baseHostname)
	params := tumblr.PostRequestParams{
		Id:        postId,
		NotesInfo: true,
	}
	postCollection, err := blog.Posts(params)
	ensureNil(err)

	// Extract the single post we're looking for
	posts := postCollection.Posts
	if len(posts) == 0 {
		return nil, fmt.Errorf("No such post was found")
	}
	if len(posts) > 1 {
		panic("Multiple posts return from API with same ID")
	}
	p := posts[0]

	// Make set of blog names
	blogNames := make(map[string]struct{})
	for _, note := range p.PostNotes() {
		blogNames[note.BlogURL] = struct{}{}
	}

	// Uniquify blog names
	uniqueBlogNames := []string{}
	for name, _ := range blogNames {
		uniqueBlogNames = append(uniqueBlogNames, name)
	}

	return uniqueBlogNames, nil
}

func getCredentials() tumblr.APICredentials {
	return tumblr.APICredentials{
		Key:    os.Getenv("ALIKER_KEY"),
		Secret: os.Getenv("ALIKER_SECRET"),
	}
}
<commit_msg>Panic if we can't find our keys!<commit_after>package main

import (
	"fmt"
	"github.com/hut8/tumblr-go"
	"os"
	"regexp"
	"strconv"
)

var (
	tumblrDataRegexp *regexp.Regexp = regexp.MustCompile("http://([^/]+)/post/(\\d+)(?:/.+)?")
	tumblrClient *tumblr.Tumblr = &tumblr.Tumblr{
		Credentials: getCredentials(),
	}
)

// http://lacecard.tumblr.com/post/76803575816/emacs-in-tron -> (lacecard.tumblr.com, 76803575816)
func extractPostId(u string) (string, int64, error) {
	m := tumblrDataRegexp.FindStringSubmatch(u)
	if m == nil {
		return "", 0, fmt.Errorf("Could not extract base hostname and post ID")
	}
	bh := m[1]
	pid, err := strconv.ParseInt(m[2], 10, 64)
	ensureNil(err) // For 2^64+
	return bh, pid, nil
}

// Return a slice of blog names who like the given post
// FIXME: The stupid API only returns 20 of these friggin things
func blogsLikingPost(baseHostname string, postId int64) ([]string, error) {
	// Get the post from the API (specifying ID guarantees only one)
	blog := tumblrClient.NewBlog(baseHostname)
	params := tumblr.PostRequestParams{
		Id:        postId,
		NotesInfo: true,
	}
	postCollection, err := blog.Posts(params)
	ensureNil(err)

	// Extract the single post we're looking for
	posts := postCollection.Posts
	if len(posts) == 0 {
		return nil, fmt.Errorf("No such post was found")
	}
	if len(posts) > 1 {
		panic("Multiple posts return from API with same ID")
	}
	p := posts[0]

	// Make set of blog names
	blogNames := make(map[string]struct{})
	for _, note := range p.PostNotes() {
		blogNames[note.BlogURL] = struct{}{}
	}

	// Uniquify blog names
	uniqueBlogNames := []string{}
	for name, _ := range blogNames {
		uniqueBlogNames = append(uniqueBlogNames, name)
	}

	return uniqueBlogNames, nil
}

func getCredentials() tumblr.APICredentials {
	c := tumblr.APICredentials{
		Key:    os.Getenv("ALIKER_KEY"),
		Secret: os.Getenv("ALIKER_SECRET"),
	}
	if c.Key == "" || c.Secret == "" {
		msg := "ALIKER_KEY and ALIKER_SECRET variables unset"
		panic(msg)
	}
	return c
}
<|endoftext|>


<commit_before>package twitter

import (
    "net/url"
    "strconv"
)

func (a TwitterApi) GetTweet(id int64, v url.Values) (tweet Tweet, err error){
    v.Set("id", strconv.FormatInt(id,10))
    err = a.apiGet("https://api.twitter.com/1.1/statuses/show.json", v, &tweet)
    return
}

<commit_msg>Implement GET statuses/retweets/:id<commit_after>package twitter

import (
    "net/url"
    "fmt"
    "strconv"
)

func (a TwitterApi) GetTweet(id int64, v url.Values) (tweet Tweet, err error){
    v.Set("id", strconv.FormatInt(id,10))
    err = a.apiGet("https://api.twitter.com/1.1/statuses/show.json", v, &tweet)
    return
}

func (a TwitterApi) GetRetweets(id int64, v url.Values) (tweets []Tweet, err error){
    err = a.apiGet(fmt.Sprintf("https://api.twitter.com/1.1/statuses/retweets/%d.json", id), v, &tweets)
    return
}

<|endoftext|>


<commit_before>// Copyright 2012 Arne Roomann-Kurrik
//
// 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 twodee

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"github.com/banthar/gl"
	"github.com/jteeuwen/glfw"
	"image"
	"image/draw"
	"image/png"
	"os"
)

type Window struct {
	Width  int
	Height int
	Title  string
}

func (w *Window) Opened() bool {
	return glfw.WindowParam(glfw.Opened) == 1
}

type Texture struct {
	texture gl.Texture
	Width   int
	Height  int
}

func LoadTexture(path string, smoothing int) (texture *Texture, err error) {
	var (
		file      *os.File
		img       image.Image
		bounds    image.Rectangle
		data      *bytes.Buffer
		gltexture gl.Texture
	)
	gltexture = gl.GenTexture()
	gltexture.Bind(gl.TEXTURE_2D)
	if file, err = os.Open(path); err != nil {
		return
	}
	defer file.Close()
	if img, err = png.Decode(file); err != nil {
		return
	}
	if data, err = EncodeTGA(path, img); err != nil {
		return
	}
	if !glfw.LoadMemoryTexture2D(data.Bytes(), 0) {
		err = fmt.Errorf("Failed to load texture: %v", path)
		return
	}
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, smoothing)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, smoothing)
	bounds = img.Bounds()
	texture = &Texture{
		texture: gltexture,
		Width:   bounds.Dx(),
		Height:  bounds.Dy(),
	}
	return
}

func (t *Texture) Bind() {
	t.texture.Bind(gl.TEXTURE_2D)
}

func (t *Texture) Unbind() {
	t.texture.Unbind(gl.TEXTURE_2D)
}

type System struct {
	Textures map[string]*Texture
}

func Init() (sys *System, err error) {
	if err = glfw.Init(); err != nil {
		return
	}
	sys = &System{}
	sys.Textures = make(map[string]*Texture, 0)
	return
}

func (s *System) SetKeyCallback(handler glfw.KeyHandler) {
	glfw.SetKeyCallback(handler)
}

func (s *System) Key(key int) int {
	return glfw.Key(key)
}

func (s *System) SetCharCallback(handler glfw.CharHandler) {
	glfw.SetCharCallback(handler)
}

func (s *System) Terminate() {
	glfw.Terminate()
	s.Textures = nil
}

func (s *System) setProjection(win *Window) {
	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	gl.Ortho(0, float64(win.Width), float64(win.Height), 0, 0, 1)
	gl.MatrixMode(gl.MODELVIEW)
}

func (s *System) Open(win *Window) (err error) {
	glfw.SetWindowSizeCallback(func(w, h int) {
		win.Width = w
		win.Height = h
		s.setProjection(win)
	})
	err = glfw.OpenWindow(
		win.Width,
		win.Height,
		0, 0, 0, 0, 0, 0,
		glfw.Windowed)
	glfw.SetWindowTitle(win.Title)
	win.Width, win.Height = glfw.WindowSize()
	s.setProjection(win)
	gl.Enable(gl.TEXTURE_2D)
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	return
}

func (s *System) clamp(i int, max int) gl.GLclampf {
	return gl.GLclampf(float32(i) / float32(max))
}

func (s *System) SetClearColor(r int, g int, b int, a int) {
	gl.ClearColor(s.clamp(r, 255), s.clamp(g, 255), s.clamp(b, 255), s.clamp(a, 255))
}

func (s *System) LoadTexture(name string, path string, inter int) (err error) {
	var texture *Texture
	if texture, err = LoadTexture(path, inter); err != nil {
		return
	}
	s.Textures[name] = texture
	return
}

func EncodeTGA(name string, img image.Image) (buf *bytes.Buffer, err error) {
	var (
		bounds image.Rectangle = img.Bounds()
		ident  []byte          = []byte(name)
		width  []byte          = make([]byte, 2)
		height []byte          = make([]byte, 2)
		nrgba  *image.NRGBA
		data   []byte
	)
	binary.LittleEndian.PutUint16(width, uint16(bounds.Dx()))
	binary.LittleEndian.PutUint16(height, uint16(bounds.Dy()))

	// See http://paulbourke.net/dataformats/tga/
	buf = &bytes.Buffer{}
	buf.WriteByte(byte(len(ident)))
	buf.WriteByte(0)
	buf.WriteByte(2) // uRGBI
	buf.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0})
	buf.Write([]byte(width))
	buf.Write([]byte(height))
	buf.WriteByte(32) // Bits per pixel
	buf.WriteByte(8)
	if buf.Len() != 18 {
		err = fmt.Errorf("TGA header is not 18 bytes: %v", buf.Len())
		return
	}

	nrgba = image.NewNRGBA(bounds)
	draw.Draw(nrgba, bounds, img, bounds.Min, draw.Src)
	buf.Write(ident)
	data = make([]byte, bounds.Dx()*bounds.Dy()*4)
	var (
		lineLength int = bounds.Dx() * 4
		destOffset int = len(data) - lineLength
	)
	for srcOffset := 0; srcOffset < len(nrgba.Pix); {
		var (
			dest   = data[destOffset : destOffset+lineLength]
			source = nrgba.Pix[srcOffset : srcOffset+nrgba.Stride]
		)
		copy(dest, source)
		destOffset -= lineLength
		srcOffset += nrgba.Stride
	}
	for x := 0; x < len(data); {
		buf.WriteByte(data[x+2])
		buf.WriteByte(data[x+1])
		buf.WriteByte(data[x+0])
		buf.WriteByte(data[x+3])
		x += 4
	}
	return
}

func (s *System) Paint(scene *Scene) {
	gl.Clear(gl.COLOR_BUFFER_BIT)
	scene.Draw()
	glfw.SwapBuffers()
}
<commit_msg>log resize messages<commit_after>// Copyright 2012 Arne Roomann-Kurrik
//
// 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 twodee

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"github.com/banthar/gl"
	"github.com/jteeuwen/glfw"
	"image"
	"image/draw"
	"image/png"
	"os"
)

type Window struct {
	Width  int
	Height int
	Title  string
}

func (w *Window) Opened() bool {
	return glfw.WindowParam(glfw.Opened) == 1
}

type Texture struct {
	texture gl.Texture
	Width   int
	Height  int
}

func LoadTexture(path string, smoothing int) (texture *Texture, err error) {
	var (
		file      *os.File
		img       image.Image
		bounds    image.Rectangle
		data      *bytes.Buffer
		gltexture gl.Texture
	)
	gltexture = gl.GenTexture()
	gltexture.Bind(gl.TEXTURE_2D)
	if file, err = os.Open(path); err != nil {
		return
	}
	defer file.Close()
	if img, err = png.Decode(file); err != nil {
		return
	}
	if data, err = EncodeTGA(path, img); err != nil {
		return
	}
	if !glfw.LoadMemoryTexture2D(data.Bytes(), 0) {
		err = fmt.Errorf("Failed to load texture: %v", path)
		return
	}
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, smoothing)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, smoothing)
	bounds = img.Bounds()
	texture = &Texture{
		texture: gltexture,
		Width:   bounds.Dx(),
		Height:  bounds.Dy(),
	}
	return
}

func (t *Texture) Bind() {
	t.texture.Bind(gl.TEXTURE_2D)
}

func (t *Texture) Unbind() {
	t.texture.Unbind(gl.TEXTURE_2D)
}

type System struct {
	Textures map[string]*Texture
}

func Init() (sys *System, err error) {
	if err = glfw.Init(); err != nil {
		return
	}
	sys = &System{}
	sys.Textures = make(map[string]*Texture, 0)
	return
}

func (s *System) SetKeyCallback(handler glfw.KeyHandler) {
	glfw.SetKeyCallback(handler)
}

func (s *System) Key(key int) int {
	return glfw.Key(key)
}

func (s *System) SetCharCallback(handler glfw.CharHandler) {
	glfw.SetCharCallback(handler)
}

func (s *System) Terminate() {
	glfw.Terminate()
	s.Textures = nil
}

func (s *System) setProjection(win *Window) {
	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	gl.Ortho(0, float64(win.Width), float64(win.Height), 0, 0, 1)
	gl.MatrixMode(gl.MODELVIEW)
}

func (s *System) Open(win *Window) (err error) {
	glfw.SetWindowSizeCallback(func(w, h int) {
		fmt.Printf("Resizing window to %v, %v\n", w, h)
		win.Width = w
		win.Height = h
		s.setProjection(win)
	})
	err = glfw.OpenWindow(
		win.Width,
		win.Height,
		0, 0, 0, 0, 0, 0,
		glfw.Windowed)
	glfw.SetWindowTitle(win.Title)
	win.Width, win.Height = glfw.WindowSize()
	s.setProjection(win)
	gl.Enable(gl.TEXTURE_2D)
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	return
}

func (s *System) clamp(i int, max int) gl.GLclampf {
	return gl.GLclampf(float32(i) / float32(max))
}

func (s *System) SetClearColor(r int, g int, b int, a int) {
	gl.ClearColor(s.clamp(r, 255), s.clamp(g, 255), s.clamp(b, 255), s.clamp(a, 255))
}

func (s *System) LoadTexture(name string, path string, inter int) (err error) {
	var texture *Texture
	if texture, err = LoadTexture(path, inter); err != nil {
		return
	}
	s.Textures[name] = texture
	return
}

func EncodeTGA(name string, img image.Image) (buf *bytes.Buffer, err error) {
	var (
		bounds image.Rectangle = img.Bounds()
		ident  []byte          = []byte(name)
		width  []byte          = make([]byte, 2)
		height []byte          = make([]byte, 2)
		nrgba  *image.NRGBA
		data   []byte
	)
	binary.LittleEndian.PutUint16(width, uint16(bounds.Dx()))
	binary.LittleEndian.PutUint16(height, uint16(bounds.Dy()))

	// See http://paulbourke.net/dataformats/tga/
	buf = &bytes.Buffer{}
	buf.WriteByte(byte(len(ident)))
	buf.WriteByte(0)
	buf.WriteByte(2) // uRGBI
	buf.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0})
	buf.Write([]byte(width))
	buf.Write([]byte(height))
	buf.WriteByte(32) // Bits per pixel
	buf.WriteByte(8)
	if buf.Len() != 18 {
		err = fmt.Errorf("TGA header is not 18 bytes: %v", buf.Len())
		return
	}

	nrgba = image.NewNRGBA(bounds)
	draw.Draw(nrgba, bounds, img, bounds.Min, draw.Src)
	buf.Write(ident)
	data = make([]byte, bounds.Dx()*bounds.Dy()*4)
	var (
		lineLength int = bounds.Dx() * 4
		destOffset int = len(data) - lineLength
	)
	for srcOffset := 0; srcOffset < len(nrgba.Pix); {
		var (
			dest   = data[destOffset : destOffset+lineLength]
			source = nrgba.Pix[srcOffset : srcOffset+nrgba.Stride]
		)
		copy(dest, source)
		destOffset -= lineLength
		srcOffset += nrgba.Stride
	}
	for x := 0; x < len(data); {
		buf.WriteByte(data[x+2])
		buf.WriteByte(data[x+1])
		buf.WriteByte(data[x+0])
		buf.WriteByte(data[x+3])
		x += 4
	}
	return
}

func (s *System) Paint(scene *Scene) {
	gl.Clear(gl.COLOR_BUFFER_BIT)
	scene.Draw()
	glfw.SwapBuffers()
}
<|endoftext|>


<commit_before>package organization

import (
	"encoding/json"
	"fmt"

	"github.com/deckarep/golang-set"
	ldap "gopkg.in/ldap.v2"
)

const (
	AuditActionAdd    = `add`
	AuditActionDel    = `delete`
	AuditActionUpdate = `update`
)

const (
	AuditCategoryUnit   = `department`
	AuditCategoryMember = `member`
)

type AuditContent []map[string]interface{}

// 更新权限和角色时会影响整个数据视图
func (org *Organization) refreshRBACIfNeeded(o, n []string) {
	if set(o).Equal(set(n)) { // 没有变化
		return
	}
	err := org.RefreshRBAC() // 更新RBAC缓存
	if err == nil {
		func(dn string) {
			sq := ldap.NewSearchRequest(dn, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, true, `(objectClass=audit)`, nil, nil)
			sr, _ := org.l.Search(sq)
			for _, r := range sr.Entries {
				org.l.Del(ldap.NewDelRequest(r.DN, nil))
			}
		}(org.parentDN(audit))
		org.latestResetVersion = newTimeStampVersion()
	} else {
		fmt.Print(`err: %s`, err.Error())
	}
}

// utils
func set(v []string) mapset.Set {
	set := mapset.NewSet()
	for _, s := range v {
		set.Add(s)
	}
	return set
}

func vset(ss []interface{}) []string {
	var strs []string
	for _, s := range ss {
		strs = append(strs, s.(string))
	}
	return strs
}

func (org *Organization) relatedMIDs(tid, category string) ([]string, error) {
	var mids []string
	var err error
	if category == AuditCategoryMember {
		mids, err = org.MemberIDsByTypeIDs([]string{tid})
	} else {
		rids := org.rbacx.RoleIDsByTypeID(tid)
		if len(rids) == 0 {
			mids = make([]string, 0)
		} else {
			mids, err = org.MemberIDsByRoleIDs(rids)
		}
	}
	return mids, err
}

func (org *Organization) logAddMember(member map[string]interface{}) error {
	return org.typeAdded(AuditCategoryMember, member)
}

func (org *Organization) logModifyMember(oMember, nMember map[string]interface{}) error {

	// 1. 判断用户角色有没有变化
	oRole := set(oMember[`rbacRole`].([]string))
	nRole := set(nMember[`rbacRole`].([]string))
	if !oRole.Equal(nRole) { // 用户的角色发生了变化
		// 计算发生变化前后的`Type`变化y
		oType := set(org.rbacx.MatchedTypes(oMember[`rbacRole`].([]string)))
		nType := set(org.rbacx.MatchedTypes(nMember[`rbacRole`].([]string)))

		addTypes := oType.Difference(nType)

		// 下面这2块代码，需要重构

		// 获取增加的用户ID
		mids, err := org.MemberIDsByTypeIDs(vset(addTypes.ToSlice()))
		if err != nil {
			return err
		}
		members, err := org.MemberByIDs(mids, false, false)
		if err != nil {
			return err
		}
		err = org.addAuditLog(AuditActionAdd, AuditCategoryMember, []string{nMember[`id`].(string)}, members)
		if err != nil {
			return err
		}

		// 获取删除的用户ID
		delTypes := nType.Difference(oType)

		mids, err = org.MemberIDsByTypeIDs(vset(delTypes.ToSlice()))
		if err != nil {
			return err
		}
		members, err = org.MemberByIDs(mids, false, false)
		if err != nil {
			return err
		}
		err = org.addAuditLog(AuditActionDel, AuditCategoryMember, []string{nMember[`id`].(string)}, members)
		if err != nil {
			return err
		}

		// 写两条日志
		// 1. 这个member 通讯录新增加了那几条访问权限
		// ADD|member[`id`].(string)|-----
		// 2. 这个member 删除了那几条访问权限
		// DEL|member[`id`].(string)|------
	}

	oType := oMember[`rbacType`].(string)
	nType := nMember[`rbacType`].(string)
	if oType != nType { // 修改了用户的类型
		org.typeChange(oType, nType, AuditCategoryMember, nMember)
		// 写日志
		// ADD|add|member
		// DEL|del|member
	}

	mids, err := org.relatedMIDs(nMember[`rbacType`].(string), AuditCategoryMember)
	if err != nil {
		return err
	}

	return org.addAuditLog(AuditActionUpdate, AuditCategoryMember, mids, AuditContent{nMember})
}

func (org *Organization) logDelMember(id string, relatedMIDs []string) error {
	return org.addAuditLog(AuditActionDel, AuditCategoryMember, relatedMIDs, AuditContent{
		map[string]interface{}{
			`id`: id,
		},
	})
}

func (org *Organization) logAddUnit(unit map[string]interface{}) error {
	return org.typeAdded(AuditCategoryUnit, unit)
}

func (org *Organization) logModifyUnit(oUnit, nUnit map[string]interface{}) error {
	oType := oUnit[`rbacType`].(string)
	nType := nUnit[`rbacType`].(string)
	if oType != nType {
		org.typeChange(oType, nType, AuditCategoryUnit, nUnit)
	}
	mids, err := org.relatedMIDs(nType, AuditCategoryUnit)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionUpdate, AuditCategoryUnit, mids, AuditContent{nUnit})
}

func (org *Organization) logDelUnit(id string, tid string) error {
	mids, err := org.relatedMIDs(tid, AuditCategoryUnit)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionDel, AuditCategoryUnit, mids, AuditContent{
		map[string]interface{}{
			`id`: id,
		},
	})
}

func (org *Organization) typeAdded(category string, entry map[string]interface{}) error {
	mids, err := org.relatedMIDs(entry[`rbacType`].(string), category)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionAdd, category, mids, AuditContent{entry})
}

func (org *Organization) typeChange(oType, nType string, category string, entry map[string]interface{}) error {
	oMIDs, err := org.relatedMIDs(oType, category)
	if err != nil {
		return err
	}
	nMIDs, err := org.relatedMIDs(nType, category)
	if err != nil {
		return err
	}

	// 哪些用户失去了对当前用户的访问
	delMIDs := vset(set(oMIDs).Difference(set(nMIDs)).ToSlice())
	addMIDs := vset(set(nMIDs).Difference(set(oMIDs)).ToSlice())

	err = org.addAuditLog(AuditActionAdd, category, addMIDs, AuditContent{entry})
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionDel, category, delMIDs, AuditContent{entry})
}

func (org *Organization) addAuditLog(action, category string, mids []string, content []map[string]interface{}) error {

	if len(mids) == 0 || len(content) == 0 {
		return nil
	}

	for _, c := range content {
		delete(c, `rbacType`)
		delete(c, `rbacRole`)
	}

	json, err := json.Marshal(content)
	if err != nil {
		return err
	}

	id := generateNewID()
	aq := ldap.NewAddRequest(org.dn(id, audit))

	aq.Attribute(`objectClass`, []string{`audit`, `top`})
	aq.Attribute(`action`, []string{action})
	aq.Attribute(`category`, []string{category})
	aq.Attribute(`mid`, mids)
	aq.Attribute(`auditContent`, []string{string(json)})

	err = org.l.Add(aq)
	if err != nil {
		return err
	}
	return nil
}

func (org *Organization) fetchAuditLog(memberID, lastedLogID string) ([]map[string]interface{}, error) {
	filter := fmt.Sprintf(`(&(mid=%s)(createTimestamp>=%s))`, memberID, lastedLogID)
	sq := ldap.NewSearchRequest(org.parentDN(audit),
		ldap.ScopeSingleLevel, ldap.DerefAlways, 0, 0, false, filter,
		[]string{`createTimestamp`, `action`, `auditContent`, `category`}, nil)
	sr, err := org.l.Search(sq)
	if err != nil {
		return nil, err
	}
	var result []map[string]interface{}
	for _, e := range sr.Entries {
		var content []map[string]interface{}
		err := json.Unmarshal(e.GetRawAttributeValue(`auditContent`), &content)
		if err != nil {
			continue
		}
		log := make(map[string]interface{}, 0)
		log[`content`] = content
		log[`createTimestamp`] = e.GetAttributeValue(`createTimestamp`)
		log[`action`] = e.GetAttributeValue(`action`)
		log[`category`] = e.GetAttributeValue(`category`)
		result = append(result, log)
	}

	// TODO 这里需要对 logs 根据 timestamp 排序
	return result, nil
}

func (org *Organization) IsValidVersion(version string) bool {
	return org.latestResetVersion == `` || version > org.latestResetVersion
}

func (org *Organization) GenerateChangeLogFromVersion(version string, mid string) (string, []map[string]interface{}, error) {
	if org.IsValidVersion(version) {
		logs, err := org.fetchAuditLog(mid, version)
		return newTimeStampVersion(), logs, err
	} else {
		return ``, nil, fmt.Errorf(`version invalid. latest reset version %s`, org.latestResetVersion)
	}
}
<commit_msg>Remove `dn` from audit log.<commit_after>package organization

import (
	"encoding/json"
	"fmt"

	"github.com/deckarep/golang-set"
	ldap "gopkg.in/ldap.v2"
)

const (
	AuditActionAdd    = `add`
	AuditActionDel    = `delete`
	AuditActionUpdate = `update`
)

const (
	AuditCategoryUnit   = `department`
	AuditCategoryMember = `member`
)

type AuditContent []map[string]interface{}

// 更新权限和角色时会影响整个数据视图
func (org *Organization) refreshRBACIfNeeded(o, n []string) {
	if set(o).Equal(set(n)) { // 没有变化
		return
	}
	err := org.RefreshRBAC() // 更新RBAC缓存
	if err == nil {
		func(dn string) {
			sq := ldap.NewSearchRequest(dn, ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, true, `(objectClass=audit)`, nil, nil)
			sr, _ := org.l.Search(sq)
			for _, r := range sr.Entries {
				org.l.Del(ldap.NewDelRequest(r.DN, nil))
			}
		}(org.parentDN(audit))
		org.latestResetVersion = newTimeStampVersion()
	} else {
		fmt.Print(`err: %s`, err.Error())
	}
}

// utils
func set(v []string) mapset.Set {
	set := mapset.NewSet()
	for _, s := range v {
		set.Add(s)
	}
	return set
}

func vset(ss []interface{}) []string {
	var strs []string
	for _, s := range ss {
		strs = append(strs, s.(string))
	}
	return strs
}

func (org *Organization) relatedMIDs(tid, category string) ([]string, error) {
	var mids []string
	var err error
	if category == AuditCategoryMember {
		mids, err = org.MemberIDsByTypeIDs([]string{tid})
	} else {
		rids := org.rbacx.RoleIDsByTypeID(tid)
		if len(rids) == 0 {
			mids = make([]string, 0)
		} else {
			mids, err = org.MemberIDsByRoleIDs(rids)
		}
	}
	return mids, err
}

func (org *Organization) logAddMember(member map[string]interface{}) error {
	return org.typeAdded(AuditCategoryMember, member)
}

func (org *Organization) logModifyMember(oMember, nMember map[string]interface{}) error {

	// 1. 判断用户角色有没有变化
	oRole := set(oMember[`rbacRole`].([]string))
	nRole := set(nMember[`rbacRole`].([]string))
	if !oRole.Equal(nRole) { // 用户的角色发生了变化
		// 计算发生变化前后的`Type`变化y
		oType := set(org.rbacx.MatchedTypes(oMember[`rbacRole`].([]string)))
		nType := set(org.rbacx.MatchedTypes(nMember[`rbacRole`].([]string)))

		addTypes := oType.Difference(nType)

		// 下面这2块代码，需要重构

		// 获取增加的用户ID
		mids, err := org.MemberIDsByTypeIDs(vset(addTypes.ToSlice()))
		if err != nil {
			return err
		}
		members, err := org.MemberByIDs(mids, false, false)
		if err != nil {
			return err
		}
		err = org.addAuditLog(AuditActionAdd, AuditCategoryMember, []string{nMember[`id`].(string)}, members)
		if err != nil {
			return err
		}

		// 获取删除的用户ID
		delTypes := nType.Difference(oType)

		mids, err = org.MemberIDsByTypeIDs(vset(delTypes.ToSlice()))
		if err != nil {
			return err
		}
		members, err = org.MemberByIDs(mids, false, false)
		if err != nil {
			return err
		}
		err = org.addAuditLog(AuditActionDel, AuditCategoryMember, []string{nMember[`id`].(string)}, members)
		if err != nil {
			return err
		}

		// 写两条日志
		// 1. 这个member 通讯录新增加了那几条访问权限
		// ADD|member[`id`].(string)|-----
		// 2. 这个member 删除了那几条访问权限
		// DEL|member[`id`].(string)|------
	}

	oType := oMember[`rbacType`].(string)
	nType := nMember[`rbacType`].(string)
	if oType != nType { // 修改了用户的类型
		org.typeChange(oType, nType, AuditCategoryMember, nMember)
		// 写日志
		// ADD|add|member
		// DEL|del|member
	}

	mids, err := org.relatedMIDs(nMember[`rbacType`].(string), AuditCategoryMember)
	if err != nil {
		return err
	}

	return org.addAuditLog(AuditActionUpdate, AuditCategoryMember, mids, AuditContent{nMember})
}

func (org *Organization) logDelMember(id string, relatedMIDs []string) error {
	return org.addAuditLog(AuditActionDel, AuditCategoryMember, relatedMIDs, AuditContent{
		map[string]interface{}{
			`id`: id,
		},
	})
}

func (org *Organization) logAddUnit(unit map[string]interface{}) error {
	return org.typeAdded(AuditCategoryUnit, unit)
}

func (org *Organization) logModifyUnit(oUnit, nUnit map[string]interface{}) error {
	oType := oUnit[`rbacType`].(string)
	nType := nUnit[`rbacType`].(string)
	if oType != nType {
		org.typeChange(oType, nType, AuditCategoryUnit, nUnit)
	}
	mids, err := org.relatedMIDs(nType, AuditCategoryUnit)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionUpdate, AuditCategoryUnit, mids, AuditContent{nUnit})
}

func (org *Organization) logDelUnit(id string, tid string) error {
	mids, err := org.relatedMIDs(tid, AuditCategoryUnit)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionDel, AuditCategoryUnit, mids, AuditContent{
		map[string]interface{}{
			`id`: id,
		},
	})
}

func (org *Organization) typeAdded(category string, entry map[string]interface{}) error {
	mids, err := org.relatedMIDs(entry[`rbacType`].(string), category)
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionAdd, category, mids, AuditContent{entry})
}

func (org *Organization) typeChange(oType, nType string, category string, entry map[string]interface{}) error {
	oMIDs, err := org.relatedMIDs(oType, category)
	if err != nil {
		return err
	}
	nMIDs, err := org.relatedMIDs(nType, category)
	if err != nil {
		return err
	}

	// 哪些用户失去了对当前用户的访问
	delMIDs := vset(set(oMIDs).Difference(set(nMIDs)).ToSlice())
	addMIDs := vset(set(nMIDs).Difference(set(oMIDs)).ToSlice())

	err = org.addAuditLog(AuditActionAdd, category, addMIDs, AuditContent{entry})
	if err != nil {
		return err
	}
	return org.addAuditLog(AuditActionDel, category, delMIDs, AuditContent{entry})
}

func (org *Organization) addAuditLog(action, category string, mids []string, content []map[string]interface{}) error {

	if len(mids) == 0 || len(content) == 0 {
		return nil
	}

	for _, c := range content {
		delete(c, `rbacType`)
		delete(c, `rbacRole`)
		delete(c, `dn`)
	}

	json, err := json.Marshal(content)
	if err != nil {
		return err
	}

	id := generateNewID()
	aq := ldap.NewAddRequest(org.dn(id, audit))

	aq.Attribute(`objectClass`, []string{`audit`, `top`})
	aq.Attribute(`action`, []string{action})
	aq.Attribute(`category`, []string{category})
	aq.Attribute(`mid`, mids)
	aq.Attribute(`auditContent`, []string{string(json)})

	err = org.l.Add(aq)
	if err != nil {
		return err
	}
	return nil
}

func (org *Organization) fetchAuditLog(memberID, lastedLogID string) ([]map[string]interface{}, error) {
	filter := fmt.Sprintf(`(&(mid=%s)(createTimestamp>=%s))`, memberID, lastedLogID)
	sq := ldap.NewSearchRequest(org.parentDN(audit),
		ldap.ScopeSingleLevel, ldap.DerefAlways, 0, 0, false, filter,
		[]string{`createTimestamp`, `action`, `auditContent`, `category`}, nil)
	sr, err := org.l.Search(sq)
	if err != nil {
		return nil, err
	}
	var result []map[string]interface{}
	for _, e := range sr.Entries {
		var content []map[string]interface{}
		err := json.Unmarshal(e.GetRawAttributeValue(`auditContent`), &content)
		if err != nil {
			continue
		}
		log := make(map[string]interface{}, 0)
		log[`content`] = content
		log[`createTimestamp`] = e.GetAttributeValue(`createTimestamp`)
		log[`action`] = e.GetAttributeValue(`action`)
		log[`category`] = e.GetAttributeValue(`category`)
		result = append(result, log)
	}

	// TODO 这里需要对 logs 根据 timestamp 排序
	return result, nil
}

func (org *Organization) IsValidVersion(version string) bool {
	return org.latestResetVersion == `` || version > org.latestResetVersion
}

func (org *Organization) GenerateChangeLogFromVersion(version string, mid string) (string, []map[string]interface{}, error) {
	if org.IsValidVersion(version) {
		logs, err := org.fetchAuditLog(mid, version)
		return newTimeStampVersion(), logs, err
	} else {
		return ``, nil, fmt.Errorf(`version invalid. latest reset version %s`, org.latestResetVersion)
	}
}
<|endoftext|>


<commit_before>package contour

import "fmt"

type UpdateErr struct {
	k    string
	typ  SettingType
	slug string
}

func (e UpdateErr) Error() string {
	return fmt.Sprintf("update of %s failed: %s", e.k, e.slug)
}

type BasicUpdateErr struct {
	typ SettingType
	k   string
}

func (e BasicUpdateErr) Error() string {
	return fmt.Sprintf("%s: %s settings cannot be updated by a basic update", e.k, e.typ)
}

type CoreUpdateErr struct {
	typ SettingType
	k   string
}

func (e CoreUpdateErr) Error() string {
	return fmt.Sprintf("%s: %s settings cannot be updated by a core update", e.k, e.typ)
}

// Only non-core settings are updateable. This assumes that the lock has
// already been obtained by the caller.
func (s *Settings) update(k string, v interface{}) error {
	// if can't update, a false will also return an error explaining why.
	//can := s.canUpdate(typ, k)
	//if !can {
	//	return fmt.Errorf("%se: cannot update using %s", typ)
	//}
	val, _ := s.settings[k]
	val.Value = v
	s.settings[k] = val
	return nil
}

// UpdateBool updates a bool setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateBool(k string, v bool) error { return settings.UpdateBool(k, v) }
func (s *Settings) UpdateBool(k string, v bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateBool(k, v)
}

func (s *Settings) updateBool(k string, v bool) error {
	return s.update(k, v)
}

// UpdateInt updates an int setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateInt(k string, v int) error { return settings.UpdateInt(k, v) }
func (s *Settings) UpdateInt(k string, v int) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateInt(k, v)
}

func (s *Settings) updateInt(k string, v int) error {
	return s.update(k, v)
}

// UpdateInt64 updates an int64 setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateInt64(k string, v int64) error { return settings.UpdateInt64(k, v) }
func (s *Settings) UpdateInt64(k string, v int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateInt64(k, v)
}

func (s *Settings) updateInt64(k string, v int64) error {
	return s.update(k, v)
}

// UpdateString updates a string setting. If the setting k doesn't exist, both
// a false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateString(k string, v string) error { return settings.UpdateString(k, v) }
func (s *Settings) UpdateString(k, v string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateString(k, v)
}

func (s *Settings) updateString(k, v string) error {
	return s.update(k, v)
}

// canUpdate checks to see if the passed setting key is updateable. If the key
// doesn't exist, both a false and a SettingNotFoundErr will be returned. If
// the setting is not updateable, both a false and an Update type specific err
// will be returned, e.g. CoreUpdateErr. This assumes that the lock has already
// been obtained by the caller.
//
// core settings can never be set.
// settings that are not a ConfFileVar, EnvVar, and Flag, i.e. a Basic setting,
//   cannot be set by a configuration file, environment variable, or a flag as
//   it has not explicitly been exposed to them. They can only be set by the
//   application code, i.e. non-contour code.
//
// all other settings, for whatever reason, may be any combination of types,
// e.g. it could be a conf var and a flag. Settings of type conf var, env var
// or flag can be set if a higher precedence type has not already been set.
//
// examples:
//    a setting that IsConfFileVar && IsFlag can be set by a ConfFile if both
//    flagsParsed and confFileVarsSet are False.
//
//    a setting that IsConfFileVar && IsFlag can be set by flags if both
//    flagsParsed is False.
//
// k is the key of the setting and typ is the type of update that is being
// checked, e.g. an update from an env var will have a typ of EnvVar.
func (s *Settings) canUpdate(typ SettingType, k string) (can bool, err error) {
	// See if the key exists, if it doesn't already exist, it can't be updated.
	v, ok := s.settings[k]
	if !ok {
		return false, SettingNotFoundErr{name: k}
	}
	// See if there are any settings that prevent it from being overridden.  Core and
	// environment variables are never settable. Core must be set during registration.
	if v.IsCore {
		return false, CoreUpdateErr{k: k}
	}
	// regular, Basic, settings are always updateable as long as this is a Basic
	// update.
	if typ == Basic {
		if !v.IsConfFileVar && !v.IsEnv && !v.IsFlag {
			return true, nil
		}
		var t SettingType
		if v.IsFlag {
			t = Flag
			goto basicErr
		}
		if v.IsEnv {
			t = Env
			goto basicErr
		}
		t = ConfFileVar
	basicErr:
		return false, BasicUpdateErr{typ: t, k: k}
	}

	// check by update type
	switch typ {
	case ConfFileVar:
		if v.IsConfFileVar {
			if !s.confFileVarsSet && !s.envSet && !s.flagsParsed {
				return true, nil
			}
			var set string
			if s.flagsParsed {
				set = "flags"
				goto confErr
			}
			if s.envSet {
				set = "env vars"
				goto confErr
			}
			set = "the configuration file"
		confErr:
			return false, UpdateErr{k: k, slug: fmt.Sprintf("already set from %s", set)}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not a %s", ConfFileVar)}
	case Env:
		if v.IsEnv {
			if !s.envSet && !s.flagsParsed {
				return true, nil
			}
			var set string
			if s.flagsParsed {
				set = "flags"
			} else {
				set = "env vars"
			}
			return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("already set from %s", set)}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not an %s", Env)}
	case Flag:
		if v.IsFlag {
			if !s.flagsParsed {
				return true, nil
			}
			return false, UpdateErr{typ: typ, k: k, slug: "already set from flags"}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not a %s", Flag)}
	}
	// If it was not one of the above, we return a false. It's better to not allow
	// an update if the case isn't handled than be too permissive. Getting here is
	// a sign that something within this func should be updated and/or fixed.
	return false, UpdateErr{typ: typ, k: k, slug: "invalid update type"}
}
<commit_msg>better document Update related stuff<commit_after>package contour

import "fmt"

// UpdateErr is any error that happens on an update that isn't one of the
// following: SettingNotFoundErr, BasicUpdateErr, or CoreUpdateErr.
type UpdateErr struct {
	k    string
	typ  SettingType
	slug string
}

func (e UpdateErr) Error() string {
	return fmt.Sprintf("update of %s failed: %s", e.k, e.slug)
}

// BasicUpdateErr happens when there's an attempt to do a basic update on a
// non-basic setting.
type BasicUpdateErr struct {
	typ SettingType
	k   string
}

func (e BasicUpdateErr) Error() string {
	return fmt.Sprintf("%s: %s settings cannot be updated by a basic update", e.k, e.typ)
}

// CoreUpdateErr happens when there's an attempt to do a core update, which
// should never occur, or update a Core setting.
type CoreUpdateErr struct {
	typ SettingType
	k   string
}

func (e CoreUpdateErr) Error() string {
	return fmt.Sprintf("%s: %s settings cannot be updated by a core update", e.k, e.typ)
}

// Only non-core settings are updateable. This assumes that the lock has
// already been obtained by the caller.
func (s *Settings) update(k string, v interface{}) error {
	// if can't update, a false will also return an error explaining why.
	//can := s.canUpdate(typ, k)
	//if !can {
	//	return fmt.Errorf("%se: cannot update using %s", typ)
	//}
	val, _ := s.settings[k]
	val.Value = v
	s.settings[k] = val
	return nil
}

// UpdateBool updates a bool setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateBool(k string, v bool) error { return settings.UpdateBool(k, v) }

// UpdateBool updates a bool setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func (s *Settings) UpdateBool(k string, v bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateBool(k, v)
}

// this assumes the lock is held by the caller.
func (s *Settings) updateBool(k string, v bool) error {
	return s.update(k, v)
}

// UpdateInt updates an int setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateInt(k string, v int) error { return settings.UpdateInt(k, v) }

// UpdateInt updates an int setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func (s *Settings) UpdateInt(k string, v int) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateInt(k, v)
}

// this assumes the lock is held by the caller.
func (s *Settings) updateInt(k string, v int) error {
	return s.update(k, v)
}

// UpdateInt64 updates an int64 setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateInt64(k string, v int64) error { return settings.UpdateInt64(k, v) }

// UpdateInt64 updates an int64 setting. If the setting k doesn't exist, both a
// false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func (s *Settings) UpdateInt64(k string, v int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateInt64(k, v)
}

// this assumes the lock is held by the caller.
func (s *Settings) updateInt64(k string, v int64) error {
	return s.update(k, v)
}

// UpdateString updates a string setting. If the setting k doesn't exist, both
// a false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func UpdateString(k string, v string) error { return settings.UpdateString(k, v) }

// UpdateString updates a string setting. If the setting k doesn't exist, both
// a false and a SettingNotFoundErr will be returned. If the setting k is not
// updateable, both a false and one of the following errors will be returned:
// CoreUpdateErr. BasicUpdateErr, or UpdateErr.
func (s *Settings) UpdateString(k, v string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.updateString(k, v)
}

// this assumes the lock is held by the caller.
func (s *Settings) updateString(k, v string) error {
	return s.update(k, v)
}

// canUpdate checks to see if the passed setting key is updateable. If the key
// doesn't exist, both a false and a SettingNotFoundErr will be returned. If
// the setting is not updateable, both a false and an Update type specific err
// will be returned, e.g. CoreUpdateErr. This assumes that the lock has already
// been obtained by the caller.
//
// core settings can never be set.
// settings that are not a ConfFileVar, EnvVar, and Flag, i.e. a Basic setting,
//   cannot be set by a configuration file, environment variable, or a flag as
//   it has not explicitly been exposed to them. They can only be set by the
//   application code, i.e. non-contour code.
//
// all other settings, for whatever reason, may be any combination of types,
// e.g. it could be a conf var and a flag. Settings of type conf var, env var
// or flag can be set if a higher precedence type has not already been set.
//
// examples:
//    a setting that IsConfFileVar && IsFlag can be set by a ConfFile if both
//    flagsParsed and confFileVarsSet are False.
//
//    a setting that IsConfFileVar && IsFlag can be set by flags if both
//    flagsParsed is False.
//
// k is the key of the setting and typ is the type of update that is being
// checked, e.g. an update from an env var will have a typ of EnvVar.
func (s *Settings) canUpdate(typ SettingType, k string) (can bool, err error) {
	// See if the key exists, if it doesn't already exist, it can't be updated.
	v, ok := s.settings[k]
	if !ok {
		return false, SettingNotFoundErr{name: k}
	}
	// See if there are any settings that prevent it from being overridden.  Core and
	// environment variables are never settable. Core must be set during registration.
	if v.IsCore {
		return false, CoreUpdateErr{k: k}
	}
	// regular, Basic, settings are always updateable as long as this is a Basic
	// update.
	if typ == Basic {
		if !v.IsConfFileVar && !v.IsEnv && !v.IsFlag {
			return true, nil
		}
		var t SettingType
		if v.IsFlag {
			t = Flag
			goto basicErr
		}
		if v.IsEnv {
			t = Env
			goto basicErr
		}
		t = ConfFileVar
	basicErr:
		return false, BasicUpdateErr{typ: t, k: k}
	}

	// check by update type
	switch typ {
	case ConfFileVar:
		if v.IsConfFileVar {
			if !s.confFileVarsSet && !s.envSet && !s.flagsParsed {
				return true, nil
			}
			var set string
			if s.flagsParsed {
				set = "flags"
				goto confErr
			}
			if s.envSet {
				set = "env vars"
				goto confErr
			}
			set = "the configuration file"
		confErr:
			return false, UpdateErr{k: k, slug: fmt.Sprintf("already set from %s", set)}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not a %s", ConfFileVar)}
	case Env:
		if v.IsEnv {
			if !s.envSet && !s.flagsParsed {
				return true, nil
			}
			var set string
			if s.flagsParsed {
				set = "flags"
			} else {
				set = "env vars"
			}
			return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("already set from %s", set)}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not an %s", Env)}
	case Flag:
		if v.IsFlag {
			if !s.flagsParsed {
				return true, nil
			}
			return false, UpdateErr{typ: typ, k: k, slug: "already set from flags"}
		}
		return false, UpdateErr{typ: typ, k: k, slug: fmt.Sprintf("is not a %s", Flag)}
	}
	// If it was not one of the above, we return a false. It's better to not allow
	// an update if the case isn't handled than be too permissive. Getting here is
	// a sign that something within this func should be updated and/or fixed.
	return false, UpdateErr{typ: typ, k: k, slug: "invalid update type"}
}
<|endoftext|>


<commit_before>package chevalier

import (
	zmq "github.com/pebbe/zmq4"
)

// GetFullContents requests a contents list for origin from a Vaultaire
// readerd listening on endpoint, returning it as a DataSourceBurst.
func GetContents(endpoint, origin string) (*DataSourceBurst, error) {
	sock, err := zmq.NewSocket(zmq.REQ)
	if err != nil {
		return nil, err
	}
	err = sock.Connect(endpoint)
	if err != nil {
		return nil, err
	}
	_, err = sock.Send(origin, 0)
	if err != nil {
		return nil, err
	}
	b, err := sock.RecvBytes(0)
	if err != nil {
		return nil, err
	}
	burst, err := UnmarshalSourceBurst(b)
	if err != nil {
		return nil, err
	}
	return burst, nil
}
<commit_msg>Send the right byte to request contents list<commit_after>package chevalier

import (
	zmq "github.com/pebbe/zmq4"
)

const (
	ContentsListRequest = 0x00
)

// GetContents list for origin from a Vaultaire
// readerd listening on endpoint, returning it as a DataSourceBurst.
func GetContents(endpoint, origin string) (*DataSourceBurst, error) {
	sock, err := zmq.NewSocket(zmq.REQ)
	if err != nil {
		return nil, err
	}
	err = sock.Connect(endpoint)
	if err != nil {
		return nil, err
	}
	_, err = sock.Send(origin, ContentsListRequest)
	if err != nil {
		return nil, err
	}
	b, err := sock.RecvBytes(0)
	if err != nil {
		return nil, err
	}
	burst, err := UnmarshalSourceBurst(b)
	if err != nil {
		return nil, err
	}
	return burst, nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dickeyxxx/golock"
	"github.com/franela/goreq"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Hidden:      true,
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Flags:       []Flag{{Name: "background", Hidden: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = Channel
		}
		t := "foreground"
		if ctx.Flags["background"] == true {
			t = "background"
		}
		Update(channel, t)
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport.TLSClientConfig = &tls.Config{RootCAs: getCACerts()}
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}

// Update updates the CLI and plugins
func Update(channel string, t string) {
	done := make(chan bool)
	go func() {
		if !IsUpdateNeeded(t) {
			return
		}
		touchAutoupdateFile()
		updateCLI(channel)
		updatePlugins(t)
		done <- true
	}()
	select {
	case <-time.After(time.Second * 120):
		Errln("Timed out while updating")
	case <-done:
	}
}

func updatePlugins(t string) {
	updated := false
	plugins := PluginNames()
	if len(plugins) == 0 {
		return
	}
	Err("updating plugins... ")
	if t == "foreground" || t == "block" {
		b, _ := node.UpdatePackages()
		if len(b) > 0 {
			updated = true
		}
	} else {
		for _, name := range plugins {
			lockfile := updateLockPath + "." + name
			LogIfError(golock.Lock(lockfile))
			b, _ := node.UpdatePackage(name)
			LogIfError(golock.Unlock(lockfile))
			if len(b) > 0 {
				updated = true
			}
		}
	}
	Errln("done")
	if updated {
		Err("rebuilding plugins cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	}
}

func updateCLI(channel string) {
	manifest, err := getUpdateManifest(channel)
	if err != nil {
		Warn("Error updating CLI")
		PrintError(err)
		return
	}
	if manifest.Version == Version && manifest.Channel == Channel {
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out\n", Version, manifest.Version)
		return
	}
	LogIfError(golock.Lock(updateLockPath))
	defer golock.Unlock(updateLockPath)
	Errf("updating v4 CLI to %s (%s)... ", manifest.Version, manifest.Channel)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", build.URL); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != build.Sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	os.Rename(binPath, binPath+".old")
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
	os.Remove(binPath + ".old")
	Errln("done")
}

// IsUpdateNeeded checks if an update is available
func IsUpdateNeeded(t string) bool {
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	if t == "background" {
		return time.Since(f.ModTime()) > 4*time.Hour
	} else if t == "block" {
		return time.Since(f.ModTime()) > 168*time.Hour
	}
	return true
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) (*manifest, error) {
	res, err := goreq.Request{
		Uri:       "https://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return nil, err
	}
	var m manifest
	res.Body.FromJsonTo(&m)
	return &m, nil
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	res, err := goreq.Request{
		Uri:       url + ".gz",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return err
	}
	defer res.Body.Close()
	_, err = io.Copy(out, res.Body)
	if err != nil {
		return err
	}
	return out.Close()
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

// TriggerBackgroundUpdate will trigger an update to the client in the background
func TriggerBackgroundUpdate() {
	if IsUpdateNeeded("background") {
		exec.Command(binPath, "update", "--background").Start()
	}
}
<commit_msg>fixed bug with 120 second timeouts when no update<commit_after>package main

import (
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dickeyxxx/golock"
	"github.com/franela/goreq"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Hidden:      true,
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Flags:       []Flag{{Name: "background", Hidden: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = Channel
		}
		t := "foreground"
		if ctx.Flags["background"] == true {
			t = "background"
		}
		Update(channel, t)
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport.TLSClientConfig = &tls.Config{RootCAs: getCACerts()}
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}

// Update updates the CLI and plugins
func Update(channel string, t string) {
	if !IsUpdateNeeded(t) {
		return
	}
	done := make(chan bool)
	go func() {
		touchAutoupdateFile()
		updateCLI(channel)
		updatePlugins(t)
		done <- true
	}()
	select {
	case <-time.After(time.Second * 120):
		Errln("Timed out while updating")
	case <-done:
	}
}

func updatePlugins(t string) {
	updated := false
	plugins := PluginNames()
	if len(plugins) == 0 {
		return
	}
	Err("updating plugins... ")
	if t == "foreground" || t == "block" {
		b, _ := node.UpdatePackages()
		if len(b) > 0 {
			updated = true
		}
	} else {
		for _, name := range plugins {
			lockfile := updateLockPath + "." + name
			LogIfError(golock.Lock(lockfile))
			b, _ := node.UpdatePackage(name)
			LogIfError(golock.Unlock(lockfile))
			if len(b) > 0 {
				updated = true
			}
		}
	}
	Errln("done")
	if updated {
		Err("rebuilding plugins cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	}
}

func updateCLI(channel string) {
	manifest, err := getUpdateManifest(channel)
	if err != nil {
		Warn("Error updating CLI")
		PrintError(err)
		return
	}
	if manifest.Version == Version && manifest.Channel == Channel {
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out\n", Version, manifest.Version)
		return
	}
	LogIfError(golock.Lock(updateLockPath))
	defer golock.Unlock(updateLockPath)
	Errf("updating v4 CLI to %s (%s)... ", manifest.Version, manifest.Channel)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", build.URL); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != build.Sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	os.Rename(binPath, binPath+".old")
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
	os.Remove(binPath + ".old")
	Errln("done")
}

// IsUpdateNeeded checks if an update is available
func IsUpdateNeeded(t string) bool {
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	if t == "background" {
		return time.Since(f.ModTime()) > 4*time.Hour
	} else if t == "block" {
		return time.Since(f.ModTime()) > 168*time.Hour
	}
	return true
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) (*manifest, error) {
	res, err := goreq.Request{
		Uri:       "https://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return nil, err
	}
	var m manifest
	res.Body.FromJsonTo(&m)
	return &m, nil
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	res, err := goreq.Request{
		Uri:       url + ".gz",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return err
	}
	defer res.Body.Close()
	_, err = io.Copy(out, res.Body)
	if err != nil {
		return err
	}
	return out.Close()
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

// TriggerBackgroundUpdate will trigger an update to the client in the background
func TriggerBackgroundUpdate() {
	if IsUpdateNeeded("background") {
		exec.Command(binPath, "update", "--background").Start()
	}
}
<|endoftext|>


<commit_before>package app

import (
	"html/template"
	"net/http"

	"appengine"

	"github.com/twinj/uuid"
	"github.com/murrekatt/go-s3presigned-post"
)

const (
	regionName = "FILL-ME-IN"
	bucketName = "FILL-ME-IN"
	accessKeyID = "FILL-ME-IN"
	secretAccessKey = "FILL-ME-IN"
)

// HTML form for pre-signed POST
const htmlDocument = `

  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

AWS S3 File Uploader

  <form action="{{.Action}}" method="post" enctype="multipart/form-data">
    <input type="hidden" name="key" value="{{.Key}}" />
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="X-Amz-Credential" value="{{.Credential}}" />
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="hidden" name="X-Amz-Date" value="{{.Date}}" />

Tags

    <input type="input"  name="x-amz-meta-tag" value="" size="50" />

    <input type="hidden" name="Policy" value="{{.Policy}}" />
    <input type="hidden" name="X-Amz-Signature" value="{{.Signature}}" />

File

    <input type="file"   name="file" />

    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>

`

// Handles upload requests.
func handleUpload(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	// parse HTML template
	t, err := template.New("presign").Parse(htmlDocument)
	if err != nil {
		c.Errorf("Error parsing template: %s", err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// a unique key to upload
	id := uuid.NewV4().String()

	// AWS S3 credentials
	creds := &s3.Credentials{
		Region: regionName,
		Bucket: bucketName,
		AccessKeyID: accessKeyID,
		SecretAccessKey: secretAccessKey,
	}

	// create pre-signed POST details
	post, err := s3.NewPresignedPOST(id, creds)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// render HTML form
	t.Execute(w, post)
}
<commit_msg>Removes meta from form as lib changed.<commit_after>package app

import (
	"html/template"
	"net/http"

	"appengine"

	"github.com/twinj/uuid"
	"github.com/murrekatt/go-s3presigned-post"
)

const (
	regionName = "FILL-ME-IN"
	bucketName = "FILL-ME-IN"
	accessKeyID = "FILL-ME-IN"
	secretAccessKey = "FILL-ME-IN"
)

// HTML form for pre-signed POST
const htmlDocument = `

  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

AWS S3 File Uploader

  <form action="{{.Action}}" method="post" enctype="multipart/form-data">
    <input type="hidden" name="key" value="{{.Key}}" />
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="X-Amz-Credential" value="{{.Credential}}" />
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="hidden" name="X-Amz-Date" value="{{.Date}}" />

Tags

    <input type="hidden" name="Policy" value="{{.Policy}}" />
    <input type="hidden" name="X-Amz-Signature" value="{{.Signature}}" />

File

    <input type="file"   name="file" />

    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>

`

// Handles upload requests.
func handleUpload(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	// parse HTML template
	t, err := template.New("presign").Parse(htmlDocument)
	if err != nil {
		c.Errorf("Error parsing template: %s", err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// a unique key to upload
	id := uuid.NewV4().String()

	// AWS S3 credentials
	creds := &s3.Credentials{
		Region: regionName,
		Bucket: bucketName,
		AccessKeyID: accessKeyID,
		SecretAccessKey: secretAccessKey,
	}

	// create pre-signed POST details
	post, err := s3.NewPresignedPOST(id, creds)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// render HTML form
	t.Execute(w, post)
}
<|endoftext|>