<commit_before>// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/pkg/sysinfo"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fillPlatformInfo fills the platform related info.
func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
	v.CgroupDriver = daemon.getCgroupDriver()
	v.CgroupVersion = "1"
	if sysInfo.CgroupUnified {
		v.CgroupVersion = "2"
	}

	v.MemoryLimit = sysInfo.MemoryLimit
	v.SwapLimit = sysInfo.SwapLimit
	v.KernelMemory = sysInfo.KernelMemory
	v.KernelMemoryTCP = sysInfo.KernelMemoryTCP
	v.OomKillDisable = sysInfo.OomKillDisable
	v.CPUCfsPeriod = sysInfo.CPUCfs
	v.CPUCfsQuota = sysInfo.CPUCfs
	v.CPUShares = sysInfo.CPUShares
	v.CPUSet = sysInfo.Cpuset
	v.PidsLimit = sysInfo.PidsLimit
	v.Runtimes = daemon.configStore.GetAllRuntimes()
	v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
	v.InitBinary = daemon.configStore.GetInitPath()

	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, _, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
			v.RuncCommit.ID = "N/A"
		} else {
			v.RuncCommit.ID = commit
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
		v.RuncCommit.ID = "N/A"
	}

	// runc is now shipped as a separate package. Set "expected" to same value
	// as "ID" to prevent clients from reporting a version-mismatch
	v.RuncCommit.Expected = v.RuncCommit.ID

	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.ContainerdCommit.ID = rv.Revision
	} else {
		logrus.Warnf("failed to retrieve containerd version: %v", err)
		v.ContainerdCommit.ID = "N/A"
	}

	// containerd is now shipped as a separate package. Set "expected" to same
	// value as "ID" to prevent clients from reporting a version-mismatch
	v.ContainerdCommit.Expected = v.ContainerdCommit.ID

	// TODO is there still a need to check the expected version for tini?
	// if not, we can change this, and just set "Expected" to v.InitCommit.ID
	v.InitCommit.Expected = dockerversion.InitCommitID

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if _, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
			v.InitCommit.ID = "N/A"
		} else {
			v.InitCommit.ID = commit
			if len(dockerversion.InitCommitID) > len(commit) {
				v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
			}
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
		v.InitCommit.ID = "N/A"
	}

	if v.CgroupDriver == cgroupNoneDriver {
		if v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.")
		} else {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.")
		}
	} else {
		if !v.MemoryLimit {
			v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
		}
		if !v.SwapLimit {
			v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
		}
		if !v.KernelMemoryTCP && v.CgroupVersion == "1" {
			// kernel memory is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No kernel memory TCP limit support")
		}
		if !v.OomKillDisable && v.CgroupVersion == "1" {
			// oom kill disable is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
		}
		if !v.CPUCfsQuota {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
		}
		if !v.CPUCfsPeriod {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
		}
		if !v.CPUShares {
			v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
		}
		if !v.CPUSet {
			v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
		}
		if v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: Support for cgroup v2 is experimental")
		}
		// TODO add fields for these options in types.Info
		if !sysInfo.BlkioWeight && v.CgroupVersion == "2" {
			// blkio weight is not available on cgroup v1 since kernel 5.0.
			// Warning is not printed on cgroup v1, because there is no action user can take.
			// On cgroup v2, blkio weight is implemented using io.weight
			v.Warnings = append(v.Warnings, "WARNING: No io.weight support")
		}
		if !sysInfo.BlkioWeightDevice && v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: No io.weight (per device) support")
		}
		if !sysInfo.BlkioReadBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (rbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_bps_device support")
			}
		}
		if !sysInfo.BlkioWriteBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_bps_device support")
			}
		}
		if !sysInfo.BlkioReadIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (riops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_iops_device support")
			}
		}
		if !sysInfo.BlkioWriteIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wiops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_iops_device support")
			}
		}
	}
	if !v.IPv4Forwarding {
		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
	}
	if !v.BridgeNfIptables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
	}
	if !v.BridgeNfIP6tables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
	}
}

func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.Components = append(v.Components, types.ComponentVersion{
			Name:    "containerd",
			Version: rv.Version,
			Details: map[string]string{
				"GitCommit": rv.Revision,
			},
		})
	}

	defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
	defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, ver, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    defaultRuntime,
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
	}

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if ver, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    filepath.Base(defaultInitBinary),
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
	}
}

func fillDriverWarnings(v *types.Info) {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Data loop file" {
			msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+
				"strongly discouraged for production use.\n         "+
				"Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver)

			v.Warnings = append(v.Warnings, msg)
			continue
		}
		if pair[0] == "Supports d_type" && pair[1] == "false" {
			backingFs := getBackingFs(v)

			msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs)
			if backingFs == "xfs" {
				msg += "         Reformat the filesystem with ftype=1 to enable d_type support.\n"
			}
			msg += "         Running without d_type support will not be supported in future releases."

			v.Warnings = append(v.Warnings, msg)
			continue
		}
	}
}

func getBackingFs(v *types.Info) string {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Backing Filesystem" {
			return pair[1]
		}
	}
	return ""
}

// parseInitVersion parses a Tini version string, and extracts the "version"
// and "git commit" from the output.
//
// Output example from `docker-init --version`:
//
//     tini version 0.18.0 - git.fec3683
func parseInitVersion(v string) (version string, commit string, err error) {
	parts := strings.Split(v, " - ")

	if len(parts) >= 2 {
		gitParts := strings.Split(strings.TrimSpace(parts[1]), ".")
		if len(gitParts) == 2 && gitParts[0] == "git" {
			commit = gitParts[1]
		}
	}
	parts[0] = strings.TrimSpace(parts[0])
	if strings.HasPrefix(parts[0], "tini version ") {
		version = strings.TrimPrefix(parts[0], "tini version ")
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return version, commit, err
}

// parseRuntimeVersion parses the output of `[runtime] --version` and extracts the
// "name", "version" and "git commit" from the output.
//
// Output example from `runc --version`:
//
//   runc version 1.0.0-rc5+dev
//   commit: 69663f0bd4b60df09991c08812a60108003fa340
//   spec: 1.0.0
func parseRuntimeVersion(v string) (runtime string, version string, commit string, err error) {
	lines := strings.Split(strings.TrimSpace(v), "\n")
	for _, line := range lines {
		if strings.Contains(line, "version") {
			s := strings.Split(line, "version")
			runtime = strings.TrimSpace(s[0])
			version = strings.TrimSpace(s[len(s)-1])
			continue
		}
		if strings.HasPrefix(line, "commit:") {
			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
			continue
		}
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return runtime, version, commit, err
}

func (daemon *Daemon) cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo) bool {
	return sysInfo.CgroupNamespaces && containertypes.CgroupnsMode(daemon.configStore.CgroupNamespaceMode).IsPrivate()
}

// Rootless returns true if daemon is running in rootless mode
func (daemon *Daemon) Rootless() bool {
	return daemon.configStore.Rootless
}
<commit_msg>Move cgroup v2 out of experimental<commit_after>// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/pkg/sysinfo"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fillPlatformInfo fills the platform related info.
func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
	v.CgroupDriver = daemon.getCgroupDriver()
	v.CgroupVersion = "1"
	if sysInfo.CgroupUnified {
		v.CgroupVersion = "2"
	}

	v.MemoryLimit = sysInfo.MemoryLimit
	v.SwapLimit = sysInfo.SwapLimit
	v.KernelMemory = sysInfo.KernelMemory
	v.KernelMemoryTCP = sysInfo.KernelMemoryTCP
	v.OomKillDisable = sysInfo.OomKillDisable
	v.CPUCfsPeriod = sysInfo.CPUCfs
	v.CPUCfsQuota = sysInfo.CPUCfs
	v.CPUShares = sysInfo.CPUShares
	v.CPUSet = sysInfo.Cpuset
	v.PidsLimit = sysInfo.PidsLimit
	v.Runtimes = daemon.configStore.GetAllRuntimes()
	v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
	v.InitBinary = daemon.configStore.GetInitPath()

	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, _, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
			v.RuncCommit.ID = "N/A"
		} else {
			v.RuncCommit.ID = commit
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
		v.RuncCommit.ID = "N/A"
	}

	// runc is now shipped as a separate package. Set "expected" to same value
	// as "ID" to prevent clients from reporting a version-mismatch
	v.RuncCommit.Expected = v.RuncCommit.ID

	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.ContainerdCommit.ID = rv.Revision
	} else {
		logrus.Warnf("failed to retrieve containerd version: %v", err)
		v.ContainerdCommit.ID = "N/A"
	}

	// containerd is now shipped as a separate package. Set "expected" to same
	// value as "ID" to prevent clients from reporting a version-mismatch
	v.ContainerdCommit.Expected = v.ContainerdCommit.ID

	// TODO is there still a need to check the expected version for tini?
	// if not, we can change this, and just set "Expected" to v.InitCommit.ID
	v.InitCommit.Expected = dockerversion.InitCommitID

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if _, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
			v.InitCommit.ID = "N/A"
		} else {
			v.InitCommit.ID = commit
			if len(dockerversion.InitCommitID) > len(commit) {
				v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
			}
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
		v.InitCommit.ID = "N/A"
	}

	if v.CgroupDriver == cgroupNoneDriver {
		if v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.")
		} else {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.")
		}
	} else {
		if !v.MemoryLimit {
			v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
		}
		if !v.SwapLimit {
			v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
		}
		if !v.KernelMemoryTCP && v.CgroupVersion == "1" {
			// kernel memory is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No kernel memory TCP limit support")
		}
		if !v.OomKillDisable && v.CgroupVersion == "1" {
			// oom kill disable is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
		}
		if !v.CPUCfsQuota {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
		}
		if !v.CPUCfsPeriod {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
		}
		if !v.CPUShares {
			v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
		}
		if !v.CPUSet {
			v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
		}
		// TODO add fields for these options in types.Info
		if !sysInfo.BlkioWeight && v.CgroupVersion == "2" {
			// blkio weight is not available on cgroup v1 since kernel 5.0.
			// Warning is not printed on cgroup v1, because there is no action user can take.
			// On cgroup v2, blkio weight is implemented using io.weight
			v.Warnings = append(v.Warnings, "WARNING: No io.weight support")
		}
		if !sysInfo.BlkioWeightDevice && v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: No io.weight (per device) support")
		}
		if !sysInfo.BlkioReadBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (rbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_bps_device support")
			}
		}
		if !sysInfo.BlkioWriteBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_bps_device support")
			}
		}
		if !sysInfo.BlkioReadIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (riops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_iops_device support")
			}
		}
		if !sysInfo.BlkioWriteIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wiops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_iops_device support")
			}
		}
	}
	if !v.IPv4Forwarding {
		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
	}
	if !v.BridgeNfIptables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
	}
	if !v.BridgeNfIP6tables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
	}
}

func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.Components = append(v.Components, types.ComponentVersion{
			Name:    "containerd",
			Version: rv.Version,
			Details: map[string]string{
				"GitCommit": rv.Revision,
			},
		})
	}

	defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
	defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, ver, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    defaultRuntime,
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
	}

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if ver, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    filepath.Base(defaultInitBinary),
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
	}
}

func fillDriverWarnings(v *types.Info) {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Data loop file" {
			msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+
				"strongly discouraged for production use.\n         "+
				"Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver)

			v.Warnings = append(v.Warnings, msg)
			continue
		}
		if pair[0] == "Supports d_type" && pair[1] == "false" {
			backingFs := getBackingFs(v)

			msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs)
			if backingFs == "xfs" {
				msg += "         Reformat the filesystem with ftype=1 to enable d_type support.\n"
			}
			msg += "         Running without d_type support will not be supported in future releases."

			v.Warnings = append(v.Warnings, msg)
			continue
		}
	}
}

func getBackingFs(v *types.Info) string {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Backing Filesystem" {
			return pair[1]
		}
	}
	return ""
}

// parseInitVersion parses a Tini version string, and extracts the "version"
// and "git commit" from the output.
//
// Output example from `docker-init --version`:
//
//     tini version 0.18.0 - git.fec3683
func parseInitVersion(v string) (version string, commit string, err error) {
	parts := strings.Split(v, " - ")

	if len(parts) >= 2 {
		gitParts := strings.Split(strings.TrimSpace(parts[1]), ".")
		if len(gitParts) == 2 && gitParts[0] == "git" {
			commit = gitParts[1]
		}
	}
	parts[0] = strings.TrimSpace(parts[0])
	if strings.HasPrefix(parts[0], "tini version ") {
		version = strings.TrimPrefix(parts[0], "tini version ")
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return version, commit, err
}

// parseRuntimeVersion parses the output of `[runtime] --version` and extracts the
// "name", "version" and "git commit" from the output.
//
// Output example from `runc --version`:
//
//   runc version 1.0.0-rc5+dev
//   commit: 69663f0bd4b60df09991c08812a60108003fa340
//   spec: 1.0.0
func parseRuntimeVersion(v string) (runtime string, version string, commit string, err error) {
	lines := strings.Split(strings.TrimSpace(v), "\n")
	for _, line := range lines {
		if strings.Contains(line, "version") {
			s := strings.Split(line, "version")
			runtime = strings.TrimSpace(s[0])
			version = strings.TrimSpace(s[len(s)-1])
			continue
		}
		if strings.HasPrefix(line, "commit:") {
			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
			continue
		}
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return runtime, version, commit, err
}

func (daemon *Daemon) cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo) bool {
	return sysInfo.CgroupNamespaces && containertypes.CgroupnsMode(daemon.configStore.CgroupNamespaceMode).IsPrivate()
}

// Rootless returns true if daemon is running in rootless mode
func (daemon *Daemon) Rootless() bool {
	return daemon.configStore.Rootless
}
<|endoftext|>


<commit_before>package amqp

import (
	"log"
	"testing"
)

func TestChannelOpenOnAClosedConnectionFails(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	conn.Close()

	if !conn.IsClosed() {
		log.Fatalf("connection %s is expected to be closed", conn)
	}

	_, err = conn.Channel()
	if err != ErrClosed {
		log.Fatalf("channel.open on a closed connection %s is expected to fail", conn)
	}
}

func TestQueueDeclareOnAClosedConnectionFails(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	ch, _ := conn.Channel()

	conn.Close()

	if !conn.IsClosed() {
		log.Fatalf("connection %s is expected to be closed", conn)
	}

	_, err = ch.QueueDeclare("an example", false, false, false, false, nil)
	if err != ErrClosed {
		log.Fatalf("queue.declare on a closed connection %s is expected to fail", conn)
	}
}

func TestConcurrentClose(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	if err != nil {
		log.Fatalf("Could't connect to amqp server, err = %s", err)
	}

	for i := 0; i < 5; i++ {
		t.Run("ConcurrentClose", func(t *testing.T) {
			t.Parallel()
			err := conn.Close()
			if err != nil && err != ErrClosed {
				log.Fatalf("Expected nil or ErrClosed - got %s", err)
			}
		})
	}
}
<commit_msg>Show err type on test fail<commit_after>package amqp

import (
	"log"
	"testing"
)

func TestChannelOpenOnAClosedConnectionFails(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	conn.Close()

	if !conn.IsClosed() {
		log.Fatalf("connection %s is expected to be closed", conn)
	}

	_, err = conn.Channel()
	if err != ErrClosed {
		log.Fatalf("channel.open on a closed connection %s is expected to fail", conn)
	}
}

func TestQueueDeclareOnAClosedConnectionFails(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	ch, _ := conn.Channel()

	conn.Close()

	if !conn.IsClosed() {
		log.Fatalf("connection %s is expected to be closed", conn)
	}

	_, err = ch.QueueDeclare("an example", false, false, false, false, nil)
	if err != ErrClosed {
		log.Fatalf("queue.declare on a closed connection %s is expected to fail", conn)
	}
}

func TestConcurrentClose(t *testing.T) {
	conn, err := Dial("amqp://guest:guest@localhost:5672/")
	if err != nil {
		log.Fatalf("Could't connect to amqp server, err = %s", err)
	}

	for i := 0; i < 5; i++ {
		t.Run("ConcurrentClose", func(t *testing.T) {
			t.Parallel()
			err := conn.Close()
			if err != nil && err != ErrClosed {
				log.Fatalf("Expected nil or ErrClosed - got %#v, type is %T", err, err)
			}
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strconv"
)

const temporaryArff = "solves.arff"

const r2RegularExpression = `=== Cross-validation ===\n\nCorrelation coefficient\s*(\d\.\d{1,10})`

var wekaJar string

type appOptions struct {
	inFile  string
	outFile string
	help    bool
	flagSet *flag.FlagSet
}

func init() {

	//Check for various installed versions of Weka

	//TODO: make this WAY more resilient to different versions
	possibleJarLocations := []string{
		"/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar",
		"/Applications/weka-3-6-12-oracle-jvm.app/Contents/Java/weka.jar",
	}

	for _, path := range possibleJarLocations {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			//Found it!
			wekaJar = path
			continue
		}
	}

	if wekaJar == "" {
		log.Fatalln("Could not find Weka")
	}

}

func (a *appOptions) defineFlags() {
	if a.flagSet == nil {
		return
	}
	a.flagSet.StringVar(&a.inFile, "i", "solves.csv", "Which file to read from")
	a.flagSet.StringVar(&a.outFile, "o", "analysis.txt", "Which file to output analysis to")
	a.flagSet.BoolVar(&a.help, "h", false, "If provided, will print help and exit.")
}

func (a *appOptions) parse(args []string) {
	a.flagSet.Parse(args)
}

func newAppOptions(flagSet *flag.FlagSet) *appOptions {
	a := &appOptions{
		flagSet: flagSet,
	}
	a.defineFlags()
	return a
}

func main() {

	options := newAppOptions(flag.CommandLine)
	options.parse(os.Args[1:])

	if options.help {
		options.flagSet.PrintDefaults()
		return
	}

	//TODO: allow configuring just a relativedifficulties file and run the whole pipeline

	//First, convert the file to arff.

	cmd := execJavaCommand("weka.core.converters.CSVLoader", options.inFile)

	out, err := os.Create(temporaryArff)

	if err != nil {
		log.Println(err)
		return
	}

	cmd.Stdout = out
	cmd.Stderr = os.Stderr

	err = cmd.Run()

	if err != nil {
		log.Println(err)
		return
	}

	//Do the training
	trainCmd := execJavaCommand("weka.classifiers.functions.SMOreg",
		"-C", "1.0", "-N", "2", "-I", `weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V`,
		"-K", `weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0`, "-c", "first", "-i", "-t", "solves.arff")

	trainCmd.Stderr = os.Stderr

	output, err := trainCmd.Output()

	if err != nil {
		log.Println(err)
		return
	}

	r2 := extractR2(string(output))

	fmt.Println("R2 =", r2)

	ioutil.WriteFile(options.outFile, output, 0644)

	//Remove the temporary arff file.
	os.Remove(temporaryArff)

}

func execJavaCommand(input ...string) *exec.Cmd {

	var args []string
	args = append(args, "-cp")
	args = append(args, wekaJar)
	args = append(args, input...)

	return exec.Command("java", args...)
}

func extractR2(input string) float64 {
	re := regexp.MustCompile(r2RegularExpression)
	result := re.FindStringSubmatch(input)

	if len(result) != 2 {
		return 0.0
	}

	//Match 0 is the entire expression, so the float is in match 1

	float, _ := strconv.ParseFloat(result[1], 64)
	return float
}
<commit_msg>Turn off the descriptive error messages, since Weka is a pain about annoyances on stderry<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strconv"
)

const temporaryArff = "solves.arff"

const r2RegularExpression = `=== Cross-validation ===\n\nCorrelation coefficient\s*(\d\.\d{1,10})`

var wekaJar string

type appOptions struct {
	inFile  string
	outFile string
	help    bool
	flagSet *flag.FlagSet
}

func init() {

	//Check for various installed versions of Weka

	//TODO: make this WAY more resilient to different versions
	possibleJarLocations := []string{
		"/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar",
		"/Applications/weka-3-6-12-oracle-jvm.app/Contents/Java/weka.jar",
	}

	for _, path := range possibleJarLocations {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			//Found it!
			wekaJar = path
			continue
		}
	}

	if wekaJar == "" {
		log.Fatalln("Could not find Weka")
	}

}

func (a *appOptions) defineFlags() {
	if a.flagSet == nil {
		return
	}
	a.flagSet.StringVar(&a.inFile, "i", "solves.csv", "Which file to read from")
	a.flagSet.StringVar(&a.outFile, "o", "analysis.txt", "Which file to output analysis to")
	a.flagSet.BoolVar(&a.help, "h", false, "If provided, will print help and exit.")
}

func (a *appOptions) parse(args []string) {
	a.flagSet.Parse(args)
}

func newAppOptions(flagSet *flag.FlagSet) *appOptions {
	a := &appOptions{
		flagSet: flagSet,
	}
	a.defineFlags()
	return a
}

func main() {

	options := newAppOptions(flag.CommandLine)
	options.parse(os.Args[1:])

	if options.help {
		options.flagSet.PrintDefaults()
		return
	}

	//TODO: allow configuring just a relativedifficulties file and run the whole pipeline

	//First, convert the file to arff.

	cmd := execJavaCommand("weka.core.converters.CSVLoader", options.inFile)

	out, err := os.Create(temporaryArff)

	if err != nil {
		log.Println(err)
		return
	}

	cmd.Stdout = out

	//TODO: really we should pipe the output to stderr, but Weka complains
	//about some stupid unnecessary database JARs every time, so it's
	//generally annoying.

	//cmd.Stderr = os.Stderr

	err = cmd.Run()

	if err != nil {
		log.Println(err)
		return
	}

	//Do the training
	trainCmd := execJavaCommand("weka.classifiers.functions.SMOreg",
		"-C", "1.0", "-N", "2", "-I", `weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V`,
		"-K", `weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0`, "-c", "first", "-i", "-t", "solves.arff")

	trainCmd.Stderr = os.Stderr

	output, err := trainCmd.Output()

	if err != nil {
		log.Println(err)
		return
	}

	r2 := extractR2(string(output))

	fmt.Println("R2 =", r2)

	ioutil.WriteFile(options.outFile, output, 0644)

	//Remove the temporary arff file.
	os.Remove(temporaryArff)

}

func execJavaCommand(input ...string) *exec.Cmd {

	var args []string
	args = append(args, "-cp")
	args = append(args, wekaJar)
	args = append(args, input...)

	return exec.Command("java", args...)
}

func extractR2(input string) float64 {
	re := regexp.MustCompile(r2RegularExpression)
	result := re.FindStringSubmatch(input)

	if len(result) != 2 {
		return 0.0
	}

	//Match 0 is the entire expression, so the float is in match 1

	float, _ := strconv.ParseFloat(result[1], 64)
	return float
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"log"

	"github.com/google/trillian"
	"github.com/google/trillian-examples/registers/records"
	"google.golang.org/grpc"
)

var (
	trillianMap = flag.String("trillian_map", "localhost:8095", "address of the Trillian Map RPC server.")
	mapID       = flag.Int64("map_id", 0, "Trillian MapID to read.")
)

func getValue(tmc trillian.TrillianMapClient, hash []byte) *string {
	index := [1][]byte{hash}
	req := &trillian.GetMapLeavesRequest{
		MapId: *mapID,
		Index: index[:],
	}

	resp, err := tmc.GetLeaves(context.Background(), req)
	if err != nil {
		log.Fatalf("Can't get leaf '%s': %v", hash, err)
	}
	if resp.MapLeafInclusion[0].Leaf.LeafValue == nil {
		return nil
	}
	s := string(resp.MapLeafInclusion[0].Leaf.LeafValue)
	return &s
}

func getRecord(tmc trillian.TrillianMapClient, k string) {
	fmt.Printf("%s\n", k)
	resp := getValue(tmc, records.RecordHash(k))
	fmt.Printf("%v\n", *resp)
}

func main() {
	flag.Parse()

	g, err := grpc.Dial(*trillianMap, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Failed to dial Trillian Log: %v", err)
	}
	tmc := trillian.NewTrillianMapClient(g)

	if len(flag.Args()) == 0 {
		n := 0
		for {
			resp := getValue(tmc, records.KeyHash(n))
			if resp == nil {
				break
			}
			getRecord(tmc, *resp)
			n++
		}
	}

	for _, k := range flag.Args() {
		getRecord(tmc, k)
	}
}
<commit_msg>More usual loop.<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	"log"

	"github.com/google/trillian"
	"github.com/google/trillian-examples/registers/records"
	"google.golang.org/grpc"
)

var (
	trillianMap = flag.String("trillian_map", "localhost:8095", "address of the Trillian Map RPC server.")
	mapID       = flag.Int64("map_id", 0, "Trillian MapID to read.")
)

func getValue(tmc trillian.TrillianMapClient, hash []byte) *string {
	index := [1][]byte{hash}
	req := &trillian.GetMapLeavesRequest{
		MapId: *mapID,
		Index: index[:],
	}

	resp, err := tmc.GetLeaves(context.Background(), req)
	if err != nil {
		log.Fatalf("Can't get leaf '%s': %v", hash, err)
	}
	if resp.MapLeafInclusion[0].Leaf.LeafValue == nil {
		return nil
	}
	s := string(resp.MapLeafInclusion[0].Leaf.LeafValue)
	return &s
}

func getRecord(tmc trillian.TrillianMapClient, k string) {
	fmt.Printf("%s\n", k)
	resp := getValue(tmc, records.RecordHash(k))
	fmt.Printf("%v\n", *resp)
}

func main() {
	flag.Parse()

	g, err := grpc.Dial(*trillianMap, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Failed to dial Trillian Log: %v", err)
	}
	tmc := trillian.NewTrillianMapClient(g)

	if len(flag.Args()) == 0 {
		for n := 0; ; n++ {
			resp := getValue(tmc, records.KeyHash(n))
			if resp == nil {
				break
			}
			getRecord(tmc, *resp)
		}
	}

	for _, k := range flag.Args() {
		getRecord(tmc, k)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

func splitString(s string) []string {
	var returnArray []string
	for i := 0; i < len(s); i++ {
		returnArray = append(returnArray, string(s[i]))
	}
	return returnArray
}

func RomanToInt(s string) int {
	charList := splitString(s)
	sumNumber := 0
	for i := 0; i < len(charList)-1; i++ {

	}

}

func main() {
	dicOfLetters := map[string]int{
		"I": 1,
		"V": 5,
		"X": 10,
		"D": 500,
		"L": 50,
		"C": 100,
		"M": 1000,
	}
	fmt.Println(dicOfLetters)
	fmt.Println(splitString("XIIX"))
	fmt.Println(RomanToInt("XIIX"))
}
<commit_msg>keep doing<commit_after>package main

import (
	"fmt"
)

var dicOfLetters = make(map[string]int)

dicOfLetters["I"]=1
dicOfLetters["V"]=5
dicOfLetters["X"]=10
dicOfLetters["D"]=500
dicOfLetters["L"]=50
dicOfLetters["C"]=100
dicOfLetters["M"]=1000

func splitString(s string) []string {
	var returnArray []string
	for i := 0; i < len(s); i++ {
		returnArray = append(returnArray, string(s[i]))
	}
	return returnArray
}

func RomanToInt(s string) int {
	charList := splitString(s)
	sumNumber := 0
	char2, char1 := 0, 0
	for i := 0; i < len(charList)-1; i++ {
		char2 = dicOfLetters[charList[i]]
		fmt.Println(char2)
	}

}

func main() {
	dicOfLetters := map[string]int{
		"I": 1,
		"V": 5,
		"X": 10,
		"D": 500,
		"L": 50,
		"C": 100,
		"M": 1000,
	}
	fmt.Println(dicOfLetters)
	fmt.Println(splitString("XIIX"))
	fmt.Println(RomanToInt("XIIX"))
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	//_ "expvar"
	"flag"
	"log"
	"net/http"
	//_ "net/http/pprof"
	"time"

	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache "github.com/pierrre/imageserver/cache"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_processor "github.com/pierrre/imageserver/processor"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_provider "github.com/pierrre/imageserver/provider"
	imageserver_testdata "github.com/pierrre/imageserver/testdata"
)

func main() {
	var httpAddr string
	flag.StringVar(&httpAddr, "http", ":8080", "Http")
	flag.Parse()

	log.Println("Start")

	var cache imageserver_cache.Cache
	cache = &imageserver_cache_redis.Cache{
		Pool: &redigo.Pool{
			Dial: func() (redigo.Conn, error) {
				return redigo.Dial("tcp", "localhost:6379")
			},
			MaxIdle: 50,
		},
		Expire: time.Duration(7 * 24 * time.Hour),
	}
	cache = &imageserver_cache.Async{
		Cache: cache,
		ErrFunc: func(err error, key string, image *imageserver.Image, parameters imageserver.Parameters) {
			log.Println("Cache error:", err)
		},
	}
	cache = imageserver_cache.List{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		cache,
	}

	var processor imageserver_processor.Processor
	processor = &imageserver_processor_graphicsmagick.Processor{
		Executable: "gm",
		Timeout:    time.Duration(10 * time.Second),
		AllowedFormats: []string{
			"jpeg",
			"png",
			"bmp",
			"gif",
		},
	}
	processor = imageserver_processor.NewLimit(processor, 16)

	var server imageserver.Server
	server = &imageserver_provider.Server{
		Provider: imageserver_testdata.Provider,
	}
	server = &imageserver_processor.Server{
		Server:    server,
		Processor: processor,
	}
	server = &imageserver_cache.Server{
		Server:       server,
		Cache:        cache,
		KeyGenerator: imageserver_cache.NewParametersHashKeyGenerator(sha256.New),
	}

	var handler http.Handler
	handler = &imageserver_http.Handler{
		Parser: &imageserver_http.ListParser{
			&imageserver_http.SourceParser{},
			&imageserver_http_parser_graphicsmagick.Parser{},
		},
		Server:   server,
		ETagFunc: imageserver_http.NewParametersHashETagFunc(sha256.New),
		ErrorFunc: func(err error, request *http.Request) {
			log.Println("Error:", err)
		},
	}
	handler = &imageserver_http.ExpiresHandler{
		Handler: handler,
		Expires: time.Duration(7 * 24 * time.Hour),
	}
	http.Handle("/", handler)

	err := http.ListenAndServe(httpAddr, nil)
	if err != nil {
		log.Panic(err)
	}
}
<commit_msg>remove unused import<commit_after>package main

import (
	"crypto/sha256"
	"flag"
	"log"
	"net/http"
	"time"

	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache "github.com/pierrre/imageserver/cache"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_processor "github.com/pierrre/imageserver/processor"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_provider "github.com/pierrre/imageserver/provider"
	imageserver_testdata "github.com/pierrre/imageserver/testdata"
)

func main() {
	var httpAddr string
	flag.StringVar(&httpAddr, "http", ":8080", "Http")
	flag.Parse()

	log.Println("Start")

	var cache imageserver_cache.Cache
	cache = &imageserver_cache_redis.Cache{
		Pool: &redigo.Pool{
			Dial: func() (redigo.Conn, error) {
				return redigo.Dial("tcp", "localhost:6379")
			},
			MaxIdle: 50,
		},
		Expire: time.Duration(7 * 24 * time.Hour),
	}
	cache = &imageserver_cache.Async{
		Cache: cache,
		ErrFunc: func(err error, key string, image *imageserver.Image, parameters imageserver.Parameters) {
			log.Println("Cache error:", err)
		},
	}
	cache = imageserver_cache.List{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		cache,
	}

	var processor imageserver_processor.Processor
	processor = &imageserver_processor_graphicsmagick.Processor{
		Executable: "gm",
		Timeout:    time.Duration(10 * time.Second),
		AllowedFormats: []string{
			"jpeg",
			"png",
			"bmp",
			"gif",
		},
	}
	processor = imageserver_processor.NewLimit(processor, 16)

	var server imageserver.Server
	server = &imageserver_provider.Server{
		Provider: imageserver_testdata.Provider,
	}
	server = &imageserver_processor.Server{
		Server:    server,
		Processor: processor,
	}
	server = &imageserver_cache.Server{
		Server:       server,
		Cache:        cache,
		KeyGenerator: imageserver_cache.NewParametersHashKeyGenerator(sha256.New),
	}

	var handler http.Handler
	handler = &imageserver_http.Handler{
		Parser: &imageserver_http.ListParser{
			&imageserver_http.SourceParser{},
			&imageserver_http_parser_graphicsmagick.Parser{},
		},
		Server:   server,
		ETagFunc: imageserver_http.NewParametersHashETagFunc(sha256.New),
		ErrorFunc: func(err error, request *http.Request) {
			log.Println("Error:", err)
		},
	}
	handler = &imageserver_http.ExpiresHandler{
		Handler: handler,
		Expires: time.Duration(7 * 24 * time.Hour),
	}
	http.Handle("/", handler)

	err := http.ListenAndServe(httpAddr, nil)
	if err != nil {
		log.Panic(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package templates

import "html/template"

var SelectCaveats = template.Must(selectCaveats.Parse(headPartial))

var selectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>

  {{template "head" .}}

  <title>Blessings: Select Caveats</title>

    Vanadium

    Select Caveats

    {{.Extension}}

  <form method="POST" id="caveats-form" name="input" action="{{.MacaroonURL}}" role="form">

Seeking Blessing: {{.BlessingName}}/{{.Extension}}

  <input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">

      Extension
      <input name="blessingExtension" type="text" id="blessing-extension" placeholder="(optional) name of the device/application for which the blessing is being sought, e.g. homelaptop">
      <input type="text" class="hidden" id="timezoneOffset" name="timezoneOffset">

    Expiration

        <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" checked>
        When explicitly revoked

          <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
          <input type="datetime-local" id="expiry" name="expiry">

Additional caveats

  Optional additional restrictions on the use of the blessing

      <select name="caveat" class="caveats">
        <option value="none" selected="selected">Select a caveat.</option>
        {{ $caveatList := .CaveatList }}
        {{range $index, $name := $caveatList}}
          {{if eq $name "ExpiryCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Expires</option>
          {{else if eq $name "MethodCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Allowed Methods</option>
          {{else if eq $name "PeerBlessingsCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Allowed Peers</option>
          {{else}}
          <option name="{{$name}}" value="{{$name}}">{{$name}}</option>
          {{end}}
        {{end}}
      </select>

      {{range $index, $name := $caveatList}}
        {{if eq $name "ExpiryCaveat"}}
        <input type="datetime-local" class="caveatInput" id="{{$name}}" name="{{$name}}">
        {{else if eq $name "MethodCaveat"}}
        <input type="text" id="{{$name}}" class="caveatInput" name="{{$name}}" placeholder="comma-separated method list">
        {{else if eq $name "PeerBlessingsCaveat"}}
        <input type="text" id="{{$name}}" class="form-control caveatInput" name="{{$name}}" placeholder="comma-separated blessing-pattern list">
        {{end}}
      {{end}}
      <button type="button" class="button-passive right addCaveat">Add Caveat</button>

  The blessing name will be visible to any peers that this blessing is shared
with. Thus, if your email address is in the blessing name, it will be visible
to peers you share the blessing with.

  By clicking "Bless", you consent to be bound by Google's general Terms of Service
  and Google's general Privacy Policy.

    <button class="cell button-passive" type="submit">Bless</button>
    <button class="cell button-passive" id="cancel">Cancel</button>

  </form>

`))
<commit_msg>Update Terms of Service based on lawyer feedback.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package templates

import "html/template"

var SelectCaveats = template.Must(selectCaveats.Parse(headPartial))

var selectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>

  {{template "head" .}}

  <title>Blessings: Select Caveats</title>

    Vanadium

    Select Caveats

    {{.Extension}}

  <form method="POST" id="caveats-form" name="input" action="{{.MacaroonURL}}" role="form">

Seeking Blessing: {{.BlessingName}}/{{.Extension}}

  <input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">

      Extension
      <input name="blessingExtension" type="text" id="blessing-extension" placeholder="(optional) name of the device/application for which the blessing is being sought, e.g. homelaptop">
      <input type="text" class="hidden" id="timezoneOffset" name="timezoneOffset">

    Expiration

        <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" checked>
        When explicitly revoked

          <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
          <input type="datetime-local" id="expiry" name="expiry">

Additional caveats

  Optional additional restrictions on the use of the blessing

      <select name="caveat" class="caveats">
        <option value="none" selected="selected">Select a caveat.</option>
        {{ $caveatList := .CaveatList }}
        {{range $index, $name := $caveatList}}
          {{if eq $name "ExpiryCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Expires</option>
          {{else if eq $name "MethodCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Allowed Methods</option>
          {{else if eq $name "PeerBlessingsCaveat"}}
          <option name="{{$name}}" value="{{$name}}">Allowed Peers</option>
          {{else}}
          <option name="{{$name}}" value="{{$name}}">{{$name}}</option>
          {{end}}
        {{end}}
      </select>

      {{range $index, $name := $caveatList}}
        {{if eq $name "ExpiryCaveat"}}
        <input type="datetime-local" class="caveatInput" id="{{$name}}" name="{{$name}}">
        {{else if eq $name "MethodCaveat"}}
        <input type="text" id="{{$name}}" class="caveatInput" name="{{$name}}" placeholder="comma-separated method list">
        {{else if eq $name "PeerBlessingsCaveat"}}
        <input type="text" id="{{$name}}" class="form-control caveatInput" name="{{$name}}" placeholder="comma-separated blessing-pattern list">
        {{end}}
      {{end}}
      <button type="button" class="button-passive right addCaveat">Add Caveat</button>

  The blessing name will be visible to any peers that this blessing is shared
with. Thus, if your email address is in the blessing name, it will be visible
to peers you share the blessing with.

  By clicking "Bless", you consent to be bound by
  Google's general Terms of Service,
  the Google APIs Terms of Service,
  and Google's general Privacy Policy.

    <button class="cell button-passive" type="submit">Bless</button>
    <button class="cell button-passive" id="cancel">Cancel</button>

  </form>

`))
<|endoftext|>


<commit_before>package ebs

import (
	_ "encoding/json"
	"errors"
	"fmt"
	"os/exec"
	"syscall"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	Name         = "aws"
	AwsTableName = "AwsOpenStorage"
)

var (
	devMinor int32
)

// This data is persisted in a DB.
type awsVolume struct {
	spec       api.VolumeSpec
	formatted  bool
	attached   bool
	mounted    bool
	device     string
	mountpath  string
	instanceID string
}

// Implements the open storage volume interface.
type awsProvider struct {
	ec2 *ec2.EC2
	db  *dynamodb.DynamoDB
}

func Init(params volume.DriverParams) (volume.VolumeDriver, error) {
	// Initialize the EC2 interface.
	creds := credentials.NewEnvCredentials()

	// TODO make the region an env variable.
	config := &aws.Config{Region: "us-west-1", Credentials: creds}
	inst := &awsProvider{ec2: ec2.New(config), db: dynamodb.New(config)}

	err := inst.init()

	return inst, err
}

// AWS provisioned IOPS range is 100 - 20000.
func mapIops(cos api.VolumeCos) int64 {
	if cos < 3 {
		return 1000
	} else if cos < 7 {
		return 10000
	} else {
		return 20000
	}
}

func (self *awsProvider) get(volumeID string) (*awsVolume, error) {
	v := &awsVolume{}
	return v, nil
	/*
		err := self.db.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(AwsBucketName))
			b := bucket.Get([]byte(volumeID))

			if b == nil {
				return errors.New("no such volume ID")
			} else {
				err := json.Unmarshal(b, v)
				return err
			}
		})

		return v, err
	*/
}

func (self *awsProvider) put(volumeID string, v *awsVolume) error {
	/*
		b, _ := json.Marshal(v)

		err := self.db.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(AwsBucketName))
			err := bucket.Put([]byte(volumeID), b)
			return err
		})

		return err
	*/
	return nil
}

// Create a DB if one does not exist.  This is where we persist the
// Amazon instance ID, sdevice and volume ID mappings.
func (self *awsProvider) init() error {
	listParams := &dynamodb.ListTablesInput{
		ExclusiveStartTableName: aws.String(AwsTableName),
		Limit: aws.Long(1),
	}

	_, err := self.db.ListTables(listParams)
	if err == nil {
		return nil
	}

	// Assume table does not exist and re-create it.
	createParams := &dynamodb.CreateTableInput{
		AttributeDefinitions: []*dynamodb.AttributeDefinition{
			{
				AttributeName: aws.String("KeySchemaAttributeName"),
				AttributeType: aws.String("ScalarAttributeType"),
			},
		},
		KeySchema: []*dynamodb.KeySchemaElement{
			{
				AttributeName: aws.String("KeySchemaAttributeName"),
				KeyType:       aws.String("KeyType"),
			},
		},
		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
			ReadCapacityUnits:  aws.Long(1),
			WriteCapacityUnits: aws.Long(1),
		},
		TableName: aws.String(AwsTableName),
		GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
			{
				IndexName: aws.String("IndexName"),
				KeySchema: []*dynamodb.KeySchemaElement{
					{
						AttributeName: aws.String("KeySchemaAttributeName"),
						KeyType:       aws.String("KeyType"),
					},
				},
				Projection: &dynamodb.Projection{
					NonKeyAttributes: []*string{
						aws.String("NonKeyAttributeName"),
					},
					ProjectionType: aws.String("ProjectionType"),
				},
				ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
					ReadCapacityUnits:  aws.Long(1),
					WriteCapacityUnits: aws.Long(1),
				},
			},
		},
		LocalSecondaryIndexes: []*dynamodb.LocalSecondaryIndex{
			{
				IndexName: aws.String("IndexName"),
				KeySchema: []*dynamodb.KeySchemaElement{
					{
						AttributeName: aws.String("KeySchemaAttributeName"),
						KeyType:       aws.String("KeyType"),
					},
				},
				Projection: &dynamodb.Projection{
					NonKeyAttributes: []*string{
						aws.String("NonKeyAttributeName"),
					},
					ProjectionType: aws.String("ProjectionType"),
				},
			},
		},
		StreamSpecification: &dynamodb.StreamSpecification{
			StreamEnabled:  aws.Boolean(true),
			StreamViewType: aws.String("StreamViewType"),
		},
	}

	_, err = self.db.CreateTable(createParams)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
			if reqErr, ok := err.(awserr.RequestFailure); ok {
				fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID())
			}
		} else {
			fmt.Println(err)
		}

		return err
	}

	return nil
}

func (self *awsProvider) String() string {
	return Name
}

func (self *awsProvider) Create(l api.VolumeLocator, opt *api.CreateOptions, spec *api.VolumeSpec) (api.VolumeID, error) {
	// TODO get this via an env variable.
	availabilityZone := "us-west-1a"
	sz := int64(spec.Size / (1024 * 1024 * 1024))
	iops := mapIops(spec.Cos)
	req := &ec2.CreateVolumeInput{
		AvailabilityZone: &availabilityZone,
		Size:             &sz,
		IOPS:             &iops}
	v, err := self.ec2.CreateVolume(req)
	if err != nil {
		fmt.Println(err)
		return api.VolumeID(""), err
	}

	err = self.put(*v.VolumeID, &awsVolume{spec: *spec})

	return api.VolumeID(*v.VolumeID), err
}

func (self *awsProvider) Attach(volumeID api.VolumeID) (string, error) {
	v, err := self.get(string(volumeID))
	if err != nil {
		return "", err
	}

	devMinor++
	device := fmt.Sprintf("/dev/ec2%v", int(devMinor))
	vol := string(volumeID)
	inst := string("")
	req := &ec2.AttachVolumeInput{
		Device:     &device,
		InstanceID: &inst,
		VolumeID:   &vol,
	}

	resp, err := self.ec2.AttachVolume(req)
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	v.instanceID = inst
	v.attached = true
	err = self.put(string(volumeID), v)

	return *resp.Device, err
}

func (self *awsProvider) Mount(volumeID api.VolumeID, mountpath string) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	err = syscall.Mount(v.device, mountpath, "ext4", 0, "")
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.mountpath = mountpath
	v.mounted = true
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Detach(volumeID api.VolumeID) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	vol := string(volumeID)
	inst := v.instanceID
	force := true
	req := &ec2.DetachVolumeInput{
		InstanceID: &inst,
		VolumeID:   &vol,
		Force:      &force,
	}

	_, err = self.ec2.DetachVolume(req)
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.instanceID = inst
	v.attached = false
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Unmount(volumeID api.VolumeID, mountpath string) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	err = syscall.Unmount(v.mountpath, 0)
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.mountpath = ""
	v.mounted = false
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Delete(volumeID api.VolumeID) error {
	return nil
}

func (self *awsProvider) Format(volumeID api.VolumeID) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	if !v.attached {
		return errors.New("volume must be attached")
	}

	if v.mounted {
		return errors.New("volume already mounted")
	}

	if v.formatted {
		return errors.New("volume already formatted")
	}

	cmd := "/sbin/mkfs." + string(v.spec.Format)
	_, err = exec.Command(cmd, v.device).Output()
	if err != nil {
		fmt.Println(err)
		return err
	}
	// TODO validate output

	v.formatted = true
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Inspect(volumeIDs []api.VolumeID) (volume []api.Volume, err error) {
	return nil, nil
}

func (self *awsProvider) Enumerate(locator api.VolumeLocator, labels api.Labels) (volumes []api.Volume, err error) {
	return nil, errors.New("Unsupported")
}

func (self *awsProvider) Snapshot(volumeID api.VolumeID, labels api.Labels) (snap api.SnapID, err error) {
	return "", errors.New("Unsupported")
}

func (self *awsProvider) SnapDelete(snapID api.SnapID) (err error) {
	return errors.New("Unsupported")
}

func (self *awsProvider) SnapInspect(snapID api.SnapID) (snap api.VolumeSnap, err error) {
	return api.VolumeSnap{}, errors.New("Unsupported")
}

func (self *awsProvider) SnapEnumerate(locator api.VolumeLocator, labels api.Labels) (snaps *[]api.SnapID, err error) {
	return nil, errors.New("Unsupported")
}

func (self *awsProvider) Stats(volumeID api.VolumeID) (stats api.VolumeStats, err error) {
	return api.VolumeStats{}, errors.New("Unsupported")
}

func (self *awsProvider) Alerts(volumeID api.VolumeID) (stats api.VolumeAlerts, err error) {
	return api.VolumeAlerts{}, errors.New("Unsupported")
}

func (self *awsProvider) Shutdown() {
	fmt.Printf("%s Shutting down", Name)
}

func init() {
	// Register ourselves as an openstorage volume driver.
	volume.Register(Name, Init)
}
<commit_msg>Switch the DB to dynamodb for the AWS provider<commit_after>package ebs

import (
	_ "encoding/json"
	"errors"
	"fmt"
	"os/exec"
	"syscall"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/volume"
)

const (
	Name         = "aws"
	AwsTableName = "AwsOpenStorage"
)

var (
	devMinor int32
)

// This data is persisted in a DB.
type awsVolume struct {
	spec       api.VolumeSpec
	formatted  bool
	attached   bool
	mounted    bool
	device     string
	mountpath  string
	instanceID string
}

// Implements the open storage volume interface.
type awsProvider struct {
	ec2 *ec2.EC2
	db  *dynamodb.DynamoDB
}

func Init(params volume.DriverParams) (volume.VolumeDriver, error) {
	// Initialize the EC2 interface.
	creds := credentials.NewEnvCredentials()

	// TODO make the region an env variable.
	config := &aws.Config{Region: "us-west-1", Credentials: creds}
	inst := &awsProvider{ec2: ec2.New(config), db: dynamodb.New(config)}

	err := inst.init()

	return inst, err
}

// AWS provisioned IOPS range is 100 - 20000.
func mapIops(cos api.VolumeCos) int64 {
	if cos < 3 {
		return 1000
	} else if cos < 7 {
		return 10000
	} else {
		return 20000
	}
}

func (self *awsProvider) get(volumeID string) (*awsVolume, error) {
	params := &dynamodb.GetItemInput{
		Key: map[string]*dynamodb.AttributeValue{
			"Key": {
				B:    []byte("PAYLOAD"),
				BOOL: aws.Boolean(true),
				BS: [][]byte{
					[]byte("PAYLOAD"),
				},
				L: []*dynamodb.AttributeValue{
					{},
				},
				M: map[string]*dynamodb.AttributeValue{
					"Key": {},
				},
				N: aws.String("NumberAttributeValue"),
				NS: []*string{
					aws.String("NumberAttributeValue"),
				},
				NULL: aws.Boolean(true),
				S:    aws.String("StringAttributeValue"),
				SS: []*string{
					aws.String("StringAttributeValue"),
				},
			},
		},
		TableName: aws.String("TableName"),
		AttributesToGet: []*string{
			aws.String("AttributeName"),
		},
		ConsistentRead: aws.Boolean(true),
		ExpressionAttributeNames: map[string]*string{
			"Key": aws.String("AttributeName"),
		},
		ProjectionExpression:   aws.String("ProjectionExpression"),
		ReturnConsumedCapacity: aws.String("ReturnConsumedCapacity"),
	}
	resp, err := self.db.GetItem(params)

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
			if reqErr, ok := err.(awserr.RequestFailure); ok {
				fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID())
			}
		} else {
			fmt.Println(err.Error())
		}
		return nil, err
	}

	v := &awsVolume{}
	// err = json.Unmarshal(b, v)
	return v, nil

	/*
		err := self.db.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(AwsBucketName))
			b := bucket.Get([]byte(volumeID))

			if b == nil {
				return errors.New("no such volume ID")
			} else {
			}
		})

		return v, err
	*/
}

func (self *awsProvider) put(volumeID string, v *awsVolume) error {
	/*
		b, _ := json.Marshal(v)

		err := self.db.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(AwsBucketName))
			err := bucket.Put([]byte(volumeID), b)
			return err
		})

		return err
	*/
	return nil
}

// Create a DB if one does not exist.  This is where we persist the
// Amazon instance ID, sdevice and volume ID mappings.
func (self *awsProvider) init() error {
	listParams := &dynamodb.ListTablesInput{
		ExclusiveStartTableName: aws.String(AwsTableName),
		Limit: aws.Long(1),
	}

	_, err := self.db.ListTables(listParams)
	if err == nil {
		return nil
	}

	// Assume table does not exist and re-create it.
	createParams := &dynamodb.CreateTableInput{
		AttributeDefinitions: []*dynamodb.AttributeDefinition{
			{
				AttributeName: aws.String("KeySchemaAttributeName"),
				AttributeType: aws.String("ScalarAttributeType"),
			},
		},
		KeySchema: []*dynamodb.KeySchemaElement{
			{
				AttributeName: aws.String("KeySchemaAttributeName"),
				KeyType:       aws.String("KeyType"),
			},
		},
		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
			ReadCapacityUnits:  aws.Long(1),
			WriteCapacityUnits: aws.Long(1),
		},
		TableName: aws.String(AwsTableName),
		GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{
			{
				IndexName: aws.String("IndexName"),
				KeySchema: []*dynamodb.KeySchemaElement{
					{
						AttributeName: aws.String("KeySchemaAttributeName"),
						KeyType:       aws.String("KeyType"),
					},
				},
				Projection: &dynamodb.Projection{
					NonKeyAttributes: []*string{
						aws.String("NonKeyAttributeName"),
					},
					ProjectionType: aws.String("ProjectionType"),
				},
				ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
					ReadCapacityUnits:  aws.Long(1),
					WriteCapacityUnits: aws.Long(1),
				},
			},
		},
		LocalSecondaryIndexes: []*dynamodb.LocalSecondaryIndex{
			{
				IndexName: aws.String("IndexName"),
				KeySchema: []*dynamodb.KeySchemaElement{
					{
						AttributeName: aws.String("KeySchemaAttributeName"),
						KeyType:       aws.String("KeyType"),
					},
				},
				Projection: &dynamodb.Projection{
					NonKeyAttributes: []*string{
						aws.String("NonKeyAttributeName"),
					},
					ProjectionType: aws.String("ProjectionType"),
				},
			},
		},
		StreamSpecification: &dynamodb.StreamSpecification{
			StreamEnabled:  aws.Boolean(true),
			StreamViewType: aws.String("StreamViewType"),
		},
	}

	_, err = self.db.CreateTable(createParams)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
			if reqErr, ok := err.(awserr.RequestFailure); ok {
				fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID())
			}
		} else {
			fmt.Println(err.Error())
		}

		return err
	}

	return nil
}

func (self *awsProvider) String() string {
	return Name
}

func (self *awsProvider) Create(l api.VolumeLocator, opt *api.CreateOptions, spec *api.VolumeSpec) (api.VolumeID, error) {
	// TODO get this via an env variable.
	availabilityZone := "us-west-1a"
	sz := int64(spec.Size / (1024 * 1024 * 1024))
	iops := mapIops(spec.Cos)
	req := &ec2.CreateVolumeInput{
		AvailabilityZone: &availabilityZone,
		Size:             &sz,
		IOPS:             &iops}
	v, err := self.ec2.CreateVolume(req)
	if err != nil {
		fmt.Println(err)
		return api.VolumeID(""), err
	}

	err = self.put(*v.VolumeID, &awsVolume{spec: *spec})

	return api.VolumeID(*v.VolumeID), err
}

func (self *awsProvider) Attach(volumeID api.VolumeID) (string, error) {
	v, err := self.get(string(volumeID))
	if err != nil {
		return "", err
	}

	devMinor++
	device := fmt.Sprintf("/dev/ec2%v", int(devMinor))
	vol := string(volumeID)
	inst := string("")
	req := &ec2.AttachVolumeInput{
		Device:     &device,
		InstanceID: &inst,
		VolumeID:   &vol,
	}

	resp, err := self.ec2.AttachVolume(req)
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	v.instanceID = inst
	v.attached = true
	err = self.put(string(volumeID), v)

	return *resp.Device, err
}

func (self *awsProvider) Mount(volumeID api.VolumeID, mountpath string) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	err = syscall.Mount(v.device, mountpath, "ext4", 0, "")
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.mountpath = mountpath
	v.mounted = true
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Detach(volumeID api.VolumeID) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	vol := string(volumeID)
	inst := v.instanceID
	force := true
	req := &ec2.DetachVolumeInput{
		InstanceID: &inst,
		VolumeID:   &vol,
		Force:      &force,
	}

	_, err = self.ec2.DetachVolume(req)
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.instanceID = inst
	v.attached = false
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Unmount(volumeID api.VolumeID, mountpath string) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	err = syscall.Unmount(v.mountpath, 0)
	if err != nil {
		fmt.Println(err)
		return err
	}

	v.mountpath = ""
	v.mounted = false
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Delete(volumeID api.VolumeID) error {
	return nil
}

func (self *awsProvider) Format(volumeID api.VolumeID) error {
	v, err := self.get(string(volumeID))
	if err != nil {
		return err
	}

	if !v.attached {
		return errors.New("volume must be attached")
	}

	if v.mounted {
		return errors.New("volume already mounted")
	}

	if v.formatted {
		return errors.New("volume already formatted")
	}

	cmd := "/sbin/mkfs." + string(v.spec.Format)
	_, err = exec.Command(cmd, v.device).Output()
	if err != nil {
		fmt.Println(err)
		return err
	}
	// TODO validate output

	v.formatted = true
	err = self.put(string(volumeID), v)

	return err
}

func (self *awsProvider) Inspect(volumeIDs []api.VolumeID) (volume []api.Volume, err error) {
	return nil, nil
}

func (self *awsProvider) Enumerate(locator api.VolumeLocator, labels api.Labels) (volumes []api.Volume, err error) {
	return nil, errors.New("Unsupported")
}

func (self *awsProvider) Snapshot(volumeID api.VolumeID, labels api.Labels) (snap api.SnapID, err error) {
	return "", errors.New("Unsupported")
}

func (self *awsProvider) SnapDelete(snapID api.SnapID) (err error) {
	return errors.New("Unsupported")
}

func (self *awsProvider) SnapInspect(snapID api.SnapID) (snap api.VolumeSnap, err error) {
	return api.VolumeSnap{}, errors.New("Unsupported")
}

func (self *awsProvider) SnapEnumerate(locator api.VolumeLocator, labels api.Labels) (snaps *[]api.SnapID, err error) {
	return nil, errors.New("Unsupported")
}

func (self *awsProvider) Stats(volumeID api.VolumeID) (stats api.VolumeStats, err error) {
	return api.VolumeStats{}, errors.New("Unsupported")
}

func (self *awsProvider) Alerts(volumeID api.VolumeID) (stats api.VolumeAlerts, err error) {
	return api.VolumeAlerts{}, errors.New("Unsupported")
}

func (self *awsProvider) Shutdown() {
	fmt.Printf("%s Shutting down", Name)
}

func init() {
	// Register ourselves as an openstorage volume driver.
	volume.Register(Name, Init)
}
<|endoftext|>


<commit_before>package doorman

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"github.com/gin-gonic/gin"
	"github.com/ory/ladon"
	manager "github.com/ory/ladon/manager/memory"
	log "github.com/sirupsen/logrus"
	jwt "gopkg.in/square/go-jose.v2/jwt"
	"gopkg.in/yaml.v2"

	"github.com/leplatrem/iam/utilities"
)

// DefaultPoliciesFilename is the default policies filename.
const DefaultPoliciesFilename string = "policies.yaml"

// DoormanContextKey is the Gin context key to obtain the *Doorman instance.
const DoormanContextKey string = "doorman"

// JWTContextKey is the Gin context key to obtain the *jwt.Claims instance.
const JWTContextKey string = "JWT"

const maxInt int64 = 1<<63 - 1

// Doorman is the backend in charge of checking requests against policies.
type Doorman struct {
	PoliciesFilenames []string
	JWTIssuer         string
	ladons            map[string]ladon.Ladon
}

// Configuration represents the policies file content.
type Configuration struct {
	Audience string
	Policies []*ladon.DefaultPolicy
}

// New instantiates a new doorman.
func New(filenames []string, issuer string) (*Doorman, error) {
	// If not specified, read default file in current directory `./policies.yaml`
	if len(filenames) == 0 {
		here, _ := os.Getwd()
		filename := filepath.Join(here, DefaultPoliciesFilename)
		filenames = []string{filename}
	}

	w := &Doorman{filenames, issuer, map[string]ladon.Ladon{}}
	if err := w.loadPolicies(); err != nil {
		return nil, err
	}
	return w, nil
}

// IsAllowed is responsible for deciding if subject can perform action on a resource with a context.
func (doorman *Doorman) IsAllowed(audience string, request *ladon.Request) error {
	ladon, ok := doorman.ladons[audience]
	if !ok {
		return fmt.Errorf("unknown audience %q", audience)
	}
	return ladon.IsAllowed(request)
}

// LoadPolicies (re)loads configuration and policies from the YAML files.
func (doorman *Doorman) loadPolicies() error {
	// Clear every existing policy, and load new ones.
	for audience, l := range doorman.ladons {
		existing, err := l.Manager.GetAll(0, maxInt)
		if err != nil {
			return err
		}
		for _, pol := range existing {
			err := l.Manager.Delete(pol.GetID())
			if err != nil {
				return err
			}
		}
		delete(doorman.ladons, audience)
	}
	// Load each configuration file.
	for _, filename := range doorman.PoliciesFilenames {
		log.Info("Load configuration ", filename)
		config, err := loadConfiguration(filename)
		if err != nil {
			return err
		}

		l := ladon.Ladon{
			Manager: manager.NewMemoryManager(),
		}
		for _, pol := range config.Policies {
			log.Info("Load policy ", pol.GetID()+": ", pol.GetDescription())
			err := l.Manager.Create(pol)
			if err != nil {
				return err
			}
		}
		_, exists := doorman.ladons[config.Audience]
		if exists {
			return fmt.Errorf("duplicated audience %q (filename %q)", config.Audience, filename)
		}
		doorman.ladons[config.Audience] = l
	}
	return nil
}

func loadConfiguration(filename string) (*Configuration, error) {
	yamlFile, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if len(yamlFile) == 0 {
		return nil, fmt.Errorf("empty file %q", filename)
	}
	// Ladon does not support un/marshaling YAML.
	// https://github.com/ory/ladon/issues/83
	var generic interface{}
	if err := yaml.Unmarshal(yamlFile, &generic); err != nil {
		return nil, err
	}
	asJSON := utilities.Yaml2JSON(generic)
	jsonData, err := json.Marshal(asJSON)
	if err != nil {
		return nil, err
	}

	var config Configuration
	if err := json.Unmarshal(jsonData, &config); err != nil {
		return nil, err
	}

	if config.Audience == "" {
		return nil, fmt.Errorf("empty audience in %q", filename)
	}

	if len(config.Policies) == 0 {
		log.Warningf("no policies found in %q", filename)
	}

	return &config, nil
}

// ContextMiddleware adds the Doorman instance to the Gin context.
func ContextMiddleware(doorman *Doorman) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set(DoormanContextKey, doorman)
		c.Next()
	}
}

// SetupRoutes adds doorman views to query the policies.
func SetupRoutes(r *gin.Engine, doorman *Doorman) {
	r.Use(ContextMiddleware(doorman))
	if doorman.JWTIssuer != "" {
		validator := &Auth0Validator{
			Issuer: doorman.JWTIssuer,
		}
		r.Use(VerifyJWTMiddleware(validator))
	} else {
		log.Warning("No JWT issuer configured. No authentication will be required.")
	}
	r.POST("/allowed", allowedHandler)
}

func allowedHandler(c *gin.Context) {
	if c.Request.ContentLength == 0 {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "Missing body",
		})
		return
	}

	var accessRequest ladon.Request
	if err := c.BindJSON(&accessRequest); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	payloadJWT, ok := c.Get(JWTContextKey)
	// Is VerifyJWTMiddleware enabled? (disabled in tests)
	if ok {
		claims := payloadJWT.(*jwt.Claims)
		// Subject is taken from JWT.
		accessRequest.Subject = claims.Subject
	}

	doorman := c.MustGet(DoormanContextKey).(*Doorman)

	origin := c.Request.Header.Get("Origin")

	// Will fail if origin is unknown.
	err := doorman.IsAllowed(origin, &accessRequest)
	allowed := (err == nil)

	log.WithFields(
		log.Fields{
			"allowed":  allowed,
			"subject":  accessRequest.Subject,
			"action":   accessRequest.Action,
			"resource": accessRequest.Resource,
		},
	).Info("request.authorization")

	c.JSON(http.StatusOK, gin.H{
		"allowed": allowed,
		"user": gin.H{
			"id": accessRequest.Subject,
		},
	})
}
<commit_msg>Simplify reload<commit_after>package doorman

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"github.com/gin-gonic/gin"
	"github.com/ory/ladon"
	manager "github.com/ory/ladon/manager/memory"
	log "github.com/sirupsen/logrus"
	jwt "gopkg.in/square/go-jose.v2/jwt"
	"gopkg.in/yaml.v2"

	"github.com/leplatrem/iam/utilities"
)

// DefaultPoliciesFilename is the default policies filename.
const DefaultPoliciesFilename string = "policies.yaml"

// DoormanContextKey is the Gin context key to obtain the *Doorman instance.
const DoormanContextKey string = "doorman"

// JWTContextKey is the Gin context key to obtain the *jwt.Claims instance.
const JWTContextKey string = "JWT"

const maxInt int64 = 1<<63 - 1

// Doorman is the backend in charge of checking requests against policies.
type Doorman struct {
	PoliciesFilenames []string
	JWTIssuer         string
	ladons            map[string]ladon.Ladon
}

// Configuration represents the policies file content.
type Configuration struct {
	Audience string
	Policies []*ladon.DefaultPolicy
}

// New instantiates a new doorman.
func New(filenames []string, issuer string) (*Doorman, error) {
	// If not specified, read default file in current directory `./policies.yaml`
	if len(filenames) == 0 {
		here, _ := os.Getwd()
		filename := filepath.Join(here, DefaultPoliciesFilename)
		filenames = []string{filename}
	}

	w := &Doorman{filenames, issuer, map[string]ladon.Ladon{}}
	if err := w.loadPolicies(); err != nil {
		return nil, err
	}
	return w, nil
}

// IsAllowed is responsible for deciding if subject can perform action on a resource with a context.
func (doorman *Doorman) IsAllowed(audience string, request *ladon.Request) error {
	ladon, ok := doorman.ladons[audience]
	if !ok {
		return fmt.Errorf("unknown audience %q", audience)
	}
	return ladon.IsAllowed(request)
}

// LoadPolicies (re)loads configuration and policies from the YAML files.
func (doorman *Doorman) loadPolicies() error {
	// Clear every existing policy, and load new ones.
	for audience := range doorman.ladons {
		delete(doorman.ladons, audience)
	}
	// Load each configuration file.
	for _, filename := range doorman.PoliciesFilenames {
		log.Info("Load configuration ", filename)
		config, err := loadConfiguration(filename)
		if err != nil {
			return err
		}

		l := ladon.Ladon{
			Manager: manager.NewMemoryManager(),
		}
		for _, pol := range config.Policies {
			log.Info("Load policy ", pol.GetID()+": ", pol.GetDescription())
			err := l.Manager.Create(pol)
			if err != nil {
				return err
			}
		}
		_, exists := doorman.ladons[config.Audience]
		if exists {
			return fmt.Errorf("duplicated audience %q (filename %q)", config.Audience, filename)
		}
		doorman.ladons[config.Audience] = l
	}
	return nil
}

func loadConfiguration(filename string) (*Configuration, error) {
	yamlFile, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if len(yamlFile) == 0 {
		return nil, fmt.Errorf("empty file %q", filename)
	}
	// Ladon does not support un/marshaling YAML.
	// https://github.com/ory/ladon/issues/83
	var generic interface{}
	if err := yaml.Unmarshal(yamlFile, &generic); err != nil {
		return nil, err
	}
	asJSON := utilities.Yaml2JSON(generic)
	jsonData, err := json.Marshal(asJSON)
	if err != nil {
		return nil, err
	}

	var config Configuration
	if err := json.Unmarshal(jsonData, &config); err != nil {
		return nil, err
	}

	if config.Audience == "" {
		return nil, fmt.Errorf("empty audience in %q", filename)
	}

	if len(config.Policies) == 0 {
		log.Warningf("no policies found in %q", filename)
	}

	return &config, nil
}

// ContextMiddleware adds the Doorman instance to the Gin context.
func ContextMiddleware(doorman *Doorman) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set(DoormanContextKey, doorman)
		c.Next()
	}
}

// SetupRoutes adds doorman views to query the policies.
func SetupRoutes(r *gin.Engine, doorman *Doorman) {
	r.Use(ContextMiddleware(doorman))
	if doorman.JWTIssuer != "" {
		validator := &Auth0Validator{
			Issuer: doorman.JWTIssuer,
		}
		r.Use(VerifyJWTMiddleware(validator))
	} else {
		log.Warning("No JWT issuer configured. No authentication will be required.")
	}
	r.POST("/allowed", allowedHandler)
}

func allowedHandler(c *gin.Context) {
	if c.Request.ContentLength == 0 {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "Missing body",
		})
		return
	}

	var accessRequest ladon.Request
	if err := c.BindJSON(&accessRequest); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	payloadJWT, ok := c.Get(JWTContextKey)
	// Is VerifyJWTMiddleware enabled? (disabled in tests)
	if ok {
		claims := payloadJWT.(*jwt.Claims)
		// Subject is taken from JWT.
		accessRequest.Subject = claims.Subject
	}

	doorman := c.MustGet(DoormanContextKey).(*Doorman)

	origin := c.Request.Header.Get("Origin")

	// Will fail if origin is unknown.
	err := doorman.IsAllowed(origin, &accessRequest)
	allowed := (err == nil)

	log.WithFields(
		log.Fields{
			"allowed":  allowed,
			"subject":  accessRequest.Subject,
			"action":   accessRequest.Action,
			"resource": accessRequest.Resource,
		},
	).Info("request.authorization")

	c.JSON(http.StatusOK, gin.H{
		"allowed": allowed,
		"user": gin.H{
			"id": accessRequest.Subject,
		},
	})
}
<|endoftext|>


<commit_before>package directoryroles

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/manicminer/hamilton/msgraph"
	"github.com/manicminer/hamilton/odata"

	"github.com/hashicorp/terraform-provider-azuread/internal/clients"
	"github.com/hashicorp/terraform-provider-azuread/internal/services/directoryroles/parse"
	"github.com/hashicorp/terraform-provider-azuread/internal/tf"
	"github.com/hashicorp/terraform-provider-azuread/internal/utils"
	"github.com/hashicorp/terraform-provider-azuread/internal/validate"
)

const directoryRoleMemberResourceName = "azuread_directory_role_member"

func directoryRoleMemberResource() *schema.Resource {
	return &schema.Resource{
		CreateContext: directoryRoleMemberResourceCreate,
		ReadContext:   directoryRoleMemberResourceRead,
		DeleteContext: directoryRoleMemberResourceDelete,

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(5 * time.Minute),
			Read:   schema.DefaultTimeout(5 * time.Minute),
			Update: schema.DefaultTimeout(5 * time.Minute),
			Delete: schema.DefaultTimeout(5 * time.Minute),
		},

		Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
			_, err := parse.DirectoryRoleMemberID(id)
			return err
		}),

		Schema: map[string]*schema.Schema{
			"role_object_id": {
				Description:      "The object ID of the directory role",
				Type:             schema.TypeString,
				Optional:         true,
				ForceNew:         true,
				ValidateDiagFunc: validate.UUID,
			},

			"member_object_id": {
				Description:      "The object ID of the member",
				Type:             schema.TypeString,
				Optional:         true,
				ForceNew:         true,
				ValidateDiagFunc: validate.UUID,
			},
		},
	}
}

func directoryRoleMemberResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient
	directoryObjectsClient := meta.(*clients.Client).DirectoryRoles.DirectoryObjectsClient

	id := parse.NewDirectoryRoleMemberID(d.Get("role_object_id").(string), d.Get("member_object_id").(string))

	tf.LockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)
	defer tf.UnlockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)

	role, status, err := client.Get(ctx, id.DirectoryRoleId)
	if err != nil {
		if status == http.StatusNotFound {
			return tf.ErrorDiagPathF(nil, "object_id", "Directory role with object ID %q was not found", id.DirectoryRoleId)
		}
		return tf.ErrorDiagPathF(err, "object_id", "Retrieving directory role with object ID: %q", id.DirectoryRoleId)
	}

	if _, status, err = client.GetMember(ctx, id.DirectoryRoleId, id.MemberId); err == nil {
		return tf.ImportAsExistsDiag("azuread_directory_role_member", id.String())
	} else if status != http.StatusNotFound {
		return tf.ErrorDiagF(err, "Checking for existing membership of member %q for directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	memberObject, _, err := directoryObjectsClient.Get(ctx, id.MemberId, odata.Query{})
	if err != nil {
		return tf.ErrorDiagF(err, "Could not retrieve member principal object %q", id.MemberId)
	}
	if memberObject == nil {
		return tf.ErrorDiagF(errors.New("returned memberObject was nil"), "Could not retrieve member principal object %q", id.MemberId)
	}
	// TODO: remove this workaround for https://github.com/hashicorp/terraform-provider-azuread/issues/588
	//if memberObject.ODataId == nil {
	//	return tf.ErrorDiagF(errors.New("ODataId was nil"), "Could not retrieve member principal object %q", id.MemberId)
	//}
	memberObject.ODataId = (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s",
		client.BaseClient.Endpoint, client.BaseClient.TenantId, id.MemberId)))

	role.Members = &msgraph.Members{*memberObject}

	if _, err := client.AddMembers(ctx, role); err != nil {
		return tf.ErrorDiagF(err, "Adding role member %q to directory role %q", id.MemberId, id.DirectoryRoleId)
	}

	// Wait for role membership to reflect
	deadline, ok := ctx.Deadline()
	if !ok {
		return tf.ErrorDiagF(errors.New("context has no deadline"), "Waiting for role member %q to reflect for directory role %q", id.MemberId, id.DirectoryRoleId)
	}
	timeout := time.Until(deadline)
	_, err = (&resource.StateChangeConf{
		Pending:                   []string{"Waiting"},
		Target:                    []string{"Done"},
		Timeout:                   timeout,
		MinTimeout:                1 * time.Second,
		ContinuousTargetOccurence: 3,
		Refresh: func() (interface{}, string, error) {
			_, status, err := client.GetMember(ctx, id.DirectoryRoleId, id.MemberId)
			if err != nil {
				if status == http.StatusNotFound {
					return "stub", "Waiting", nil
				}
				return nil, "Error", fmt.Errorf("retrieving role member")
			}
			return "stub", "Done", nil
		},
	}).WaitForStateContext(ctx)
	if err != nil {
		return tf.ErrorDiagF(err, "Waiting for role member %q to reflect for directory role %q", id.MemberId, id.DirectoryRoleId)
	}

	d.SetId(id.String())

	return directoryRoleMemberResourceRead(ctx, d, meta)
}

func directoryRoleMemberResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient

	id, err := parse.DirectoryRoleMemberID(d.Id())
	if err != nil {
		return tf.ErrorDiagPathF(err, "id", "Parsing Directory Role Member ID %q", d.Id())
	}

	if _, status, err := client.GetMember(ctx, id.DirectoryRoleId, id.MemberId); err != nil {
		if status == http.StatusNotFound {
			log.Printf("[DEBUG] Member with ID %q was not found in directory role %q - removing from state", id.MemberId, id.DirectoryRoleId)
			d.SetId("")
			return nil
		}
		return tf.ErrorDiagF(err, "Retrieving role member %q for directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	tf.Set(d, "role_object_id", id.DirectoryRoleId)
	tf.Set(d, "member_object_id", id.MemberId)

	return nil
}

func directoryRoleMemberResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient

	id, err := parse.DirectoryRoleMemberID(d.Id())
	if err != nil {
		return tf.ErrorDiagPathF(err, "id", "Parsing Directory Role Member ID %q", d.Id())
	}

	tf.LockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)
	defer tf.UnlockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)

	if _, err := client.RemoveMembers(ctx, id.DirectoryRoleId, &[]string{id.MemberId}); err != nil {
		return tf.ErrorDiagF(err, "Removing member %q from directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	return nil
}
<commit_msg>azuread_directory_role_member: check for consistency on deletion<commit_after>package directoryroles

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/manicminer/hamilton/msgraph"
	"github.com/manicminer/hamilton/odata"

	"github.com/hashicorp/terraform-provider-azuread/internal/clients"
	"github.com/hashicorp/terraform-provider-azuread/internal/helpers"
	"github.com/hashicorp/terraform-provider-azuread/internal/services/directoryroles/parse"
	"github.com/hashicorp/terraform-provider-azuread/internal/tf"
	"github.com/hashicorp/terraform-provider-azuread/internal/utils"
	"github.com/hashicorp/terraform-provider-azuread/internal/validate"
)

const directoryRoleMemberResourceName = "azuread_directory_role_member"

func directoryRoleMemberResource() *schema.Resource {
	return &schema.Resource{
		CreateContext: directoryRoleMemberResourceCreate,
		ReadContext:   directoryRoleMemberResourceRead,
		DeleteContext: directoryRoleMemberResourceDelete,

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(5 * time.Minute),
			Read:   schema.DefaultTimeout(5 * time.Minute),
			Update: schema.DefaultTimeout(5 * time.Minute),
			Delete: schema.DefaultTimeout(5 * time.Minute),
		},

		Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
			_, err := parse.DirectoryRoleMemberID(id)
			return err
		}),

		Schema: map[string]*schema.Schema{
			"role_object_id": {
				Description:      "The object ID of the directory role",
				Type:             schema.TypeString,
				Optional:         true,
				ForceNew:         true,
				ValidateDiagFunc: validate.UUID,
			},

			"member_object_id": {
				Description:      "The object ID of the member",
				Type:             schema.TypeString,
				Optional:         true,
				ForceNew:         true,
				ValidateDiagFunc: validate.UUID,
			},
		},
	}
}

func directoryRoleMemberResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient
	directoryObjectsClient := meta.(*clients.Client).DirectoryRoles.DirectoryObjectsClient

	id := parse.NewDirectoryRoleMemberID(d.Get("role_object_id").(string), d.Get("member_object_id").(string))

	tf.LockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)
	defer tf.UnlockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)

	role, status, err := client.Get(ctx, id.DirectoryRoleId)
	if err != nil {
		if status == http.StatusNotFound {
			return tf.ErrorDiagPathF(nil, "object_id", "Directory role with object ID %q was not found", id.DirectoryRoleId)
		}
		return tf.ErrorDiagPathF(err, "object_id", "Retrieving directory role with object ID: %q", id.DirectoryRoleId)
	}

	if _, status, err = client.GetMember(ctx, id.DirectoryRoleId, id.MemberId); err == nil {
		return tf.ImportAsExistsDiag("azuread_directory_role_member", id.String())
	} else if status != http.StatusNotFound {
		return tf.ErrorDiagF(err, "Checking for existing membership of member %q for directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	memberObject, _, err := directoryObjectsClient.Get(ctx, id.MemberId, odata.Query{})
	if err != nil {
		return tf.ErrorDiagF(err, "Could not retrieve member principal object %q", id.MemberId)
	}
	if memberObject == nil {
		return tf.ErrorDiagF(errors.New("returned memberObject was nil"), "Could not retrieve member principal object %q", id.MemberId)
	}
	// TODO: remove this workaround for https://github.com/hashicorp/terraform-provider-azuread/issues/588
	//if memberObject.ODataId == nil {
	//	return tf.ErrorDiagF(errors.New("ODataId was nil"), "Could not retrieve member principal object %q", id.MemberId)
	//}
	memberObject.ODataId = (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s",
		client.BaseClient.Endpoint, client.BaseClient.TenantId, id.MemberId)))

	role.Members = &msgraph.Members{*memberObject}

	if _, err := client.AddMembers(ctx, role); err != nil {
		return tf.ErrorDiagF(err, "Adding role member %q to directory role %q", id.MemberId, id.DirectoryRoleId)
	}

	// Wait for role membership to reflect
	deadline, ok := ctx.Deadline()
	if !ok {
		return tf.ErrorDiagF(errors.New("context has no deadline"), "Waiting for role member %q to reflect for directory role %q", id.MemberId, id.DirectoryRoleId)
	}
	timeout := time.Until(deadline)
	_, err = (&resource.StateChangeConf{
		Pending:                   []string{"Waiting"},
		Target:                    []string{"Done"},
		Timeout:                   timeout,
		MinTimeout:                1 * time.Second,
		ContinuousTargetOccurence: 3,
		Refresh: func() (interface{}, string, error) {
			_, status, err := client.GetMember(ctx, id.DirectoryRoleId, id.MemberId)
			if err != nil {
				if status == http.StatusNotFound {
					return "stub", "Waiting", nil
				}
				return nil, "Error", fmt.Errorf("retrieving role member")
			}
			return "stub", "Done", nil
		},
	}).WaitForStateContext(ctx)
	if err != nil {
		return tf.ErrorDiagF(err, "Waiting for role member %q to reflect for directory role %q", id.MemberId, id.DirectoryRoleId)
	}

	d.SetId(id.String())

	return directoryRoleMemberResourceRead(ctx, d, meta)
}

func directoryRoleMemberResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient

	id, err := parse.DirectoryRoleMemberID(d.Id())
	if err != nil {
		return tf.ErrorDiagPathF(err, "id", "Parsing Directory Role Member ID %q", d.Id())
	}

	if _, status, err := client.GetMember(ctx, id.DirectoryRoleId, id.MemberId); err != nil {
		if status == http.StatusNotFound {
			log.Printf("[DEBUG] Member with ID %q was not found in directory role %q - removing from state", id.MemberId, id.DirectoryRoleId)
			d.SetId("")
			return nil
		}
		return tf.ErrorDiagF(err, "Retrieving role member %q for directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	tf.Set(d, "role_object_id", id.DirectoryRoleId)
	tf.Set(d, "member_object_id", id.MemberId)

	return nil
}

func directoryRoleMemberResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(*clients.Client).DirectoryRoles.DirectoryRolesClient

	id, err := parse.DirectoryRoleMemberID(d.Id())
	if err != nil {
		return tf.ErrorDiagPathF(err, "id", "Parsing Directory Role Member ID %q", d.Id())
	}

	tf.LockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)
	defer tf.UnlockByName(directoryRoleMemberResourceName, id.DirectoryRoleId)

	if _, err := client.RemoveMembers(ctx, id.DirectoryRoleId, &[]string{id.MemberId}); err != nil {
		return tf.ErrorDiagF(err, "Removing member %q from directory role with object ID: %q", id.MemberId, id.DirectoryRoleId)
	}

	// Wait for membership link to be deleted
	if err := helpers.WaitForDeletion(ctx, func(ctx context.Context) (*bool, error) {
		client.BaseClient.DisableRetries = true
		if _, status, err := client.GetMember(ctx, id.DirectoryRoleId, id.MemberId); err != nil {
			if status == http.StatusNotFound {
				return utils.Bool(false), nil
			}
			return nil, err
		}
		return utils.Bool(true), nil
	}); err != nil {
		return tf.ErrorDiagF(err, "Waiting for removal of member %q from directory role with object ID %q", id.MemberId, id.DirectoryRoleId)
	}

	return nil
}
<|endoftext|>