<commit_before>package mssql

import (
	"context"
	"database/sql"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	metrics "github.com/armon/go-metrics"
	_ "github.com/denisenkom/go-mssqldb"
	"github.com/hashicorp/errwrap"
	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/vault/helper/strutil"
	"github.com/hashicorp/vault/physical"
)

// Verify MSSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*MSSQLBackend)(nil)

type MSSQLBackend struct {
	dbTable    string
	client     *sql.DB
	statements map[string]*sql.Stmt
	logger     log.Logger
	permitPool *physical.PermitPool
}

func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	username, ok := conf["username"]
	if !ok {
		username = ""
	}

	password, ok := conf["password"]
	if !ok {
		password = ""
	}

	server, ok := conf["server"]
	if !ok || server == "" {
		return nil, fmt.Errorf("missing server")
	}

	port, ok := conf["port"]
	if !ok {
		port = ""
	}

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	database, ok := conf["database"]
	if !ok {
		database = "Vault"
	}

	table, ok := conf["table"]
	if !ok {
		table = "Vault"
	}

	appname, ok := conf["appname"]
	if !ok {
		appname = "Vault"
	}

	connectionTimeout, ok := conf["connectiontimeout"]
	if !ok {
		connectionTimeout = "30"
	}

	logLevel, ok := conf["loglevel"]
	if !ok {
		logLevel = "0"
	}

	schema, ok := conf["schema"]
	if !ok || schema == "" {
		schema = "dbo"
	}

	connectionString := fmt.Sprintf("server=%s;app name=%s;connection timeout=%s;log=%s", server, appname, connectionTimeout, logLevel)
	if username != "" {
		connectionString += ";user id=" + username
	}

	if password != "" {
		connectionString += ";password=" + password
	}

	if port != "" {
		connectionString += ";port=" + port
	}

	db, err := sql.Open("mssql", connectionString)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to mssql: {{err}}", err)
	}

	db.SetMaxOpenConns(maxParInt)

	if _, err := db.Exec("IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '" + database + "') CREATE DATABASE " + database); err != nil {
		return nil, errwrap.Wrapf("failed to create mssql database: {{err}}", err)
	}

	dbTable := database + "." + schema + "." + table
	createQuery := "IF NOT EXISTS(SELECT 1 FROM " + database + ".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME='" + table + "' AND TABLE_SCHEMA='" + schema +
		"') CREATE TABLE " + dbTable + " (Path VARCHAR(512) PRIMARY KEY, Value VARBINARY(MAX))"

	if schema != "dbo" {
		if _, err := db.Exec("USE " + database); err != nil {
			return nil, errwrap.Wrapf("failed to switch mssql database: {{err}}", err)
		}

		var num int
		err = db.QueryRow("SELECT 1 FROM sys.schemas WHERE name = '" + schema + "'").Scan(&num)

		switch {
		case err == sql.ErrNoRows:
			if _, err := db.Exec("CREATE SCHEMA " + schema); err != nil {
				return nil, errwrap.Wrapf("failed to create mssql schema: {{err}}", err)
			}

		case err != nil:
			return nil, errwrap.Wrapf("failed to check if mssql schema exists: {{err}}", err)
		}
	}

	if _, err := db.Exec(createQuery); err != nil {
		return nil, errwrap.Wrapf("failed to create mssql table: {{err}}", err)
	}

	m := &MSSQLBackend{
		dbTable:    dbTable,
		client:     db,
		statements: make(map[string]*sql.Stmt),
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
	}

	statements := map[string]string{
		"put": "IF EXISTS(SELECT 1 FROM " + dbTable + " WHERE Path = ?) UPDATE " + dbTable + " SET Value = ? WHERE Path = ?" +
			" ELSE INSERT INTO " + dbTable + " VALUES(?, ?)",
		"get":    "SELECT Value FROM " + dbTable + " WHERE Path = ?",
		"delete": "DELETE FROM " + dbTable + " WHERE Path = ?",
		"list":   "SELECT Path FROM " + dbTable + " WHERE Path LIKE ?",
	}

	for name, query := range statements {
		if err := m.prepare(name, query); err != nil {
			return nil, err
		}
	}

	return m, nil
}

func (m *MSSQLBackend) prepare(name, query string) error {
	stmt, err := m.client.Prepare(query)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("failed to prepare %q: {{err}}", name), err)
	}

	m.statements[name] = stmt

	return nil
}

func (m *MSSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"mssql", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, err := m.statements["put"].Exec(entry.Key, entry.Value, entry.Key, entry.Key, entry.Value)
	if err != nil {
		return err
	}

	return nil
}

func (m *MSSQLBackend) Get(ctx context.Context, key string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"mssql", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	var result []byte
	err := m.statements["get"].QueryRow(key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   key,
		Value: result,
	}

	return ent, nil
}

func (m *MSSQLBackend) Delete(ctx context.Context, key string) error {
	defer metrics.MeasureSince([]string{"mssql", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, err := m.statements["delete"].Exec(key)
	if err != nil {
		return err
	}

	return nil
}

func (m *MSSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"mssql", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	likePrefix := prefix + "%"
	rows, err := m.statements["list"].Query(likePrefix)
	if err != nil {
		return nil, err
	}
	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		key = strings.TrimPrefix(key, prefix)
		if i := strings.Index(key, "/"); i == -1 {
			keys = append(keys, key)
		} else if i != -1 {
			keys = strutil.AppendIfMissing(keys, string(key[:i+1]))
		}
	}

	sort.Strings(keys)

	return keys, nil
}
<commit_msg>Update mssql.go<commit_after>package mssql

import (
	"context"
	"database/sql"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	metrics "github.com/armon/go-metrics"
	_ "github.com/denisenkom/go-mssqldb"
	"github.com/hashicorp/errwrap"
	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/vault/helper/strutil"
	"github.com/hashicorp/vault/physical"
)

// Verify MSSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*MSSQLBackend)(nil)

type MSSQLBackend struct {
	dbTable    string
	client     *sql.DB
	statements map[string]*sql.Stmt
	logger     log.Logger
	permitPool *physical.PermitPool
}

func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	username, ok := conf["username"]
	if !ok {
		username = ""
	}

	password, ok := conf["password"]
	if !ok {
		password = ""
	}

	server, ok := conf["server"]
	if !ok || server == "" {
		return nil, fmt.Errorf("missing server")
	}

	port, ok := conf["port"]
	if !ok {
		port = ""
	}

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	database, ok := conf["database"]
	if !ok {
		database = "Vault"
	}

	table, ok := conf["table"]
	if !ok {
		table = "Vault"
	}

	appname, ok := conf["appname"]
	if !ok {
		appname = "Vault"
	}

	connectionTimeout, ok := conf["connectiontimeout"]
	if !ok {
		connectionTimeout = "30"
	}

	logLevel, ok := conf["loglevel"]
	if !ok {
		logLevel = "0"
	}

	schema, ok := conf["schema"]
	if !ok || schema == "" {
		schema = "dbo"
	}

	connectionString := fmt.Sprintf("server=%s;app name=%s;connection timeout=%s;log=%s", server, appname, connectionTimeout, logLevel)
	if username != "" {
		connectionString += ";user id=" + username
	}

	if password != "" {
		connectionString += ";password=" + password
	}

	if port != "" {
		connectionString += ";port=" + port
	}

	db, err := sql.Open("mssql", connectionString)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to mssql: {{err}}", err)
	}

	db.SetMaxOpenConns(maxParInt)

	if _, err := db.Exec("IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '" + database + "') CREATE DATABASE " + database); err != nil {
		return nil, errwrap.Wrapf("failed to create mssql database: {{err}}", err)
	}

	dbTable := database + "." + schema + "." + table
	createQuery := "IF NOT EXISTS(SELECT 1 FROM " + database + ".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME='" + table + "' AND TABLE_SCHEMA='" + schema +
		"') CREATE TABLE " + dbTable + " (Path VARCHAR(512) PRIMARY KEY, Value VARBINARY(MAX))"

	if schema != "dbo" {
		if _, err := db.Exec("USE " + database); err != nil {
			return nil, errwrap.Wrapf("failed to switch mssql database: {{err}}", err)
		}

		var num int
		err = db.QueryRow("SELECT 1 FROM " + database + ".sys.schemas WHERE name = '" + schema + "'").Scan(&num)

		switch {
		case err == sql.ErrNoRows:
			if _, err := db.Exec("CREATE SCHEMA " + schema); err != nil {
				return nil, errwrap.Wrapf("failed to create mssql schema: {{err}}", err)
			}

		case err != nil:
			return nil, errwrap.Wrapf("failed to check if mssql schema exists: {{err}}", err)
		}
	}

	if _, err := db.Exec(createQuery); err != nil {
		return nil, errwrap.Wrapf("failed to create mssql table: {{err}}", err)
	}

	m := &MSSQLBackend{
		dbTable:    dbTable,
		client:     db,
		statements: make(map[string]*sql.Stmt),
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
	}

	statements := map[string]string{
		"put": "IF EXISTS(SELECT 1 FROM " + dbTable + " WHERE Path = ?) UPDATE " + dbTable + " SET Value = ? WHERE Path = ?" +
			" ELSE INSERT INTO " + dbTable + " VALUES(?, ?)",
		"get":    "SELECT Value FROM " + dbTable + " WHERE Path = ?",
		"delete": "DELETE FROM " + dbTable + " WHERE Path = ?",
		"list":   "SELECT Path FROM " + dbTable + " WHERE Path LIKE ?",
	}

	for name, query := range statements {
		if err := m.prepare(name, query); err != nil {
			return nil, err
		}
	}

	return m, nil
}

func (m *MSSQLBackend) prepare(name, query string) error {
	stmt, err := m.client.Prepare(query)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("failed to prepare %q: {{err}}", name), err)
	}

	m.statements[name] = stmt

	return nil
}

func (m *MSSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"mssql", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, err := m.statements["put"].Exec(entry.Key, entry.Value, entry.Key, entry.Key, entry.Value)
	if err != nil {
		return err
	}

	return nil
}

func (m *MSSQLBackend) Get(ctx context.Context, key string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"mssql", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	var result []byte
	err := m.statements["get"].QueryRow(key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   key,
		Value: result,
	}

	return ent, nil
}

func (m *MSSQLBackend) Delete(ctx context.Context, key string) error {
	defer metrics.MeasureSince([]string{"mssql", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, err := m.statements["delete"].Exec(key)
	if err != nil {
		return err
	}

	return nil
}

func (m *MSSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"mssql", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	likePrefix := prefix + "%"
	rows, err := m.statements["list"].Query(likePrefix)
	if err != nil {
		return nil, err
	}
	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		key = strings.TrimPrefix(key, prefix)
		if i := strings.Index(key, "/"); i == -1 {
			keys = append(keys, key)
		} else if i != -1 {
			keys = strutil.AppendIfMissing(keys, string(key[:i+1]))
		}
	}

	sort.Strings(keys)

	return keys, nil
}
<|endoftext|>


<commit_before>// Package scoop provides a Pipe that generates a scoop.sh App Manifest and pushes it to a bucket
package scoop

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/client"
	"github.com/goreleaser/goreleaser/pipeline"
)

// ErrNoWindows when there is no build for windows (goos doesn't contain windows)
var ErrNoWindows = errors.New("scoop requires a windows build")

// Pipe for build
type Pipe struct{}

func (Pipe) String() string {
	return "creating Scoop Manifest"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	client, err := client.NewGitHub(ctx)
	if err != nil {
		return err
	}
	return doRun(ctx, client)
}

// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
	if ctx.Config.Scoop.CommitAuthor.Name == "" {
		ctx.Config.Scoop.CommitAuthor.Name = "goreleaserbot"
	}
	if ctx.Config.Scoop.CommitAuthor.Email == "" {
		ctx.Config.Scoop.CommitAuthor.Email = "goreleaser@carlosbecker.com"
	}
	return nil
}

func doRun(ctx *context.Context, client client.Client) error {
	if ctx.Config.Scoop.Bucket.Name == "" {
		return pipeline.Skip("scoop section is not configured")
	}
	if ctx.Config.Archive.Format == "binary" {
		return pipeline.Skip("archive format is binary")
	}

	var archives = ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("windows"),
			artifact.ByType(artifact.UploadableArchive),
		),
	).List()
	if len(archives) == 0 {
		return ErrNoWindows
	}

	path := ctx.Config.ProjectName + ".json"

	content, err := buildManifest(ctx, client, archives)
	if err != nil {
		return err
	}

	if !ctx.Publish {
		return pipeline.ErrSkipPublish
	}
	if ctx.Config.Release.Draft {
		return pipeline.Skip("release is marked as draft")
	}

	return client.CreateFile(
		ctx,
		ctx.Config.Scoop.CommitAuthor,
		ctx.Config.Scoop.Bucket,
		content,
		path)
}

// Manifest represents a scoop.sh App Manifest, more info:
// https://github.com/lukesampson/scoop/wiki/App-Manifests
type Manifest struct {
	Version      string              `json:"version"`               // The version of the app that this manifest installs.
	Architecture map[string]Resource `json:"architecture"`          // `architecture`: If the app has 32- and 64-bit versions, architecture can be used to wrap the differences.
	Homepage     string              `json:"homepage,omitempty"`    // `homepage`: The home page for the program.
	License      string              `json:"license,omitempty"`     // `license`: The software license for the program. For well-known licenses, this will be a string like "MIT" or "GPL2". For custom licenses, this should be the URL of the license.
	Description  string              `json:"description,omitempty"` // Description of the app
}

// Resource represents a combination of a url and a binary name for an architecture
type Resource struct {
	URL string `json:"url"` // URL to the archive
	Bin string `json:"bin"` // name of binary inside the archive
}

func buildManifest(ctx *context.Context, client client.Client, artifacts []artifact.Artifact) (result bytes.Buffer, err error) {
	var githubURL = "https://github.com"
	if ctx.Config.GitHubURLs.Download != "" {
		githubURL = ctx.Config.GitHubURLs.Download
	}

	manifest := Manifest{
		Version:      ctx.Version,
		Architecture: make(map[string]Resource),
		Homepage:     ctx.Config.Scoop.Homepage,
		License:      ctx.Config.Scoop.License,
		Description:  ctx.Config.Scoop.Description,
	}

	for _, artifact := range artifacts {
		if artifact.Goarch == "amd64" {
			manifest.Architecture["64bit"] = Resource{
				URL: getDownloadURL(ctx, githubURL, artifact.Name),
				Bin: ctx.Config.Builds[0].Binary + ".exe",
			}
		} else if artifact.Goarch == "386" {
			manifest.Architecture["32bit"] = Resource{
				URL: getDownloadURL(ctx, githubURL, artifact.Name),
				Bin: ctx.Config.Builds[0].Binary + ".exe",
			}
		}
	}

	data, err := json.MarshalIndent(manifest, "", "    ")
	if err != nil {
		return
	}
	_, err = result.Write(data)

	return
}

func getDownloadURL(ctx *context.Context, githubURL, file string) (url string) {
	return fmt.Sprintf("%s/%s/%s/releases/download/%s/%s",
		githubURL,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		ctx.Version,
		file)
}
<commit_msg>add: TODO comment<commit_after>// Package scoop provides a Pipe that generates a scoop.sh App Manifest and pushes it to a bucket
package scoop

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/client"
	"github.com/goreleaser/goreleaser/pipeline"
)

// ErrNoWindows when there is no build for windows (goos doesn't contain windows)
var ErrNoWindows = errors.New("scoop requires a windows build")

// Pipe for build
type Pipe struct{}

func (Pipe) String() string {
	return "creating Scoop Manifest"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	client, err := client.NewGitHub(ctx)
	if err != nil {
		return err
	}
	return doRun(ctx, client)
}

// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
	if ctx.Config.Scoop.CommitAuthor.Name == "" {
		ctx.Config.Scoop.CommitAuthor.Name = "goreleaserbot"
	}
	if ctx.Config.Scoop.CommitAuthor.Email == "" {
		ctx.Config.Scoop.CommitAuthor.Email = "goreleaser@carlosbecker.com"
	}
	return nil
}

func doRun(ctx *context.Context, client client.Client) error {
	if ctx.Config.Scoop.Bucket.Name == "" {
		return pipeline.Skip("scoop section is not configured")
	}
	if ctx.Config.Archive.Format == "binary" {
		return pipeline.Skip("archive format is binary")
	}

	var archives = ctx.Artifacts.Filter(
		artifact.And(
			artifact.ByGoos("windows"),
			artifact.ByType(artifact.UploadableArchive),
		),
	).List()
	if len(archives) == 0 {
		return ErrNoWindows
	}

	path := ctx.Config.ProjectName + ".json"

	content, err := buildManifest(ctx, client, archives)
	if err != nil {
		return err
	}

	if !ctx.Publish {
		return pipeline.ErrSkipPublish
	}
	if ctx.Config.Release.Draft {
		return pipeline.Skip("release is marked as draft")
	}

	return client.CreateFile(
		ctx,
		ctx.Config.Scoop.CommitAuthor,
		ctx.Config.Scoop.Bucket,
		content,
		path)
}

// Manifest represents a scoop.sh App Manifest, more info:
// https://github.com/lukesampson/scoop/wiki/App-Manifests
type Manifest struct {
	Version      string              `json:"version"`               // The version of the app that this manifest installs.
	Architecture map[string]Resource `json:"architecture"`          // `architecture`: If the app has 32- and 64-bit versions, architecture can be used to wrap the differences.
	Homepage     string              `json:"homepage,omitempty"`    // `homepage`: The home page for the program.
	License      string              `json:"license,omitempty"`     // `license`: The software license for the program. For well-known licenses, this will be a string like "MIT" or "GPL2". For custom licenses, this should be the URL of the license.
	Description  string              `json:"description,omitempty"` // Description of the app
}

// Resource represents a combination of a url and a binary name for an architecture
type Resource struct {
	URL string `json:"url"` // URL to the archive
	Bin string `json:"bin"` // name of binary inside the archive
}

func buildManifest(ctx *context.Context, client client.Client, artifacts []artifact.Artifact) (result bytes.Buffer, err error) {
	var githubURL = "https://github.com"
	// TODO: Make this default global
	if ctx.Config.GitHubURLs.Download != "" {
		githubURL = ctx.Config.GitHubURLs.Download
	}

	manifest := Manifest{
		Version:      ctx.Version,
		Architecture: make(map[string]Resource),
		Homepage:     ctx.Config.Scoop.Homepage,
		License:      ctx.Config.Scoop.License,
		Description:  ctx.Config.Scoop.Description,
	}

	for _, artifact := range artifacts {
		if artifact.Goarch == "amd64" {
			manifest.Architecture["64bit"] = Resource{
				URL: getDownloadURL(ctx, githubURL, artifact.Name),
				Bin: ctx.Config.Builds[0].Binary + ".exe",
			}
		} else if artifact.Goarch == "386" {
			manifest.Architecture["32bit"] = Resource{
				URL: getDownloadURL(ctx, githubURL, artifact.Name),
				Bin: ctx.Config.Builds[0].Binary + ".exe",
			}
		}
	}

	data, err := json.MarshalIndent(manifest, "", "    ")
	if err != nil {
		return
	}
	_, err = result.Write(data)

	return
}

func getDownloadURL(ctx *context.Context, githubURL, file string) (url string) {
	return fmt.Sprintf("%s/%s/%s/releases/download/%s/%s",
		githubURL,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		ctx.Version,
		file)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gce

import (
	"fmt"

	storage "google.golang.org/api/storage/v1"
	"k8s.io/klog"
	"k8s.io/kops/pkg/acls"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kops/upup/pkg/fi/cloudup/gce"
	"k8s.io/kops/util/pkg/vfs"
)

// gcsAclStrategy is the AclStrategy for objects written to google cloud storage
type gcsAclStrategy struct {
}

var _ acls.ACLStrategy = &gcsAclStrategy{}

// GetACL returns the ACL to use if this is a google cloud storage path
func (s *gcsAclStrategy) GetACL(p vfs.Path, cluster *kops.Cluster) (vfs.ACL, error) {
	if kops.CloudProviderID(cluster.Spec.CloudProvider) != kops.CloudProviderGCE {
		return nil, nil
	}
	gcsPath, ok := p.(*vfs.GSPath)
	if !ok {
		return nil, nil
	}

	bucketName := gcsPath.Bucket()
	client := gcsPath.Client()

	// TODO: Cache?
	bucket, err := client.Buckets.Get(bucketName).Do()
	if err != nil {
		return nil, fmt.Errorf("error querying bucket %q: %v", bucketName, err)
	}

	bucketPolicyOnly := false
	if bucket.IamConfiguration != nil && bucket.IamConfiguration.BucketPolicyOnly != nil {
		bucketPolicyOnly = bucket.IamConfiguration.BucketPolicyOnly.Enabled
	}

	if bucketPolicyOnly {
		klog.V(2).Infof("bucket gs://%s has bucket-policy only; won't try to set ACLs", bucketName)
		return nil, nil
	}

	// TODO: Cache?
	cloud, err := cloudup.BuildCloud(cluster)
	if err != nil {
		return nil, err
	}

	serviceAccount, err := cloud.(gce.GCECloud).ServiceAccount()
	if err != nil {
		return nil, err
	}

	var acls []*storage.ObjectAccessControl
	acls = append(acls, bucket.DefaultObjectAcl...)

	acls = append(acls, &storage.ObjectAccessControl{
		Email:  serviceAccount,
		Entity: "user-" + serviceAccount,
		Role:   "READER",
	})

	return &vfs.GSAcl{
		Acl: acls,
	}, nil
}

func Register() {
	acls.RegisterPlugin("k8s.io/kops/acl/gce", &gcsAclStrategy{})
}
<commit_msg>gce: log bucket-policy-only message at a level that always appears<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gce

import (
	"fmt"

	storage "google.golang.org/api/storage/v1"
	"k8s.io/klog"
	"k8s.io/kops/pkg/acls"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kops/upup/pkg/fi/cloudup/gce"
	"k8s.io/kops/util/pkg/vfs"
)

// gcsAclStrategy is the AclStrategy for objects written to google cloud storage
type gcsAclStrategy struct {
}

var _ acls.ACLStrategy = &gcsAclStrategy{}

// GetACL returns the ACL to use if this is a google cloud storage path
func (s *gcsAclStrategy) GetACL(p vfs.Path, cluster *kops.Cluster) (vfs.ACL, error) {
	if kops.CloudProviderID(cluster.Spec.CloudProvider) != kops.CloudProviderGCE {
		return nil, nil
	}
	gcsPath, ok := p.(*vfs.GSPath)
	if !ok {
		return nil, nil
	}

	bucketName := gcsPath.Bucket()
	client := gcsPath.Client()

	// TODO: Cache?
	bucket, err := client.Buckets.Get(bucketName).Do()
	if err != nil {
		return nil, fmt.Errorf("error querying bucket %q: %v", bucketName, err)
	}

	bucketPolicyOnly := false
	if bucket.IamConfiguration != nil && bucket.IamConfiguration.BucketPolicyOnly != nil {
		bucketPolicyOnly = bucket.IamConfiguration.BucketPolicyOnly.Enabled
	}

	if bucketPolicyOnly {
		klog.Infof("bucket gs://%s has bucket-policy only; won't try to set ACLs", bucketName)
		return nil, nil
	}

	// TODO: Cache?
	cloud, err := cloudup.BuildCloud(cluster)
	if err != nil {
		return nil, err
	}

	serviceAccount, err := cloud.(gce.GCECloud).ServiceAccount()
	if err != nil {
		return nil, err
	}

	var acls []*storage.ObjectAccessControl
	acls = append(acls, bucket.DefaultObjectAcl...)

	acls = append(acls, &storage.ObjectAccessControl{
		Email:  serviceAccount,
		Entity: "user-" + serviceAccount,
		Role:   "READER",
	})

	return &vfs.GSAcl{
		Acl: acls,
	}, nil
}

func Register() {
	acls.RegisterPlugin("k8s.io/kops/acl/gce", &gcsAclStrategy{})
}
<|endoftext|>


<commit_before>package api_test

import (
	"bytes"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/jmoiron/sqlx"
	"github.com/jvikstedt/watchful"
	"github.com/jvikstedt/watchful/pkg/api"
	"github.com/jvikstedt/watchful/pkg/exec/builtin"
	"github.com/jvikstedt/watchful/pkg/model"
)

var testHandler http.Handler
var db *sqlx.DB

func TestMain(m *testing.M) {
	logs := &bytes.Buffer{}
	logger := log.New(logs, "", log.LstdFlags)

	var err error
	db, err = model.NewDB("sqlite3", "./test.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	if err := model.EnsureTables(db); err != nil {
		log.Fatal(err)
	}

	executorMock := &executorMock{}
	testHandler = api.New(logger, db, executorMock)

	retCode := m.Run()
	os.Exit(retCode)
}

func makeRequest(t *testing.T, method string, path string, body string) (int, *bytes.Buffer) {
	b := bytes.NewBufferString(body)
	req, err := http.NewRequest(method, "/api/v1"+path, b)
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Add("Content-Type", "application/json")
	rr := httptest.NewRecorder()
	testHandler.ServeHTTP(rr, req)

	return rr.Result().StatusCode, rr.Body
}

type executorMock struct {
}

func (s *executorMock) AddJob(job *model.Job, isTestRun bool) string {
	return "test"
}

func (s *executorMock) Executables() map[string]watchful.Executable {
	return map[string]watchful.Executable{
		"http":  builtin.HTTP{},
		"equal": builtin.Equal{},
	}
}
<commit_msg>Fixed tests<commit_after>package api_test

import (
	"bytes"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/jmoiron/sqlx"
	"github.com/jvikstedt/watchful"
	"github.com/jvikstedt/watchful/pkg/api"
	"github.com/jvikstedt/watchful/pkg/exec/builtin"
	"github.com/jvikstedt/watchful/pkg/model"
	"github.com/jvikstedt/watchful/pkg/schedule"
)

var testHandler http.Handler
var db *sqlx.DB

func TestMain(m *testing.M) {
	logs := &bytes.Buffer{}
	logger := log.New(logs, "", log.LstdFlags)

	var err error
	db, err = model.NewDB("sqlite3", "./test.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	if err := model.EnsureTables(db); err != nil {
		log.Fatal(err)
	}

	executorMock := &executorMock{}
	schedulerMock := &schedule.MockScheduler{}
	testHandler = api.New(logger, db, executorMock, schedulerMock)

	retCode := m.Run()
	os.Exit(retCode)
}

func makeRequest(t *testing.T, method string, path string, body string) (int, *bytes.Buffer) {
	b := bytes.NewBufferString(body)
	req, err := http.NewRequest(method, "/api/v1"+path, b)
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Add("Content-Type", "application/json")
	rr := httptest.NewRecorder()
	testHandler.ServeHTTP(rr, req)

	return rr.Result().StatusCode, rr.Body
}

type executorMock struct {
}

func (s *executorMock) AddJob(job *model.Job, isTestRun bool) string {
	return "test"
}

func (s *executorMock) Executables() map[string]watchful.Executable {
	return map[string]watchful.Executable{
		"http":  builtin.HTTP{},
		"equal": builtin.Equal{},
	}
}
<|endoftext|>


<commit_before>package settings

import (
	"encoding/json"
	"os"
	"strings"

	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
)

var (
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master")
	AuthImage                         = NewSetting("auth-image", v3.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/19.03.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	HelmVersion                       = NewSetting("helm-version", "dev")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager")
	TelemetryOpt                      = NewSetting("telemetry-opt", "prompt")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	SystemExternalDNSCatalogID        = NewSetting("system-externaldns-catalog-id", "catalog://?catalog=system-library&template=rancher-external-dns&version=0.0.2")
	SystemCISBenchmarkCatalogID       = NewSetting("system-cis-benchmark-catalog-id", "catalog://?catalog=system-library&template=rancher-cis-benchmark&version=0.1.0")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
)

func init() {
	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func getMetadataConfig() string {
	data := map[string]interface{}{
		"url":                      "https://github.com/prachidamle/kontainer-driver-metadata.git",
		"branch":                   "no_windows_22",
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err == nil {
		return string(ans)
	}
	return ""
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}
<commit_msg>Fix the RKEMetadataConfig setting<commit_after>package settings

import (
	"encoding/json"
	"os"
	"strings"

	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
)

var (
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master")
	AuthImage                         = NewSetting("auth-image", v3.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/19.03.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	HelmVersion                       = NewSetting("helm-version", "dev")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager")
	TelemetryOpt                      = NewSetting("telemetry-opt", "prompt")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	SystemExternalDNSCatalogID        = NewSetting("system-externaldns-catalog-id", "catalog://?catalog=system-library&template=rancher-external-dns&version=0.0.2")
	SystemCISBenchmarkCatalogID       = NewSetting("system-cis-benchmark-catalog-id", "catalog://?catalog=system-library&template=rancher-cis-benchmark&version=0.1.0")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
)

func init() {
	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func getMetadataConfig() string {
	data := map[string]interface{}{
		"url":                      "https://github.com/rancher/kontainer-driver-metadata.git",
		"branch":                   "master",
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err == nil {
		return string(ans)
	}
	return ""
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"fmt"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/gigawattio/go-commons/pkg/errorlib"
	"github.com/gigawattio/go-commons/pkg/testlib"
	service "github.com/gigawattio/go-commons/pkg/web/cli/example/service"
	"github.com/gigawattio/go-commons/pkg/web/interfaces"

	"github.com/parnurzeal/gorequest"
	cliv2 "gopkg.in/urfave/cli.v2"
)

func simpleWebServiceProvider(ctx *cliv2.Context) (interfaces.WebService, error) {
	return service.New(ctx.String("bind")), nil
}

func TestCli(t *testing.T) {
	options := Options{
		AppName:            testlib.CurrentRunningTest(),
		WebServiceProvider: simpleWebServiceProvider,
		Args:               []string{"-b", "127.0.0.1:0"},
	}
	cli, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	cli.App.Action = func(ctx *cliv2.Context) error {
		webService, err := options.WebServiceProvider(ctx)
		if err != nil {
			return err
		}
		if err := webService.Start(); err != nil {
			t.Fatal(err)
		}
		resp, body, errs := gorequest.New().Get(fmt.Sprintf("http://%s/", webService.Addr())).End()
		if err := errorlib.Merge(errs); err != nil {
			t.Error(err)
		}
		if expected := http.StatusOK; resp.StatusCode != expected {
			t.Errorf("Expected response status-code=%v but actual=%v", expected, resp.StatusCode)
		}
		if expected := "hello world"; body != expected {
			t.Errorf("Expected response body=%q but actual=%q", expected, body)
		}
		if err := webService.Stop(); err != nil {
			t.Error(err)
		}
		return nil
	}
	if err := cli.Main(); err != nil {
		t.Fatal(err)
	}
}

func TestCliAppNameError(t *testing.T) {
	options := Options{
		WebServiceProvider: simpleWebServiceProvider,
		Args:               []string{"-b", "127.0.0.1:0"},
	}
	_, err := New(options)
	if expected := AppNameRequiredError; err != expected {
		t.Fatalf("Expected error=%v but actual=%s", expected, err)
	}
}

func TestCliWebServiceProviderError(t *testing.T) {
	options := Options{
		AppName: testlib.CurrentRunningTest(),
		Args:    []string{"-b", "127.0.0.1:0"},
	}
	_, err := New(options)
	if expected := WebServiceProviderRequiredError; err != expected {
		t.Fatalf("Expected error=%v but actual=%s", expected, err)
	}
}

func brokenWebServiceProvider(ctx *cliv2.Context) (interfaces.WebService, error) {
	return nil, nil
}

func TestCliBrokenWebServiceProvider(t *testing.T) {
	var (
		fakeStderr = &bytes.Buffer{}
		options    = Options{
			AppName:            testlib.CurrentRunningTest(),
			Usage:              "Fully automatic :)",
			Version:            "1024.2048.4096",
			Args:               []string{"-b", "127.0.0.1:0"},
			WebServiceProvider: brokenWebServiceProvider,
			Stderr:             fakeStderr,      // Suppress os.Stderr output.
			Stdout:             &bytes.Buffer{}, // Suppress os.Stderr output.
			ExitOnError:        false,
		}
	)
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}

	ch := make(chan error, 2)
	go func() { ch <- c.Main() }()
	select {
	case actual := <-ch:
		if expected := NilWebServiceError; actual != expected {
			t.Errorf("Expected error=%v but actual=%s", expected, actual)
		}
		if expected, actual := NilWebServiceError.Error(), strings.Trim(fakeStderr.String(), "\n\t "); actual != expected {
			t.Errorf("Expected stderr to contain value=%q but actual=%q", expected, actual)
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("Timed out after 100ms waiting for expected error")
	}
}

func TestCliOutputDefaults(t *testing.T) {
	options := Options{
		AppName:            testlib.CurrentRunningTest(),
		Args:               []string{"-b", "127.0.0.1:0"},
		WebServiceProvider: simpleWebServiceProvider,
	}
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	if c.App.Writer != os.Stdout {
		t.Errorf("Expected c.App.Writer == os.Stdout but it was set to something else instead; actual value=%T/%p", c.App.Writer, c.App.Writer)
	}
	if c.App.ErrWriter != os.Stderr {
		t.Errorf("Expected c.App.ErrWriter == os.Stderr but it was set to something else instead; actual value=%T/%p", c.App.ErrWriter, c.App.ErrWriter)
	}
}

func TestCliOutputOverrides(t *testing.T) {
	var (
		fakeStdout = &bytes.Buffer{}
		fakeStderr = &bytes.Buffer{}
		options    = Options{
			AppName:            testlib.CurrentRunningTest(),
			Args:               []string{"-b", "127.0.0.1:0"},
			WebServiceProvider: simpleWebServiceProvider,
			Stdout:             fakeStdout,
			Stderr:             fakeStderr,
		}
	)
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	if c.App.Writer != fakeStdout {
		t.Errorf("Expected c.App.Writer == fakeStdout (*bytes.Buffer) but it was set to something else instead; actual value=%T/%p", c.App.Writer, c.App.Writer)
	}
	if c.App.ErrWriter != fakeStderr {
		t.Errorf("Expected c.App.ErrWriter == fakeStderr (*bytes.Buffer) but it was set to something else instead; actual value=%T/%p", c.App.ErrWriter, c.App.ErrWriter)
	}
}
<commit_msg>Ensure unit-test cli args include binary filename.<commit_after>package cli

import (
	"bytes"
	"fmt"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/gigawattio/go-commons/pkg/errorlib"
	"github.com/gigawattio/go-commons/pkg/testlib"
	service "github.com/gigawattio/go-commons/pkg/web/cli/example/service"
	"github.com/gigawattio/go-commons/pkg/web/interfaces"

	"github.com/parnurzeal/gorequest"
	cliv2 "gopkg.in/urfave/cli.v2"
)

func simpleWebServiceProvider(ctx *cliv2.Context) (interfaces.WebService, error) {
	return service.New(ctx.String("bind")), nil
}

// genTestCliArgs prepends the current running tests name to the slice (in lieu
// of the binary filename) to form a slice whose contents mimic realistic
// command-line arguments.
func genTestCliArgs(args ...string) []string {
	args = append([]string{testlib.CurrentRunningTest()}, args...)
	return args
}

func TestCli(t *testing.T) {
	options := Options{
		AppName:            testlib.CurrentRunningTest(),
		WebServiceProvider: simpleWebServiceProvider,
		Args:               genTestCliArgs("--bind", "127.0.0.1:0"),
	}
	cli, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	cli.App.Action = func(ctx *cliv2.Context) error {
		webService, err := options.WebServiceProvider(ctx)
		if err != nil {
			return err
		}
		if err := webService.Start(); err != nil {
			t.Fatal(err)
		}
		resp, body, errs := gorequest.New().Get(fmt.Sprintf("http://%s/", webService.Addr())).End()
		if err := errorlib.Merge(errs); err != nil {
			t.Error(err)
		}
		if expected := http.StatusOK; resp.StatusCode != expected {
			t.Errorf("Expected response status-code=%v but actual=%v", expected, resp.StatusCode)
		}
		if expected := "hello world"; body != expected {
			t.Errorf("Expected response body=%q but actual=%q", expected, body)
		}
		if err := webService.Stop(); err != nil {
			t.Error(err)
		}
		return nil
	}
	if err := cli.Main(); err != nil {
		t.Fatal(err)
	}
}

func TestCliAppNameError(t *testing.T) {
	options := Options{
		WebServiceProvider: simpleWebServiceProvider,
		Args:               genTestCliArgs("-b", "127.0.0.1:0"),
	}
	_, err := New(options)
	if expected := AppNameRequiredError; err != expected {
		t.Fatalf("Expected error=%v but actual=%s", expected, err)
	}
}

func TestCliWebServiceProviderError(t *testing.T) {
	options := Options{
		AppName: testlib.CurrentRunningTest(),
		Args:    genTestCliArgs("-b", "127.0.0.1:0"),
	}
	_, err := New(options)
	if expected := WebServiceProviderRequiredError; err != expected {
		t.Fatalf("Expected error=%v but actual=%s", expected, err)
	}
}

func brokenWebServiceProvider(ctx *cliv2.Context) (interfaces.WebService, error) {
	return nil, nil
}

func TestCliBrokenWebServiceProvider(t *testing.T) {
	var (
		fakeStderr = &bytes.Buffer{}
		options    = Options{
			AppName:            testlib.CurrentRunningTest(),
			Usage:              "Fully automatic :)",
			Version:            "1024.2048.4096",
			Args:               genTestCliArgs("-b", "127.0.0.1:0"),
			WebServiceProvider: brokenWebServiceProvider,
			Stderr:             fakeStderr,      // Suppress os.Stderr output.
			Stdout:             &bytes.Buffer{}, // Suppress os.Stderr output.
			ExitOnError:        false,
		}
	)
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}

	ch := make(chan error, 2)
	go func() { ch <- c.Main() }()
	select {
	case actual := <-ch:
		if expected := NilWebServiceError; actual != expected {
			t.Errorf("Expected error=%v but actual=%s", expected, actual)
		}
		if expected, actual := NilWebServiceError.Error(), strings.Trim(fakeStderr.String(), "\n\t "); actual != expected {
			t.Errorf("Expected stderr to contain value=%q but actual=%q", expected, actual)
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("Timed out after 100ms waiting for expected error")
	}
}

func TestCliOutputDefaults(t *testing.T) {
	options := Options{
		AppName:            testlib.CurrentRunningTest(),
		Args:               genTestCliArgs("-b", "127.0.0.1:0"),
		WebServiceProvider: simpleWebServiceProvider,
	}
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	if c.App.Writer != os.Stdout {
		t.Errorf("Expected c.App.Writer == os.Stdout but it was set to something else instead; actual value=%T/%p", c.App.Writer, c.App.Writer)
	}
	if c.App.ErrWriter != os.Stderr {
		t.Errorf("Expected c.App.ErrWriter == os.Stderr but it was set to something else instead; actual value=%T/%p", c.App.ErrWriter, c.App.ErrWriter)
	}
}

func TestCliOutputOverrides(t *testing.T) {
	var (
		fakeStdout = &bytes.Buffer{}
		fakeStderr = &bytes.Buffer{}
		options    = Options{
			AppName:            testlib.CurrentRunningTest(),
			Args:               genTestCliArgs("-b", "127.0.0.1:0"),
			WebServiceProvider: simpleWebServiceProvider,
			Stdout:             fakeStdout,
			Stderr:             fakeStderr,
		}
	)
	c, err := New(options)
	if err != nil {
		t.Fatal(err)
	}
	if c.App.Writer != fakeStdout {
		t.Errorf("Expected c.App.Writer == fakeStdout (*bytes.Buffer) but it was set to something else instead; actual value=%T/%p", c.App.Writer, c.App.Writer)
	}
	if c.App.ErrWriter != fakeStderr {
		t.Errorf("Expected c.App.ErrWriter == fakeStderr (*bytes.Buffer) but it was set to something else instead; actual value=%T/%p", c.App.ErrWriter, c.App.ErrWriter)
	}
}
<|endoftext|>


<commit_before>// Package queue implements a boldDB backed queue for MDM Commands.
package queue

import (
	"context"
	"fmt"

	"github.com/boltdb/bolt"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/groob/plist"
	"github.com/pkg/errors"

	"github.com/micromdm/micromdm/mdm"
	"github.com/micromdm/micromdm/platform/command"
	"github.com/micromdm/micromdm/platform/pubsub"
)

const (
	DeviceCommandBucket = "mdm.DeviceCommands"

	CommandQueuedTopic = "mdm.CommandQueued"
)

type Store struct {
	*bolt.DB
	logger log.Logger
}

type Option func(*Store)

func WithLogger(logger log.Logger) Option {
	return func(s *Store) {
		s.logger = logger
	}
}

func (db *Store) Next(ctx context.Context, resp mdm.Response) ([]byte, error) {
	cmd, err := db.nextCommand(ctx, resp)
	if err != nil {
		return nil, err
	}
	if cmd == nil {
		return nil, nil
	}
	return cmd.Payload, nil
}

func (db *Store) nextCommand(ctx context.Context, resp mdm.Response) (*Command, error) {
	udid := resp.UDID
	if resp.UserID != nil {
		// use the user id for user level commands
		udid = *resp.UserID
	}
	dc, err := db.DeviceCommand(udid)
	if err != nil {
		if isNotFound(err) {
			return nil, nil
		}
		return nil, errors.Wrapf(err, "get device command from queue, udid: %s", resp.UDID)
	}

	var cmd *Command
	switch resp.Status {
	case "NotNow":
		// We will try this command later when the device is not
		// responding with NotNow
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil {
			break
		}
		dc.NotNow = append(dc.NotNow, *x)

	case "Acknowledged":
		// move to completed, send next
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil {
			break
		}
		dc.Completed = append(dc.Completed, *x)
	case "Error":
		// move to failed, send next
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil { // must've already bin ackd
			break
		}
		dc.Failed = append(dc.Failed, *x)

	case "CommandFormatError":
		// move to failed
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil {
			break
		}
		dc.Failed = append(dc.Failed, *x)

	case "Idle":
		// will send next command below

	default:
		return nil, fmt.Errorf("unknown response status: %s", resp.Status)
	}

	// pop the first command from the queue and add it to the end.
	// If the regular queue is empty, send a command that got
	// refused with NotNow before.
	cmd, dc.Commands = popFirst(dc.Commands)
	if cmd != nil {
		dc.Commands = append(dc.Commands, *cmd)
	} else if resp.Status != "NotNow" {
		cmd, dc.NotNow = popFirst(dc.NotNow)
		if cmd != nil {
			dc.Commands = append(dc.Commands, *cmd)
		}
	}

	if err := db.Save(dc); err != nil {
		return nil, err
	}

	return cmd, nil
}

func popFirst(all []Command) (*Command, []Command) {
	if len(all) == 0 {
		return nil, all
	}
	first := all[0]
	all = append(all[:0], all[1:]...)
	return &first, all
}

func cut(all []Command, uuid string) (*Command, []Command) {
	for i, cmd := range all {
		if cmd.UUID == uuid {
			all = append(all[:i], all[i+1:]...)
			return &cmd, all
		}
	}
	return nil, all
}

func NewQueue(db *bolt.DB, pubsub pubsub.PublishSubscriber, opts ...Option) (*Store, error) {
	err := db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(DeviceCommandBucket))
		return err
	})
	if err != nil {
		return nil, errors.Wrapf(err, "creating %s bucket", DeviceCommandBucket)
	}

	datastore := &Store{DB: db, logger: log.NewNopLogger()}
	for _, fn := range opts {
		fn(datastore)
	}

	if err := datastore.pollCommands(pubsub); err != nil {
		return nil, err
	}

	return datastore, nil
}

func (db *Store) Save(cmd *DeviceCommand) error {
	tx, err := db.DB.Begin(true)
	if err != nil {
		return errors.Wrap(err, "begin transaction")
	}
	bkt := tx.Bucket([]byte(DeviceCommandBucket))
	if bkt == nil {
		return fmt.Errorf("bucket %q not found!", DeviceCommandBucket)
	}
	devproto, err := MarshalDeviceCommand(cmd)
	if err != nil {
		return errors.Wrap(err, "marshalling DeviceCommand")
	}
	key := []byte(cmd.DeviceUDID)
	if err := bkt.Put(key, devproto); err != nil {
		return errors.Wrap(err, "put DeviceCommand to boltdb")
	}
	return tx.Commit()
}

func (db *Store) DeviceCommand(udid string) (*DeviceCommand, error) {
	var dev DeviceCommand
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(DeviceCommandBucket))
		v := b.Get([]byte(udid))
		if v == nil {
			return &notFound{"DeviceCommand", fmt.Sprintf("udid %s", udid)}
		}
		return UnmarshalDeviceCommand(v, &dev)
	})
	if err != nil {
		return nil, err
	}
	return &dev, nil
}

type notFound struct {
	ResourceType string
	Message      string
}

func (e *notFound) Error() string {
	return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
}

func (db *Store) pollCommands(pubsub pubsub.PublishSubscriber) error {
	commandEvents, err := pubsub.Subscribe(context.TODO(), "command-queue", command.CommandTopic)
	if err != nil {
		return errors.Wrapf(err,
			"subscribing push to %s topic", command.CommandTopic)
	}
	go func() {
		for {
			select {
			case event := <-commandEvents:
				var ev command.Event
				if err := command.UnmarshalEvent(event.Message, &ev); err != nil {
					level.Info(db.logger).Log("msg", "unmarshal command event in queue", "err", err)
					continue
				}

				cmd := new(DeviceCommand)
				cmd.DeviceUDID = ev.DeviceUDID
				byUDID, err := db.DeviceCommand(ev.DeviceUDID)
				if err == nil && byUDID != nil {
					cmd = byUDID
				}
				newPayload, err := plist.Marshal(ev.Payload)
				if err != nil {
					level.Info(db.logger).Log("msg", "marshal event payload", "err", err)
					continue
				}
				newCmd := Command{
					UUID:    ev.Payload.CommandUUID,
					Payload: newPayload,
				}
				cmd.Commands = append(cmd.Commands, newCmd)
				if err := db.Save(cmd); err != nil {
					level.Info(db.logger).Log("msg", "save command in db", "err", err)
					continue
				}
				level.Info(db.logger).Log(
					"msg", "queued event for device",
					"device_udid", ev.DeviceUDID,
					"command_uuid", ev.Payload.CommandUUID,
					"request_type", ev.Payload.Command.RequestType,
				)

				cq := new(QueueCommandQueued)
				cq.DeviceUDID = ev.DeviceUDID
				cq.CommandUUID = ev.Payload.CommandUUID

				msgBytes, err := MarshalQueuedCommand(cq)
				if err != nil {
					level.Info(db.logger).Log("msg", "marshal queued command", "err", err)
					continue
				}

				if err := pubsub.Publish(context.TODO(), CommandQueuedTopic, msgBytes); err != nil {
					level.Info(db.logger).Log("msg", "publish command to queued topic", "err", err)
				}
			}
		}
	}()

	return nil
}

func isNotFound(err error) bool {
	if _, ok := err.(*notFound); ok {
		return true
	}
	return false
}
<commit_msg>set acknowledge time when moving command to completed queue (#581)<commit_after>// Package queue implements a boldDB backed queue for MDM Commands.
package queue

import (
	"context"
	"fmt"
	"time"

	"github.com/boltdb/bolt"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/groob/plist"
	"github.com/pkg/errors"

	"github.com/micromdm/micromdm/mdm"
	"github.com/micromdm/micromdm/platform/command"
	"github.com/micromdm/micromdm/platform/pubsub"
)

const (
	DeviceCommandBucket = "mdm.DeviceCommands"

	CommandQueuedTopic = "mdm.CommandQueued"
)

type Store struct {
	*bolt.DB
	logger log.Logger
}

type Option func(*Store)

func WithLogger(logger log.Logger) Option {
	return func(s *Store) {
		s.logger = logger
	}
}

func (db *Store) Next(ctx context.Context, resp mdm.Response) ([]byte, error) {
	cmd, err := db.nextCommand(ctx, resp)
	if err != nil {
		return nil, err
	}
	if cmd == nil {
		return nil, nil
	}
	return cmd.Payload, nil
}

func (db *Store) nextCommand(ctx context.Context, resp mdm.Response) (*Command, error) {
	udid := resp.UDID
	if resp.UserID != nil {
		// use the user id for user level commands
		udid = *resp.UserID
	}
	dc, err := db.DeviceCommand(udid)
	if err != nil {
		if isNotFound(err) {
			return nil, nil
		}
		return nil, errors.Wrapf(err, "get device command from queue, udid: %s", resp.UDID)
	}

	var cmd *Command
	switch resp.Status {
	case "NotNow":
		// We will try this command later when the device is not
		// responding with NotNow
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil {
			break
		}
		dc.NotNow = append(dc.NotNow, *x)

	case "Acknowledged":
		// move to completed, send next
		x, a := cut(dc.Commands, resp.CommandUUID)
		x.Acknowledged = time.Now().UTC()
		dc.Commands = a
		if x == nil {
			break
		}
		dc.Completed = append(dc.Completed, *x)
	case "Error":
		// move to failed, send next
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil { // must've already bin ackd
			break
		}
		dc.Failed = append(dc.Failed, *x)

	case "CommandFormatError":
		// move to failed
		x, a := cut(dc.Commands, resp.CommandUUID)
		dc.Commands = a
		if x == nil {
			break
		}
		dc.Failed = append(dc.Failed, *x)

	case "Idle":

		// will send next command below

	default:
		return nil, fmt.Errorf("unknown response status: %s", resp.Status)
	}

	// pop the first command from the queue and add it to the end.
	// If the regular queue is empty, send a command that got
	// refused with NotNow before.
	cmd, dc.Commands = popFirst(dc.Commands)
	if cmd != nil {
		dc.Commands = append(dc.Commands, *cmd)
	} else if resp.Status != "NotNow" {
		cmd, dc.NotNow = popFirst(dc.NotNow)
		if cmd != nil {
			dc.Commands = append(dc.Commands, *cmd)
		}
	}

	if err := db.Save(dc); err != nil {
		return nil, err
	}

	return cmd, nil
}

func popFirst(all []Command) (*Command, []Command) {
	if len(all) == 0 {
		return nil, all
	}
	first := all[0]
	all = append(all[:0], all[1:]...)
	return &first, all
}

func cut(all []Command, uuid string) (*Command, []Command) {
	for i, cmd := range all {
		if cmd.UUID == uuid {
			all = append(all[:i], all[i+1:]...)
			return &cmd, all
		}
	}
	return nil, all
}

func NewQueue(db *bolt.DB, pubsub pubsub.PublishSubscriber, opts ...Option) (*Store, error) {
	err := db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(DeviceCommandBucket))
		return err
	})
	if err != nil {
		return nil, errors.Wrapf(err, "creating %s bucket", DeviceCommandBucket)
	}

	datastore := &Store{DB: db, logger: log.NewNopLogger()}
	for _, fn := range opts {
		fn(datastore)
	}

	if err := datastore.pollCommands(pubsub); err != nil {
		return nil, err
	}

	return datastore, nil
}

func (db *Store) Save(cmd *DeviceCommand) error {
	tx, err := db.DB.Begin(true)
	if err != nil {
		return errors.Wrap(err, "begin transaction")
	}
	bkt := tx.Bucket([]byte(DeviceCommandBucket))
	if bkt == nil {
		return fmt.Errorf("bucket %q not found!", DeviceCommandBucket)
	}
	devproto, err := MarshalDeviceCommand(cmd)
	if err != nil {
		return errors.Wrap(err, "marshalling DeviceCommand")
	}
	key := []byte(cmd.DeviceUDID)
	if err := bkt.Put(key, devproto); err != nil {
		return errors.Wrap(err, "put DeviceCommand to boltdb")
	}
	return tx.Commit()
}

func (db *Store) DeviceCommand(udid string) (*DeviceCommand, error) {
	var dev DeviceCommand
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(DeviceCommandBucket))
		v := b.Get([]byte(udid))
		if v == nil {
			return &notFound{"DeviceCommand", fmt.Sprintf("udid %s", udid)}
		}
		return UnmarshalDeviceCommand(v, &dev)
	})
	if err != nil {
		return nil, err
	}
	return &dev, nil
}

type notFound struct {
	ResourceType string
	Message      string
}

func (e *notFound) Error() string {
	return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
}

func (db *Store) pollCommands(pubsub pubsub.PublishSubscriber) error {
	commandEvents, err := pubsub.Subscribe(context.TODO(), "command-queue", command.CommandTopic)
	if err != nil {
		return errors.Wrapf(err,
			"subscribing push to %s topic", command.CommandTopic)
	}
	go func() {
		for {
			select {
			case event := <-commandEvents:
				var ev command.Event
				if err := command.UnmarshalEvent(event.Message, &ev); err != nil {
					level.Info(db.logger).Log("msg", "unmarshal command event in queue", "err", err)
					continue
				}

				cmd := new(DeviceCommand)
				cmd.DeviceUDID = ev.DeviceUDID
				byUDID, err := db.DeviceCommand(ev.DeviceUDID)
				if err == nil && byUDID != nil {
					cmd = byUDID
				}
				newPayload, err := plist.Marshal(ev.Payload)
				if err != nil {
					level.Info(db.logger).Log("msg", "marshal event payload", "err", err)
					continue
				}
				newCmd := Command{
					UUID:    ev.Payload.CommandUUID,
					Payload: newPayload,
				}
				cmd.Commands = append(cmd.Commands, newCmd)
				if err := db.Save(cmd); err != nil {
					level.Info(db.logger).Log("msg", "save command in db", "err", err)
					continue
				}
				level.Info(db.logger).Log(
					"msg", "queued event for device",
					"device_udid", ev.DeviceUDID,
					"command_uuid", ev.Payload.CommandUUID,
					"request_type", ev.Payload.Command.RequestType,
				)

				cq := new(QueueCommandQueued)
				cq.DeviceUDID = ev.DeviceUDID
				cq.CommandUUID = ev.Payload.CommandUUID

				msgBytes, err := MarshalQueuedCommand(cq)
				if err != nil {
					level.Info(db.logger).Log("msg", "marshal queued command", "err", err)
					continue
				}

				if err := pubsub.Publish(context.TODO(), CommandQueuedTopic, msgBytes); err != nil {
					level.Info(db.logger).Log("msg", "publish command to queued topic", "err", err)
				}
			}
		}
	}()

	return nil
}

func isNotFound(err error) bool {
	if _, ok := err.(*notFound); ok {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net"
	"strconv"

	"github.com/cenkalti/rpc2"
	uuid "github.com/satori/go.uuid"
	"github.com/szydell/gogtm"
	"github.com/szydell/mstools"
	"github.com/szydell/rjgtm/rjerr"
	"github.com/szydell/rjgtm/rjshared"
)

var id = uuid.NewV4().String()

func main() {

	err := gogtm.Start()
	defer gogtm.Stop()
	mstools.ErrCheck(err)

	conn, _ := net.Dial("tcp", "127.0.0.1:5000")
	client := rpc2.NewClient(conn)
	client.Handle("getGlvn", getGlvn)
	client.Handle("gvStats", gvStats)
	client.Handle("cleanGvStats", cleanGvStats)
	client.Handle("setGlvn", setGlvn)
	client.Handle("orderGlvn", orderGlvn)
	client.Handle("prevGlvn", prevGlvn)
	client.Handle("queryGlvn", queryGlvn)
	client.Handle("dataGlvn", dataGlvn)
	client.Run()

}

func getGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("GET glvn:" + glvn)
	response, err := gogtm.Get("^"+glvn, id)
	if err != nil {
		log.Println("503 /v1/global/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}
	if response == id {
		log.Println("404 /v1/global/" + glvn)
		return rjerr.Err404
	}
	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"" + glvn + "\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"" + glvn + "\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/data/" + glvn + " -> " + response)

	return nil
}

func gvStats(client *rpc2.Client, _, reply *string) error {

	log.Println("GET gvstat")
	response, err := gogtm.GvStat()
	if err != nil {
		log.Println("503 /v1/gvstat")
		return rjerr.ErrGtmCantGetGlvn
	}

	buildJSON := []rune("{\"RESPONSE\":[{\"")

	for _, char := range response {
		switch char {
		case 44:
			buildJSON = append(buildJSON, []rune{',', '"'}...)
		case 58:
			buildJSON = append(buildJSON, []rune{'"', ':'}...)
		case 59:
			buildJSON = append(buildJSON, []rune{'"', ':', '[', '{', '"'}...)
		case 124:
			buildJSON = append(buildJSON, []rune{'}', ']', ',', '"'}...)
		default:
			buildJSON = append(buildJSON, rune(char))
		}
	}
	buildJSON = append(buildJSON, []rune("}]}],\"STATUS\":\"OK\"}")...)
	*reply = string(buildJSON)
	return nil
}

func cleanGvStats(client *rpc2.Client, _, reply *string) error {
	log.Println("DELETE gvstat")
	err := gogtm.Xecute("S REGION=$V(\"GVFIRST\") VIEW \"GVSRESET\":REGION F I=1:1 S REGION=$V(\"GVNEXT\",REGION) Q:REGION=\"\"  VIEW \"GVSRESET\":REGION")
	if err == nil {
		*reply = "{\"status\":\"OK\"}"
	} else {
		log.Println("503 DELETE gvstats failed (cleanGvStats function)")
		*reply = "{\"status\":\"ERROR\"}"
	}
	return err
}

func setGlvn(client *rpc2.Client, glvn rjshared.Glvn, reply *string) error {
	log.Println("POST glvn:" + glvn.Key + "(setGlvn function)")
	err := gogtm.Set("^"+glvn.Key, glvn.Value)

	if err != nil {
		log.Println("503 SET /v1/global/" + glvn.Key)
		return rjerr.ErrGtmCantSetGlvn
	}
	*reply = "{\"status\":\"OK\"}"

	return nil
}

func orderGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("ORDER glvn:" + glvn)
	response, err := gogtm.Order("^"+glvn, "")
	if err != nil {
		log.Println("503 /v1/order/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"next glvn\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"next glvn\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/order/" + glvn + " -> next glvn:" + response)

	return nil
}

func prevGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("PREVIOUS glvn:" + glvn)
	response, err := gogtm.Order("^"+glvn, "-1")
	if err != nil {
		log.Println("503 /v1/prev/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"previous glvn\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"previous glvn\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/prev/" + glvn + " -> previous glvn:" + response)

	return nil
}

func queryGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("QUERY glvn:" + glvn)
	response, err := gogtm.Query("^" + glvn)
	if err != nil {
		log.Println("503 /v1/query/" + glvn)
		return err
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"query result\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"query result\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/order/" + glvn + " -> query result:" + response)

	return nil
}

func dataGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("DATA glvn:" + glvn)
	val, desc, err := gogtm.Data("^" + glvn)
	if err != nil {
		log.Println("503 /v1/data/" + glvn)
		return err
	}

	*reply = "{\"RESPONSE\":{\"value\":" + strconv.FormatBool(val) + ",\"descendent\":" + strconv.FormatBool(desc) + "}, \"STATUS\":\"OK\"}"

	log.Println("200 /v1/order/" + glvn + " -> data result:" + *reply)

	return nil
}
<commit_msg>repired uuid api change<commit_after>package main

import (
	"log"
	"net"
	"strconv"

	"github.com/cenkalti/rpc2"
	uuid "github.com/satori/go.uuid"
	"github.com/szydell/gogtm"
	"github.com/szydell/mstools"
	"github.com/szydell/rjgtm/rjerr"
	"github.com/szydell/rjgtm/rjshared"
)

var u, err = uuid.NewV4()
var id = u.String()

func main() {

	err := gogtm.Start()
	defer gogtm.Stop()
	mstools.ErrCheck(err)

	conn, _ := net.Dial("tcp", "127.0.0.1:5000")
	client := rpc2.NewClient(conn)
	client.Handle("getGlvn", getGlvn)
	client.Handle("gvStats", gvStats)
	client.Handle("cleanGvStats", cleanGvStats)
	client.Handle("setGlvn", setGlvn)
	client.Handle("orderGlvn", orderGlvn)
	client.Handle("prevGlvn", prevGlvn)
	client.Handle("queryGlvn", queryGlvn)
	client.Handle("dataGlvn", dataGlvn)
	client.Run()

}

func getGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("GET glvn:" + glvn)
	response, err := gogtm.Get("^"+glvn, id)
	if err != nil {
		log.Println("503 /v1/global/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}
	if response == id {
		log.Println("404 /v1/global/" + glvn)
		return rjerr.Err404
	}
	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"" + glvn + "\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"" + glvn + "\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/data/" + glvn + " -> " + response)

	return nil
}

func gvStats(client *rpc2.Client, _, reply *string) error {

	log.Println("GET gvstat")
	response, err := gogtm.GvStat()
	if err != nil {
		log.Println("503 /v1/gvstat")
		return rjerr.ErrGtmCantGetGlvn
	}

	buildJSON := []rune("{\"RESPONSE\":[{\"")

	for _, char := range response {
		switch char {
		case 44:
			buildJSON = append(buildJSON, []rune{',', '"'}...)
		case 58:
			buildJSON = append(buildJSON, []rune{'"', ':'}...)
		case 59:
			buildJSON = append(buildJSON, []rune{'"', ':', '[', '{', '"'}...)
		case 124:
			buildJSON = append(buildJSON, []rune{'}', ']', ',', '"'}...)
		default:
			buildJSON = append(buildJSON, rune(char))
		}
	}
	buildJSON = append(buildJSON, []rune("}]}],\"STATUS\":\"OK\"}")...)
	*reply = string(buildJSON)
	return nil
}

func cleanGvStats(client *rpc2.Client, _, reply *string) error {
	log.Println("DELETE gvstat")
	err := gogtm.Xecute("S REGION=$V(\"GVFIRST\") VIEW \"GVSRESET\":REGION F I=1:1 S REGION=$V(\"GVNEXT\",REGION) Q:REGION=\"\"  VIEW \"GVSRESET\":REGION")
	if err == nil {
		*reply = "{\"status\":\"OK\"}"
	} else {
		log.Println("503 DELETE gvstats failed (cleanGvStats function)")
		*reply = "{\"status\":\"ERROR\"}"
	}
	return err
}

func setGlvn(client *rpc2.Client, glvn rjshared.Glvn, reply *string) error {
	log.Println("POST glvn:" + glvn.Key + "(setGlvn function)")
	err := gogtm.Set("^"+glvn.Key, glvn.Value)

	if err != nil {
		log.Println("503 SET /v1/global/" + glvn.Key)
		return rjerr.ErrGtmCantSetGlvn
	}
	*reply = "{\"status\":\"OK\"}"

	return nil
}

func orderGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("ORDER glvn:" + glvn)
	response, err := gogtm.Order("^"+glvn, "")
	if err != nil {
		log.Println("503 /v1/order/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"next glvn\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"next glvn\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/order/" + glvn + " -> next glvn:" + response)

	return nil
}

func prevGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("PREVIOUS glvn:" + glvn)
	response, err := gogtm.Order("^"+glvn, "-1")
	if err != nil {
		log.Println("503 /v1/prev/" + glvn)
		return rjerr.ErrGtmCantGetGlvn
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"previous glvn\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"previous glvn\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/prev/" + glvn + " -> previous glvn:" + response)

	return nil
}

func queryGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("QUERY glvn:" + glvn)
	response, err := gogtm.Query("^" + glvn)
	if err != nil {
		log.Println("503 /v1/query/" + glvn)
		return err
	}

	//return string formatted as JSON, try to figure out if response is a string or integer
	if _, err := strconv.Atoi(response); err == nil {
		*reply = "{\"RESPONSE\":{\"query result\": " + response + "}, \"STATUS\":\"OK\"}"
	} else {
		*reply = "{\"RESPONSE\":{\"query result\": \"" + response + "\"}, \"STATUS\":\"OK\"}"
	}
	log.Println("200 /v1/order/" + glvn + " -> query result:" + response)

	return nil
}

func dataGlvn(client *rpc2.Client, glvn string, reply *string) error {

	log.Println("DATA glvn:" + glvn)
	val, desc, err := gogtm.Data("^" + glvn)
	if err != nil {
		log.Println("503 /v1/data/" + glvn)
		return err
	}

	*reply = "{\"RESPONSE\":{\"value\":" + strconv.FormatBool(val) + ",\"descendent\":" + strconv.FormatBool(desc) + "}, \"STATUS\":\"OK\"}"

	log.Println("200 /v1/order/" + glvn + " -> data result:" + *reply)

	return nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"flag"
	"fmt"
	"reflect"
	"strings"
	"time"
)

// Package variables
var (
	commands map[string]*Command
	Default  = Options{false}
)

func init() {
	commands = make(map[string]*Command)
}

type Options struct {
	Namespace bool
}

type Command struct {
	Name      string
	Reference interface{}
}

type Commander struct {
	Commands []Command
	Config   Options
}

// Uses reflection to check if interface 'val' is of type
// reflect.Struct returns an error if it isn't and nil otherwise.
func checkIfStruct(val interface{}) error {
	cmdType := reflect.TypeOf(val).Elem()
	if cmdType.Kind() != reflect.Struct {
		return fmt.Errorf("Type %v is not a struct.", cmdType.Name())
	}
	return nil
}

// Returns a new command pointer based on struct 'cmd'
// Checks if interface is of Kind reflect.Struct
// returns error if it isn't and nil otherwise.
func NewCommand(cmd interface{}) (*Command, error) {
	err := checkIfStruct(cmd)
	if err != nil {
		return nil, err
	}
	cmdType := reflect.TypeOf(cmd).Elem()
	return &Command{cmdType.Name(), cmd}, nil
}

// Goes through all the exported fields of the struct 'cmd',
// then tries to bind each exported field to a flag of the
// supported types by the flag package, returns an error if
// it finds an exported Unsupported type.
// To ignore an exported field use `cmd:"-"` as the tag.
func (cmd *Command) ParseFlags(opt Options) error {
	cmdType := reflect.TypeOf(cmd.Reference).Elem()
	for i := 0; i < cmdType.NumField(); i++ {
		field := cmdType.Field(i)
		usage := field.Tag.Get("cmd")
		// Don't try unexported and untagged fields
		if field.PkgPath == "" && usage != "-" {
			cmdValue := reflect.ValueOf(cmd.Reference).Elem()
			// Addresable interface
			value := cmdValue.FieldByName(field.Name).Addr().Interface()

			// Namespacing for field collision
			flagName := ""
			if opt.Namespace {
				flagName = strings.ToLower(cmdType.Name() + "." + field.Name)
			} else {
				flagName = strings.ToLower(field.Name)
			}
			// Only types supported by the flag package
			switch value := value.(type) {
			case *bool:
				flag.BoolVar(value, flagName, *value, usage)
			case *int:
				flag.IntVar(value, flagName, *value, usage)
			case *int64:
				flag.Int64Var(value, flagName, *value, usage)
			case *uint:
				flag.UintVar(value, flagName, *value, usage)
			case *uint64:
				flag.Uint64Var(value, flagName, *value, usage)
			case *float64:
				flag.Float64Var(value, flagName, *value, usage)
			case *string:
				flag.StringVar(value, flagName, *value, usage)
			case *time.Duration:
				flag.DurationVar(value, flagName, *value, usage)
			default:
				return fmt.Errorf("Unsupported type: %s of type %v cannot be parsed as flag.", field.Name, field.Type.Name())
			}
		}
	}
	return nil
}

// Executes method on struct 'cmd' with the given 'name',
// this match is case insensitive. Return an error if the
// methos does not exist or is unexported.
func (cmd *Command) Exec(name string) error {
	cmdType := reflect.TypeOf(cmd.Reference).Elem()
	for i := 0; i < cmdType.NumMethod(); i++ {
		method := cmdType.Method(i)
		if method.PkgPath == "" {
			if strings.ToLower(method.Name) == strings.ToLower(name) {
				method.Func.Call([]reflect.Value{reflect.ValueOf(cmd.Reference).Elem()})
				return nil
			}
		}
	}
	return fmt.Errorf("Method <%s> not found.", name)
}

// Reflects and parses any number of 'struct' values to be called
// via CLI, throws an error if any of them cannot be parsed or
// if the called method cannot be executed.
func Commanderize(opt Options, values ...interface{}) error {
	commands := make([]*Command, 0)
	for _, val := range values {
		// Check if interface is of Kind reflect.Struct
		cmd, err := NewCommand(val)
		if err != nil {
			return err
		}
		// Create flags based on StructFields
		err = cmd.ParseFlags(opt)
		if err != nil {
			return err
		}
		// Append command to the command list.
		commands = append(commands, cmd)
	}

	// Parse cli arguments into flags
	flag.Parse()

	if len(flag.Args()) == 0 {
		flag.PrintDefaults()
		return fmt.Errorf("Usage: <struct>:<method> (No command given.)")
	}

	// Parse the Arg(0) which is the actual command
	arg := strings.Split(flag.Arg(0), ":")
	if len(arg) != 2 {
		return fmt.Errorf("Usage: <struct>:<method> (Got: %s)", flag.Arg(0))
	}

	// Search for the method and execute it
	cmdName, cmdMethod := arg[0], arg[1]
	for _, cmd := range commands {
		if strings.ToLower(cmd.Name) == strings.ToLower(cmdName) {
			return cmd.Exec(cmdMethod)
		} else {
			return fmt.Errorf("Command <%s> not found.", cmdName)
		}
	}

	return nil
}
<commit_msg>Error formatting<commit_after>package cmd

import (
	"flag"
	"fmt"
	"reflect"
	"strings"
	"time"
)

// Package variables
var (
	commands map[string]*Command
	Default  = Options{false}
)

func init() {
	commands = make(map[string]*Command)
}

type Options struct {
	Namespace bool
}

type Command struct {
	Name      string
	Reference interface{}
}

type Commander struct {
	Commands []Command
	Config   Options
}

// Uses reflection to check if interface 'val' is of type
// reflect.Struct returns an error if it isn't and nil otherwise.
func checkIfStruct(val interface{}) error {
	cmdType := reflect.TypeOf(val).Elem()
	if cmdType.Kind() != reflect.Struct {
		return fmt.Errorf("type %v is not a struct", cmdType.Name())
	}
	return nil
}

// Returns a new command pointer based on struct 'cmd'
// Checks if interface is of Kind reflect.Struct
// returns error if it isn't and nil otherwise.
func NewCommand(cmd interface{}) (*Command, error) {
	err := checkIfStruct(cmd)
	if err != nil {
		return nil, err
	}
	cmdType := reflect.TypeOf(cmd).Elem()
	return &Command{cmdType.Name(), cmd}, nil
}

// Goes through all the exported fields of the struct 'cmd',
// then tries to bind each exported field to a flag of the
// supported types by the flag package, returns an error if
// it finds an exported Unsupported type.
// To ignore an exported field use `cmd:"-"` as the tag.
func (cmd *Command) ParseFlags(opt Options) error {
	cmdType := reflect.TypeOf(cmd.Reference).Elem()
	for i := 0; i < cmdType.NumField(); i++ {
		field := cmdType.Field(i)
		usage := field.Tag.Get("cmd")
		// Don't try unexported and untagged fields
		if field.PkgPath == "" && usage != "-" {
			cmdValue := reflect.ValueOf(cmd.Reference).Elem()
			// Addresable interface
			value := cmdValue.FieldByName(field.Name).Addr().Interface()

			// Namespacing for field collision
			flagName := ""
			if opt.Namespace {
				flagName = strings.ToLower(cmdType.Name() + "." + field.Name)
			} else {
				flagName = strings.ToLower(field.Name)
			}
			// Only types supported by the flag package
			switch value := value.(type) {
			case *bool:
				flag.BoolVar(value, flagName, *value, usage)
			case *int:
				flag.IntVar(value, flagName, *value, usage)
			case *int64:
				flag.Int64Var(value, flagName, *value, usage)
			case *uint:
				flag.UintVar(value, flagName, *value, usage)
			case *uint64:
				flag.Uint64Var(value, flagName, *value, usage)
			case *float64:
				flag.Float64Var(value, flagName, *value, usage)
			case *string:
				flag.StringVar(value, flagName, *value, usage)
			case *time.Duration:
				flag.DurationVar(value, flagName, *value, usage)
			default:
				return fmt.Errorf("unsupported type: %s of type %v cannot be parsed as flag", field.Name, field.Type.Name())
			}
		}
	}
	return nil
}

// Executes method on struct 'cmd' with the given 'name',
// this match is case insensitive. Return an error if the
// methos does not exist or is unexported.
func (cmd *Command) Exec(name string) error {
	cmdType := reflect.TypeOf(cmd.Reference).Elem()
	for i := 0; i < cmdType.NumMethod(); i++ {
		method := cmdType.Method(i)
		if method.PkgPath == "" {
			if strings.ToLower(method.Name) == strings.ToLower(name) {
				method.Func.Call([]reflect.Value{reflect.ValueOf(cmd.Reference).Elem()})
				return nil
			}
		}
	}
	return fmt.Errorf("method <%s> not found", name)
}

// Reflects and parses any number of 'struct' values to be called
// via CLI, throws an error if any of them cannot be parsed or
// if the called method cannot be executed.
func Commanderize(opt Options, values ...interface{}) error {
	commands := make([]*Command, 0)
	for _, val := range values {
		// Check if interface is of Kind reflect.Struct
		cmd, err := NewCommand(val)
		if err != nil {
			return err
		}
		// Create flags based on StructFields
		err = cmd.ParseFlags(opt)
		if err != nil {
			return err
		}
		// Append command to the command list.
		commands = append(commands, cmd)
	}

	// Parse cli arguments into flags
	flag.Parse()

	if len(flag.Args()) == 0 {
		flag.PrintDefaults()
		return fmt.Errorf("usage: <struct>:<method> (No command given.)")
	}

	// Parse the Arg(0) which is the actual command
	arg := strings.Split(flag.Arg(0), ":")
	if len(arg) != 2 {
		return fmt.Errorf("usage: <struct>:<method> (Got: %s)", flag.Arg(0))
	}

	// Search for the method and execute it
	cmdName, cmdMethod := arg[0], arg[1]
	for _, cmd := range commands {
		if strings.ToLower(cmd.Name) == strings.ToLower(cmdName) {
			return cmd.Exec(cmdMethod)
		}
		return fmt.Errorf("command <%s> not found", cmdName)
	}

	return nil
}
<|endoftext|>