<commit_before>package gocb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
)

// ClusterManager provides methods for performing cluster management operations.
type ClusterManager struct {
	hosts    []string
	username string
	password string
	httpCli  *http.Client
	cluster  *Cluster
}

// BucketType specifies the kind of bucket
type BucketType int

const (
	// Couchbase indicates a Couchbase bucket type.
	Couchbase = BucketType(0)

	// Memcached indicates a Memcached bucket type.
	Memcached = BucketType(1)

	// Ephemeral indicates an Ephemeral bucket type.
	Ephemeral = BucketType(2)
)

type bucketDataIn struct {
	Name         string `json:"name"`
	BucketType   string `json:"bucketType"`
	AuthType     string `json:"authType"`
	SaslPassword string `json:"saslPassword"`
	Quota        struct {
		Ram    int `json:"ram"`
		RawRam int `json:"rawRAM"`
	} `json:"quota"`
	ReplicaNumber int  `json:"replicaNumber"`
	ReplicaIndex  bool `json:"replicaIndex"`
	Controllers   struct {
		Flush string `json:"flush"`
	} `json:"controllers"`
}

// BucketSettings holds information about the settings for a bucket.
type BucketSettings struct {
	FlushEnabled  bool
	IndexReplicas bool
	Name          string
	Password      string
	Quota         int
	Replicas      int
	Type          BucketType
}

func (cm *ClusterManager) getMgmtEp() string {
	return cm.hosts[rand.Intn(len(cm.hosts))]
}

func (cm *ClusterManager) mgmtRequest(method, uri string, contentType string, body io.Reader) (*http.Response, error) {
	if contentType == "" && body != nil {
		panic("Content-type must be specified for non-null body.")
	}

	reqUri := cm.getMgmtEp() + uri
	req, err := http.NewRequest(method, reqUri, body)
	if err != nil {
		return nil, err
	}

	if contentType != "" {
		req.Header.Add("Content-Type", contentType)
	}
	if cm.username != "" || cm.password != "" {
		req.SetBasicAuth(cm.username, cm.password)
	}

	return cm.httpCli.Do(req)
}

func bucketDataInToSettings(bucketData *bucketDataIn) *BucketSettings {
	settings := &BucketSettings{
		FlushEnabled:  bucketData.Controllers.Flush != "",
		IndexReplicas: bucketData.ReplicaIndex,
		Name:          bucketData.Name,
		Password:      bucketData.SaslPassword,
		Quota:         bucketData.Quota.Ram,
		Replicas:      bucketData.ReplicaNumber,
	}
	if bucketData.BucketType == "membase" {
		settings.Type = Couchbase
	} else if bucketData.BucketType == "memcached" {
		settings.Type = Memcached
	} else if bucketData.BucketType == "ephemeral" {
		settings.Type = Ephemeral
	} else {
		panic("Unrecognized bucket type string.")
	}
	if bucketData.AuthType != "sasl" {
		settings.Password = ""
	}
	return settings
}

// GetBuckets returns a list of all active buckets on the cluster.
func (cm *ClusterManager) GetBuckets() ([]*BucketSettings, error) {
	resp, err := cm.mgmtRequest("GET", "/pools/default/buckets", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var bucketsData []*bucketDataIn
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&bucketsData)
	if err != nil {
		return nil, err
	}

	var buckets []*BucketSettings
	for _, bucketData := range bucketsData {
		buckets = append(buckets, bucketDataInToSettings(bucketData))
	}

	return buckets, nil
}

// InsertBucket creates a new bucket on the cluster.
func (cm *ClusterManager) InsertBucket(settings *BucketSettings) error {
	posts := url.Values{}
	posts.Add("name", settings.Name)
	if settings.Type == Couchbase {
		posts.Add("bucketType", "couchbase")
	} else if settings.Type == Memcached {
		posts.Add("bucketType", "memcached")
	} else if settings.Type == Ephemeral {
		posts.Add("bucketType", "ephemeral")
	} else {
		panic("Unrecognized bucket type.")
	}
	if settings.FlushEnabled {
		posts.Add("flushEnabled", "1")
	} else {
		posts.Add("flushEnabled", "0")
	}
	posts.Add("replicaNumber", fmt.Sprintf("%d", settings.Replicas))
	posts.Add("authType", "sasl")
	posts.Add("saslPassword", settings.Password)
	posts.Add("ramQuotaMB", fmt.Sprintf("%d", settings.Quota))

	data := []byte(posts.Encode())
	resp, err := cm.mgmtRequest("POST", "/pools/default/buckets", "application/x-www-form-urlencoded", bytes.NewReader(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != 202 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UpdateBucket will update the settings for a specific bucket on the cluster.
func (cm *ClusterManager) UpdateBucket(settings *BucketSettings) error {
	// Cluster-side, updates are the same as creates.
	return cm.InsertBucket(settings)
}

// RemoveBucket will delete a bucket from the cluster by name.
func (cm *ClusterManager) RemoveBucket(name string) error {
	reqUri := fmt.Sprintf("/pools/default/buckets/%s", name)

	resp, err := cm.mgmtRequest("DELETE", reqUri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UserRole represents a role for a particular user on the server.
type UserRole struct {
	Role       string
	BucketName string
}

// User represents a user which was retrieved from the server.
type User struct {
	Id    string
	Name  string
	Type  string
	Roles []UserRole
}

// AuthDomain specifies the user domain of a specific user
type AuthDomain string

const (
	// LocalDomain specifies users that are locally stored in Couchbase.
	LocalDomain AuthDomain = "local"

	// ExternalDomain specifies users that are externally stored
	// (in LDAP for instance).
	ExternalDomain = "external"
)

// UserSettings represents a user during user creation.
type UserSettings struct {
	Name     string
	Password string
	Roles    []UserRole
}

type userRoleJson struct {
	Role       string `json:"role"`
	BucketName string `json:"bucket_name"`
}

type userJson struct {
	Id    string         `json:"id"`
	Name  string         `json:"name"`
	Type  string         `json:"type"`
	Roles []userRoleJson `json:"roles"`
}

type userSettingsJson struct {
	Name     string         `json:"name"`
	Password string         `json:"password"`
	Roles    []userRoleJson `json:"roles"`
}

func transformUserJson(userData *userJson) User {
	var user User
	user.Id = userData.Id
	user.Name = userData.Name
	user.Type = userData.Type
	for _, roleData := range userData.Roles {
		user.Roles = append(user.Roles, UserRole{
			Role:       roleData.Role,
			BucketName: roleData.BucketName,
		})
	}
	return user
}

// GetUsers returns a list of all users on the cluster.
func (cm *ClusterManager) GetUsers(domain AuthDomain) ([]*User, error) {
	uri := fmt.Sprintf("/settings/rbac/users/%s", domain)
	resp, err := cm.mgmtRequest("GET", uri, "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var usersData []*userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&usersData)
	if err != nil {
		return nil, err
	}

	var users []*User
	for _, userData := range usersData {
		user := transformUserJson(userData)
		users = append(users, &user)
	}

	return users, nil
}

// GetUser returns the data for a particular user
func (cm *ClusterManager) GetUser(domain AuthDomain, name string) (*User, error) {
	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	resp, err := cm.mgmtRequest("GET", uri, "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var userData userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&userData)
	if err != nil {
		return nil, err
	}

	user := transformUserJson(&userData)
	return &user, nil
}

// UpsertUser updates a built-in RBAC user on the cluster.
func (cm *ClusterManager) UpsertUser(domain AuthDomain, name string, settings *UserSettings) error {
	var reqRoleStrs []string
	for _, roleData := range settings.Roles {
		reqRoleStrs = append(reqRoleStrs, fmt.Sprintf("%s[%s]", roleData.Role, roleData.BucketName))
	}

	reqForm := make(url.Values)
	reqForm.Add("name", settings.Name)
	reqForm.Add("password", settings.Password)
	reqForm.Add("roles", strings.Join(reqRoleStrs, ","))

	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	reqBody := bytes.NewReader([]byte(reqForm.Encode()))
	resp, err := cm.mgmtRequest("PUT", uri, "application/x-www-form-urlencoded", reqBody)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// RemoveUser removes a built-in RBAC user on the cluster.
func (cm *ClusterManager) RemoveUser(domain AuthDomain, name string) error {
	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	resp, err := cm.mgmtRequest("DELETE", uri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// SearchIndexManager returns a SearchIndexManager for performing FTS index management on this cluster
// Experimental: This API is subject to change at any time.
func (cm *ClusterManager) SearchIndexManager() *SearchIndexManager {
	return &SearchIndexManager{
		authenticator: cm.cluster.auth,
		httpCli:       cm.httpCli,
		cluster:       cm.cluster,
	}
}
<commit_msg>GOCBC-378: Expose metadata about nodes in the cluster<commit_after>package gocb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
)

// ClusterManager provides methods for performing cluster management operations.
type ClusterManager struct {
	hosts    []string
	username string
	password string
	httpCli  *http.Client
	cluster  *Cluster
}

// BucketType specifies the kind of bucket
type BucketType int

const (
	// Couchbase indicates a Couchbase bucket type.
	Couchbase = BucketType(0)

	// Memcached indicates a Memcached bucket type.
	Memcached = BucketType(1)

	// Ephemeral indicates an Ephemeral bucket type.
	Ephemeral = BucketType(2)
)

type bucketDataIn struct {
	Name         string `json:"name"`
	BucketType   string `json:"bucketType"`
	AuthType     string `json:"authType"`
	SaslPassword string `json:"saslPassword"`
	Quota        struct {
		Ram    int `json:"ram"`
		RawRam int `json:"rawRAM"`
	} `json:"quota"`
	ReplicaNumber int  `json:"replicaNumber"`
	ReplicaIndex  bool `json:"replicaIndex"`
	Controllers   struct {
		Flush string `json:"flush"`
	} `json:"controllers"`
}

// NodeMetadata contains information about a node in the cluster.
type NodeMetadata struct {
	ClusterCompatibility int                `json:"clusterCompatibility"`
	ClusterMembership    string             `json:"clusterMembership"`
	CouchAPIBase         string             `json:"couchApiBase"`
	Hostname             string             `json:"hostname"`
	InterestingStats     map[string]float64 `json:"interestingStats,omitempty"`
	MCDMemoryAllocated   float64            `json:"mcdMemoryAllocated"`
	MCDMemoryReserved    float64            `json:"mcdMemoryReserved"`
	MemoryFree           float64            `json:"memoryFree"`
	MemoryTotal          float64            `json:"memoryTotal"`
	OS                   string             `json:"os"`
	Ports                map[string]int     `json:"ports"`
	Status               string             `json:"status"`
	Uptime               int                `json:"uptime,string"`
	Version              string             `json:"version"`
	ThisNode             bool               `json:"thisNode,omitempty"`
}

// clusterCfg contains information about the cluster setup, we only need a subset of that information.
type clusterCfg struct {
	Nodes []NodeMetadata `json:"nodes"`
}

// BucketSettings holds information about the settings for a bucket.
type BucketSettings struct {
	FlushEnabled  bool
	IndexReplicas bool
	Name          string
	Password      string
	Quota         int
	Replicas      int
	Type          BucketType
}

func (cm *ClusterManager) getMgmtEp() string {
	return cm.hosts[rand.Intn(len(cm.hosts))]
}

func (cm *ClusterManager) mgmtRequest(method, uri string, contentType string, body io.Reader) (*http.Response, error) {
	if contentType == "" && body != nil {
		panic("Content-type must be specified for non-null body.")
	}

	reqUri := cm.getMgmtEp() + uri
	req, err := http.NewRequest(method, reqUri, body)
	if err != nil {
		return nil, err
	}

	if contentType != "" {
		req.Header.Add("Content-Type", contentType)
	}
	if cm.username != "" || cm.password != "" {
		req.SetBasicAuth(cm.username, cm.password)
	}

	return cm.httpCli.Do(req)
}

func bucketDataInToSettings(bucketData *bucketDataIn) *BucketSettings {
	settings := &BucketSettings{
		FlushEnabled:  bucketData.Controllers.Flush != "",
		IndexReplicas: bucketData.ReplicaIndex,
		Name:          bucketData.Name,
		Password:      bucketData.SaslPassword,
		Quota:         bucketData.Quota.Ram,
		Replicas:      bucketData.ReplicaNumber,
	}
	if bucketData.BucketType == "membase" {
		settings.Type = Couchbase
	} else if bucketData.BucketType == "memcached" {
		settings.Type = Memcached
	} else if bucketData.BucketType == "ephemeral" {
		settings.Type = Ephemeral
	} else {
		panic("Unrecognized bucket type string.")
	}
	if bucketData.AuthType != "sasl" {
		settings.Password = ""
	}
	return settings
}

// GetBuckets returns a list of all active buckets on the cluster.
func (cm *ClusterManager) GetBuckets() ([]*BucketSettings, error) {
	resp, err := cm.mgmtRequest("GET", "/pools/default/buckets", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var bucketsData []*bucketDataIn
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&bucketsData)
	if err != nil {
		return nil, err
	}

	var buckets []*BucketSettings
	for _, bucketData := range bucketsData {
		buckets = append(buckets, bucketDataInToSettings(bucketData))
	}

	return buckets, nil
}

// InsertBucket creates a new bucket on the cluster.
func (cm *ClusterManager) InsertBucket(settings *BucketSettings) error {
	posts := url.Values{}
	posts.Add("name", settings.Name)
	if settings.Type == Couchbase {
		posts.Add("bucketType", "couchbase")
	} else if settings.Type == Memcached {
		posts.Add("bucketType", "memcached")
	} else if settings.Type == Ephemeral {
		posts.Add("bucketType", "ephemeral")
	} else {
		panic("Unrecognized bucket type.")
	}
	if settings.FlushEnabled {
		posts.Add("flushEnabled", "1")
	} else {
		posts.Add("flushEnabled", "0")
	}
	posts.Add("replicaNumber", fmt.Sprintf("%d", settings.Replicas))
	posts.Add("authType", "sasl")
	posts.Add("saslPassword", settings.Password)
	posts.Add("ramQuotaMB", fmt.Sprintf("%d", settings.Quota))

	data := []byte(posts.Encode())
	resp, err := cm.mgmtRequest("POST", "/pools/default/buckets", "application/x-www-form-urlencoded", bytes.NewReader(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != 202 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UpdateBucket will update the settings for a specific bucket on the cluster.
func (cm *ClusterManager) UpdateBucket(settings *BucketSettings) error {
	// Cluster-side, updates are the same as creates.
	return cm.InsertBucket(settings)
}

// RemoveBucket will delete a bucket from the cluster by name.
func (cm *ClusterManager) RemoveBucket(name string) error {
	reqUri := fmt.Sprintf("/pools/default/buckets/%s", name)

	resp, err := cm.mgmtRequest("DELETE", reqUri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UserRole represents a role for a particular user on the server.
type UserRole struct {
	Role       string
	BucketName string
}

// User represents a user which was retrieved from the server.
type User struct {
	Id    string
	Name  string
	Type  string
	Roles []UserRole
}

// AuthDomain specifies the user domain of a specific user
type AuthDomain string

const (
	// LocalDomain specifies users that are locally stored in Couchbase.
	LocalDomain AuthDomain = "local"

	// ExternalDomain specifies users that are externally stored
	// (in LDAP for instance).
	ExternalDomain = "external"
)

// UserSettings represents a user during user creation.
type UserSettings struct {
	Name     string
	Password string
	Roles    []UserRole
}

type userRoleJson struct {
	Role       string `json:"role"`
	BucketName string `json:"bucket_name"`
}

type userJson struct {
	Id    string         `json:"id"`
	Name  string         `json:"name"`
	Type  string         `json:"type"`
	Roles []userRoleJson `json:"roles"`
}

type userSettingsJson struct {
	Name     string         `json:"name"`
	Password string         `json:"password"`
	Roles    []userRoleJson `json:"roles"`
}

// ClusterManagerInternal holds internally used cluster manager extension methods.
//
// Internal: This should never be used and is not supported.
type ClusterManagerInternal struct {
	manager *ClusterManager
}

func transformUserJson(userData *userJson) User {
	var user User
	user.Id = userData.Id
	user.Name = userData.Name
	user.Type = userData.Type
	for _, roleData := range userData.Roles {
		user.Roles = append(user.Roles, UserRole{
			Role:       roleData.Role,
			BucketName: roleData.BucketName,
		})
	}
	return user
}

// GetUsers returns a list of all users on the cluster.
func (cm *ClusterManager) GetUsers(domain AuthDomain) ([]*User, error) {
	uri := fmt.Sprintf("/settings/rbac/users/%s", domain)
	resp, err := cm.mgmtRequest("GET", uri, "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var usersData []*userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&usersData)
	if err != nil {
		return nil, err
	}

	var users []*User
	for _, userData := range usersData {
		user := transformUserJson(userData)
		users = append(users, &user)
	}

	return users, nil
}

// GetUser returns the data for a particular user
func (cm *ClusterManager) GetUser(domain AuthDomain, name string) (*User, error) {
	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	resp, err := cm.mgmtRequest("GET", uri, "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var userData userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&userData)
	if err != nil {
		return nil, err
	}

	user := transformUserJson(&userData)
	return &user, nil
}

// UpsertUser updates a built-in RBAC user on the cluster.
func (cm *ClusterManager) UpsertUser(domain AuthDomain, name string, settings *UserSettings) error {
	var reqRoleStrs []string
	for _, roleData := range settings.Roles {
		reqRoleStrs = append(reqRoleStrs, fmt.Sprintf("%s[%s]", roleData.Role, roleData.BucketName))
	}

	reqForm := make(url.Values)
	reqForm.Add("name", settings.Name)
	reqForm.Add("password", settings.Password)
	reqForm.Add("roles", strings.Join(reqRoleStrs, ","))

	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	reqBody := bytes.NewReader([]byte(reqForm.Encode()))
	resp, err := cm.mgmtRequest("PUT", uri, "application/x-www-form-urlencoded", reqBody)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// RemoveUser removes a built-in RBAC user on the cluster.
func (cm *ClusterManager) RemoveUser(domain AuthDomain, name string) error {
	uri := fmt.Sprintf("/settings/rbac/users/%s/%s", domain, name)
	resp, err := cm.mgmtRequest("DELETE", uri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// SearchIndexManager returns a SearchIndexManager for performing FTS index management on this cluster
// Experimental: This API is subject to change at any time.
func (cm *ClusterManager) SearchIndexManager() *SearchIndexManager {
	return &SearchIndexManager{
		authenticator: cm.cluster.auth,
		httpCli:       cm.httpCli,
		cluster:       cm.cluster,
	}
}

// Internal returns a ClusterManagerInternal internally used cluster manager extension methods.
//
// Internal: This should never be used and is not supported.
func (cm *ClusterManager) Internal() *ClusterManagerInternal {
	return &ClusterManagerInternal{
		manager: cm,
	}
}

// GetNodesMetadata returns a list of information about nodes in the cluster.
func (cmi *ClusterManagerInternal) GetNodesMetadata() ([]NodeMetadata, error) {
	resp, err := cmi.manager.mgmtRequest("GET", "/pools/default", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var clusterData clusterCfg
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&clusterData)
	if err != nil {
		return nil, err
	}

	return clusterData.Nodes, nil
}
<|endoftext|>


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

package main

import (
	"strings"

	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/mc/pkg/quick"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

// runConfigCmd is the handle for "mc config" sub-command
func runConfigCmd(ctx *cli.Context) {
	// show help if nothing is set
	if !ctx.Args().Present() || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "config", 1) // last argument is exit code
	}
	arg := ctx.Args().First()
	tailArgs := ctx.Args().Tail()
	if len(tailArgs) > 2 {
		log.Debug.Println(iodine.New(errInvalidArgument{}, nil))
		console.Fatalln("Incorrect number of arguments, please use \"mc config help\"")
	}
	msg, err := doConfig(arg, tailArgs)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalln(msg)
	}
}

// saveConfig writes configuration data in json format to config file.
func saveConfig(arg string, aliases []string) error {
	switch arg {
	case "generate":
		if isMcConfigExist() {
			return iodine.New(errConfigExists{}, nil)
		}
		err := writeConfig(newConfig())
		if err != nil {
			return iodine.New(err, nil)
		}
		return nil
	case "alias":
		config, err := addAlias(aliases)
		if err != nil {
			return iodine.New(err, nil)
		}
		return writeConfig(config)
	default:
		return iodine.New(errInvalidArgument{}, nil)
	}
}

// doConfig is the handler for "mc config" sub-command.
func doConfig(arg string, aliases []string) (string, error) {
	configPath, err := getMcConfigPath()
	if err != nil {
		return "Unable to determine config file path.", iodine.New(err, nil)
	}
	err = saveConfig(arg, aliases)
	if err != nil {
		switch iodine.ToError(err).(type) {
		case errConfigExists:
			return "Configuration file [" + configPath + "] already exists.", iodine.New(err, nil)
		case errInvalidArgument:
			return "Incorrect usage, please use \"mc config help\" ", iodine.New(err, nil)
		case errAliasExists:
			return "Alias [" + aliases[0] + "] already exists", iodine.New(err, nil)
		case errInvalidAliasName:
			return "Alias [" + aliases[0] + "] is reserved word or invalid", iodine.New(err, nil)
		case errInvalidURL:
			return "Alias [" + aliases[1] + "] is invalid URL", iodine.New(err, nil)
		default:
			// unexpected error
			return "Unable to generate config file [" + configPath + "].", iodine.New(err, nil)
		}
	}
	return "Configuration written to [" + configPath + "]. Please update your access credentials.", nil
}

// addAlias - add new aliases
func addAlias(aliases []string) (quick.Config, error) {
	if len(aliases) < 2 {
		return nil, iodine.New(errInvalidArgument{}, nil)
	}
	conf := newConfigV1()
	config := quick.New(conf)
	config.Load(mustGetMcConfigPath())

	aliasName := aliases[0]
	url := strings.TrimSuffix(aliases[1], "/")
	if strings.HasPrefix(aliasName, "http") {
		return nil, iodine.New(errInvalidAliasName{name: aliasName}, nil)
	}
	if !strings.HasPrefix(url, "http") {
		return nil, iodine.New(errInvalidURL{url: url}, nil)
	}
	if !isValidAliasName(aliasName) {
		return nil, iodine.New(errInvalidAliasName{name: aliasName}, nil)
	}
	// convert interface{} back to its original struct
	newConf := config.Data().(*configV1)
	if _, ok := newConf.Aliases[aliasName]; ok {
		return nil, iodine.New(errAliasExists{name: aliasName}, nil)
	}
	newConf.Aliases[aliasName] = url
	newConfig := quick.New(newConf)
	return newConfig, nil
}
<commit_msg>Add missing info<commit_after>/*
 * Minio Client (C) 2014, 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"strings"

	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/mc/pkg/quick"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

// runConfigCmd is the handle for "mc config" sub-command
func runConfigCmd(ctx *cli.Context) {
	// show help if nothing is set
	if !ctx.Args().Present() || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "config", 1) // last argument is exit code
	}
	arg := ctx.Args().First()
	tailArgs := ctx.Args().Tail()
	if len(tailArgs) > 2 {
		log.Debug.Println(iodine.New(errInvalidArgument{}, nil))
		console.Fatalln("Incorrect number of arguments, please use \"mc config help\"")
	}
	msg, err := doConfig(arg, tailArgs)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalln(msg)
	}
	console.Infoln(msg)
}

// saveConfig writes configuration data in json format to config file.
func saveConfig(arg string, aliases []string) error {
	switch arg {
	case "generate":
		if isMcConfigExist() {
			return iodine.New(errConfigExists{}, nil)
		}
		err := writeConfig(newConfig())
		if err != nil {
			return iodine.New(err, nil)
		}
		return nil
	case "alias":
		config, err := addAlias(aliases)
		if err != nil {
			return iodine.New(err, nil)
		}
		return writeConfig(config)
	default:
		return iodine.New(errInvalidArgument{}, nil)
	}
}

// doConfig is the handler for "mc config" sub-command.
func doConfig(arg string, aliases []string) (string, error) {
	configPath, err := getMcConfigPath()
	if err != nil {
		return "Unable to determine config file path.", iodine.New(err, nil)
	}
	err = saveConfig(arg, aliases)
	if err != nil {
		switch iodine.ToError(err).(type) {
		case errConfigExists:
			return "Configuration file [" + configPath + "] already exists.", iodine.New(err, nil)
		case errInvalidArgument:
			return "Incorrect usage, please use \"mc config help\" ", iodine.New(err, nil)
		case errAliasExists:
			return "Alias [" + aliases[0] + "] already exists", iodine.New(err, nil)
		case errInvalidAliasName:
			return "Alias [" + aliases[0] + "] is reserved word or invalid", iodine.New(err, nil)
		case errInvalidURL:
			return "Alias [" + aliases[1] + "] is invalid URL", iodine.New(err, nil)
		default:
			// unexpected error
			return "Unable to generate config file [" + configPath + "].", iodine.New(err, nil)
		}
	}
	return "Configuration written to [" + configPath + "]. Please update your access credentials.", nil
}

// addAlias - add new aliases
func addAlias(aliases []string) (quick.Config, error) {
	if len(aliases) < 2 {
		return nil, iodine.New(errInvalidArgument{}, nil)
	}
	conf := newConfigV1()
	config := quick.New(conf)
	config.Load(mustGetMcConfigPath())

	aliasName := aliases[0]
	url := strings.TrimSuffix(aliases[1], "/")
	if strings.HasPrefix(aliasName, "http") {
		return nil, iodine.New(errInvalidAliasName{name: aliasName}, nil)
	}
	if !strings.HasPrefix(url, "http") {
		return nil, iodine.New(errInvalidURL{url: url}, nil)
	}
	if !isValidAliasName(aliasName) {
		return nil, iodine.New(errInvalidAliasName{name: aliasName}, nil)
	}
	// convert interface{} back to its original struct
	newConf := config.Data().(*configV1)
	if _, ok := newConf.Aliases[aliasName]; ok {
		return nil, iodine.New(errAliasExists{name: aliasName}, nil)
	}
	newConf.Aliases[aliasName] = url
	newConfig := quick.New(newConf)
	return newConfig, nil
}
<|endoftext|>


<commit_before>// Copyright © 2017 Max Goltzsche
//
// 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 cmd

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"

	"github.com/mgoltzsche/cntnr/bundle"
	"github.com/mgoltzsche/cntnr/image"
	"github.com/mgoltzsche/cntnr/model"
	"github.com/mgoltzsche/cntnr/model/oci"
	exterrors "github.com/mgoltzsche/cntnr/pkg/errors"
	"github.com/mgoltzsche/cntnr/run"
	"github.com/mgoltzsche/cntnr/run/factory"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

func wrapRun(cf func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) {
	return func(cmd *cobra.Command, args []string) {
		defer func() {
			if err := recover(); err != nil {
				msg := "\n  OUPS, THIS SEEMS TO BE A BUG!"
				msg += "\n  Please report it at"
				msg += "\n    https://github.com/mgoltzsche/cntnr/issues/new"
				msg += "\n  with a description of what you did and the stacktrace"
				msg += "\n  below if you cannot find an already existing issue at"
				msg += "\n    https://github.com/mgoltzsche/cntnr/issues\n"
				stackTrace := strings.Replace(string(debug.Stack()), "\n", "\n  ", -1)
				// TODO: Add version
				logrus.Fatalf("%+v\n%s\n  PANIC: %s\n  %s", err, msg, err, stackTrace)
				os.Exit(255)
			}
		}()
		err := cf(cmd, args)
		closeLockedImageStore()
		exitOnError(cmd, err)
	}
}

func exitOnError(cmd *cobra.Command, err error) {
	if err == nil {
		return
	}

	// Usage error - print help text and exit
	if _, ok := err.(UsageError); ok {
		logger.Errorf("%s\n%s\n%s", err, cmd.UsageString(), err)
		os.Exit(1)
	}

	// Handle exit error
	exitCode := 255
	errLog := loggers.Error
	cause := errors.Cause(err)
	if exitErr, ok := cause.(*run.ExitError); ok {
		exitCode = exitErr.Code()
		errLog = errLog.WithField("id", exitErr.ContainerID()).WithField("code", exitCode)
		err = errors.New("container process terminated with error")
	}

	// Print error & exit
	errStr := err.Error()
	causeStr := fmt.Sprintf("%+v", cause)
	if causeStr != errStr {
		loggers.Debug.Println(strings.Replace(causeStr, "\n", "\n  ", -1))
	}
	errLog.Println(errStr)
	os.Exit(exitCode)
}

func usageError(msg string) UsageError {
	return UsageError(msg)
}

type UsageError string

func (err UsageError) Error() string {
	return string(err)
}

func exitError(exitCode int, frmt string, values ...interface{}) {
	loggers.Error.Printf(frmt, values...)
	os.Exit(exitCode)
}

func openImageStore() (image.ImageStoreRW, error) {
	if lockedImageStore == nil {
		s, err := store.OpenLockedImageStore()
		if err != nil {
			return nil, err
		}
		lockedImageStore = s
	}
	return lockedImageStore, nil
}

func closeLockedImageStore() {
	if lockedImageStore != nil {
		lockedImageStore.Close()
	}
}

func newContainerManager() (run.ContainerManager, error) {
	return factory.NewContainerManager(flagStateDir, flagRootless, loggers)
}

func resourceResolver(baseDir string, volumes map[string]model.Volume) model.ResourceResolver {
	paths := model.NewPathResolver(baseDir)
	return model.NewResourceResolver(paths, volumes)
}

func runServices(services []model.Service, res model.ResourceResolver) (err error) {
	manager, err := newContainerManager()
	if err != nil {
		return
	}

	containers := run.NewContainerGroup(loggers.Debug)
	defer func() {
		err = exterrors.Append(err, containers.Close())
	}()

	for _, s := range services {
		var c run.Container
		loggers.Debug.Println(s.JSON())
		if c, err = createContainer(&s, res, manager, true); err != nil {
			return
		}
		containers.Add(c)
	}

	closeLockedImageStore()
	containers.Start()
	containers.Wait()
	return
}

func createContainer(model *model.Service, res model.ResourceResolver, manager run.ContainerManager, destroyOnClose bool) (c run.Container, err error) {
	var bundle *bundle.LockedBundle
	if bundle, err = createRuntimeBundle(model, res); err != nil {
		return
	}
	defer func() {
		if err != nil {
			err = exterrors.Append(err, bundle.Close())
		}
	}()

	ioe := run.NewStdContainerIO()
	if model.StdinOpen {
		ioe.Stdin = os.Stdin
	}

	return manager.NewContainer(&run.ContainerConfig{
		Id:             "",
		Bundle:         bundle,
		Io:             ioe,
		NoNewKeyring:   model.NoNewKeyring,
		NoPivotRoot:    model.NoPivot,
		DestroyOnClose: destroyOnClose,
	})
}

func createRuntimeBundle(service *model.Service, res model.ResourceResolver) (b *bundle.LockedBundle, err error) {
	if service.Image == "" {
		return nil, errors.Errorf("service %q has no image", service.Name)
	}

	istore, err := openImageStore()
	if err != nil {
		return
	}

	bundleId := service.Bundle
	bundleDir := ""
	if isFile(bundleId) {
		bundleDir = bundleId
		bundleId = ""
	}

	// Load image and bundle builder
	var builder *bundle.BundleBuilder
	if service.Image == "" {
		builder = bundle.Builder(bundleId)
	} else {
		var img image.Image
		if img, err = image.GetImage(istore, service.Image); err != nil {
			return
		}
		if builder, err = bundle.BuilderFromImage(bundleId, &img); err != nil {
			return
		}
	}

	// Generate config.json
	if err = oci.ToSpec(service, res, flagRootless, flagPRootPath, builder.SpecBuilder); err != nil {
		return
	}

	// Create bundle
	if bundleDir != "" {
		b, err = builder.Build(bundleDir, service.BundleUpdate)
	} else {
		b, err = store.CreateBundle(builder, service.BundleUpdate)
	}
	return
}

func isFile(file string) bool {
	return file != "" && (filepath.IsAbs(file) || file == "." || file == ".." || len(file) > 1 && file[0:2] == "./" || len(file) > 2 && file[0:3] == "../" || file == "~" || len(file) > 1 && file[0:2] == "~/")
}

func checkNonEmpty(s string) (err error) {
	if len(bytes.TrimSpace([]byte(s))) == 0 {
		err = usageError("empty value")
	}
	return
}
<commit_msg>Improved stack trace output in debug logs<commit_after>// Copyright © 2017 Max Goltzsche
//
// 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 cmd

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"

	"github.com/mgoltzsche/cntnr/bundle"
	"github.com/mgoltzsche/cntnr/image"
	"github.com/mgoltzsche/cntnr/model"
	"github.com/mgoltzsche/cntnr/model/oci"
	exterrors "github.com/mgoltzsche/cntnr/pkg/errors"
	"github.com/mgoltzsche/cntnr/run"
	"github.com/mgoltzsche/cntnr/run/factory"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

func wrapRun(cf func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) {
	return func(cmd *cobra.Command, args []string) {
		defer func() {
			if err := recover(); err != nil {
				msg := "\n  OUPS, THIS SEEMS TO BE A BUG!"
				msg += "\n  Please report it at"
				msg += "\n    https://github.com/mgoltzsche/cntnr/issues/new"
				msg += "\n  with a description of what you did and the stacktrace"
				msg += "\n  below if you cannot find an already existing issue at"
				msg += "\n    https://github.com/mgoltzsche/cntnr/issues\n"
				stackTrace := strings.Replace(string(debug.Stack()), "\n", "\n  ", -1)
				// TODO: Add version
				logrus.Fatalf("%+v\n%s\n  PANIC: %s\n  %s", err, msg, err, stackTrace)
				os.Exit(255)
			}
		}()
		err := cf(cmd, args)
		closeLockedImageStore()
		exitOnError(cmd, err)
	}
}

func exitOnError(cmd *cobra.Command, err error) {
	if err == nil {
		return
	}

	// Usage error - print help text and exit
	if _, ok := err.(UsageError); ok {
		logger.Errorf("%s\n%s\n%s", err, cmd.UsageString(), err)
		os.Exit(1)
	}

	// Handle exit error
	exitCode := 255
	errLog := loggers.Error
	cause := errors.Cause(err)
	if exitErr, ok := cause.(*run.ExitError); ok {
		exitCode = exitErr.Code()
		errLog = errLog.WithField("id", exitErr.ContainerID()).WithField("code", exitCode)
		err = errors.New("container process terminated with error")
	}

	// Log stacktrace
	errStr := err.Error()
	errDetails, _ := errorCausesString(err, nil)
	if errDetails != errStr {
		loggers.Debug.Println(errDetails)
	}

	// Print error & exit
	errLog.Println(errStr)
	os.Exit(exitCode)
}

func errorCausesString(err error, lastTraceLines []string) (debug string, lastTracedCause error) {
	type causer interface {
		error
		Cause() error
	}
	type tracer interface {
		error
		StackTrace() errors.StackTrace
	}
	str := ""
	if traced, ok := err.(tracer); ok {
		st := traced.StackTrace()
		traceLines := make([]string, len(st))
		for i, t := range st {
			traceLines[i] = fmt.Sprintf("%+v", t)
		}
		truncate := len(traceLines)
		offset := len(traceLines) - len(lastTraceLines)
		for i := len(traceLines) - 1; i >= 0; i-- {
			j := i - offset
			if j < 0 || len(lastTraceLines) <= j || lastTraceLines[j] != traceLines[i] {
				truncate = i + 1
				break
			}
		}
		for _, t := range traceLines[0:truncate] {
			str += "\n    " + strings.Replace(t, "\n", "\n    ", -1)
		}
		if truncate < len(traceLines)-1 {
			str += "\n    ... (truncated)"
		}
		lastTraceLines = traceLines
	}
	errMsg := err.Error()
	wrapper, ok := err.(causer)
	if ok && wrapper.Cause() != nil {
		cause := wrapper.Cause()
		causeStr, lastTracedCause := errorCausesString(cause, lastTraceLines)
		if str == "" {
			return causeStr, lastTracedCause
		}
		causeMsg := ""
		if lastTracedCause != nil {
			causeMsg = lastTracedCause.Error()
		}
		pos := len(errMsg) - len(causeMsg)
		if strings.HasSuffix(errMsg, ": "+causeMsg) && pos > 0 {
			str = errMsg[0:pos-2] + str
		} else {
			str = errMsg + str
		}
		return str + "\n  " + causeStr, err
	}
	return errMsg + str, err
}

func usageError(msg string) UsageError {
	return UsageError(msg)
}

type UsageError string

func (err UsageError) Error() string {
	return string(err)
}

func exitError(exitCode int, frmt string, values ...interface{}) {
	loggers.Error.Printf(frmt, values...)
	os.Exit(exitCode)
}

func openImageStore() (image.ImageStoreRW, error) {
	if lockedImageStore == nil {
		s, err := store.OpenLockedImageStore()
		if err != nil {
			return nil, err
		}
		lockedImageStore = s
	}
	return lockedImageStore, nil
}

func closeLockedImageStore() {
	if lockedImageStore != nil {
		lockedImageStore.Close()
	}
}

func newContainerManager() (run.ContainerManager, error) {
	return factory.NewContainerManager(flagStateDir, flagRootless, loggers)
}

func resourceResolver(baseDir string, volumes map[string]model.Volume) model.ResourceResolver {
	paths := model.NewPathResolver(baseDir)
	return model.NewResourceResolver(paths, volumes)
}

func runServices(services []model.Service, res model.ResourceResolver) (err error) {
	manager, err := newContainerManager()
	if err != nil {
		return
	}

	containers := run.NewContainerGroup(loggers.Debug)
	defer func() {
		err = exterrors.Append(err, containers.Close())
	}()

	for _, s := range services {
		var c run.Container
		loggers.Debug.Println(s.JSON())
		if c, err = createContainer(&s, res, manager, true); err != nil {
			return
		}
		containers.Add(c)
	}

	closeLockedImageStore()
	containers.Start()
	containers.Wait()
	return
}

func createContainer(model *model.Service, res model.ResourceResolver, manager run.ContainerManager, destroyOnClose bool) (c run.Container, err error) {
	var bundle *bundle.LockedBundle
	if bundle, err = createRuntimeBundle(model, res); err != nil {
		return
	}
	defer func() {
		if err != nil {
			err = exterrors.Append(err, bundle.Close())
		}
	}()

	ioe := run.NewStdContainerIO()
	if model.StdinOpen {
		ioe.Stdin = os.Stdin
	}

	return manager.NewContainer(&run.ContainerConfig{
		Id:             "",
		Bundle:         bundle,
		Io:             ioe,
		NoNewKeyring:   model.NoNewKeyring,
		NoPivotRoot:    model.NoPivot,
		DestroyOnClose: destroyOnClose,
	})
}

func createRuntimeBundle(service *model.Service, res model.ResourceResolver) (b *bundle.LockedBundle, err error) {
	if service.Image == "" {
		return nil, errors.Errorf("service %q has no image", service.Name)
	}

	istore, err := openImageStore()
	if err != nil {
		return
	}

	bundleId := service.Bundle
	bundleDir := ""
	if isFile(bundleId) {
		bundleDir = bundleId
		bundleId = ""
	}

	// Load image and bundle builder
	var builder *bundle.BundleBuilder
	if service.Image == "" {
		builder = bundle.Builder(bundleId)
	} else {
		var img image.Image
		if img, err = image.GetImage(istore, service.Image); err != nil {
			return
		}
		if builder, err = bundle.BuilderFromImage(bundleId, &img); err != nil {
			return
		}
	}

	// Generate config.json
	if err = oci.ToSpec(service, res, flagRootless, flagPRootPath, builder.SpecBuilder); err != nil {
		return
	}

	// Create bundle
	if bundleDir != "" {
		b, err = builder.Build(bundleDir, service.BundleUpdate)
	} else {
		b, err = store.CreateBundle(builder, service.BundleUpdate)
	}
	return
}

func isFile(file string) bool {
	return file != "" && (filepath.IsAbs(file) || file == "." || file == ".." || len(file) > 1 && file[0:2] == "./" || len(file) > 2 && file[0:3] == "../" || file == "~" || len(file) > 1 && file[0:2] == "~/")
}

func checkNonEmpty(s string) (err error) {
	if len(bytes.TrimSpace([]byte(s))) == 0 {
		err = usageError("empty value")
	}
	return
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
	"time"
	"unicode"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	// "github.com/spf13/viper"

	"github.com/ostrost/ostent/flags"
	"github.com/ostrost/ostent/internal"
	"github.com/ostrost/ostent/internal/agent"
	"github.com/ostrost/ostent/internal/config"
	"github.com/ostrost/ostent/ostent"
	"github.com/ostrost/ostent/params"

	// plugging outputs:
	_ "github.com/influxdata/telegraf/plugins/outputs/graphite"
	_ "github.com/influxdata/telegraf/plugins/outputs/influxdb"
	_ "github.com/influxdata/telegraf/plugins/outputs/librato"

	_ "github.com/ostrost/ostent/internal/plugins/outputs/ostent" // "ostent" output

	// plugging inputs:
	_ "github.com/influxdata/telegraf/plugins/inputs/system" // "{cpu,disk,mem,swap}" inputs

	_ "github.com/ostrost/ostent/procstat_ostent" // "procstat_ostent" input
	_ "github.com/ostrost/ostent/system_ostent"   // "{net,system}_ostent" inputs
)

var (
	persistentPostRuns runs // a list of funcs to be cobra.Command's PersistentPostRunE.
	persistentPreRuns  runs // a list of funcs to be cobra.Command's PersistentPreRunEE.
	preRuns            runs // a list of funcs to be cobra.Command's PreRunE.
)

type runs struct {
	mutex sync.Mutex     // protect everything e.g. list
	list  []func() error // the list to have
}

func (rs *runs) add(f func() error) {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	rs.list = append(rs.list, f)
}

func (rs *runs) runE(*cobra.Command, []string) error {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	for _, run := range rs.list {
		if err := run(); err != nil {
			return err
		}
	}
	return nil
}

var (
	cconfig = config.NewConfig()
	// DelayFlags sets min and max for any delay.
	DelayFlags = flags.DelayBounds{
		Max: flags.Delay{Duration: 10 * time.Minute},
		Min: flags.Delay{Duration: time.Second},
		// 10m and 1s are corresponding defaults
	}

	// OstentBind is the flag value.
	OstentBind = flags.NewBind("", 8050)

	// OstentCmd represents the base command when called without any subcommands
	OstentCmd = &cobra.Command{
		SilenceUsage: true,
		Use:          "ostent",
		Short:        "Ostent is a metrics tool.",
		Long: `Ostent collects system metrics and put them on display.
Optionally exports them to metrics servers.

To continuously export collected metrics use graphite, influxdb and/or librato flags.
Use multiple flags and/or use comma separated endpoints for the same kind.`,
		Example: `ostent --graphite 10.0.0.1,10.0.0.2:2004\?delay=30s
ostent --influxdb http://10.0.0.3\?delay=60s
ostent --librato \?email=EMAIL\&token=TOKEN

`,

		PersistentPostRunE: persistentPostRuns.runE,
		PersistentPreRunE:  persistentPreRuns.runE,
		PreRunE:            preRuns.runE,
	}
)

func init() {
	// cobra.OnInitialize(initConfig)

	// Here you will define your flags and configuration settings.
	// Cobra supports Persistent Flags, which, if defined here,
	// will be global for your application.

	// OstentCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ostent.yaml)")
	// Cobra also supports local flags, which will only run
	// when this action is called directly.

	persistentPreRuns.add(OstentVersionRun)
	OstentCmd.PersistentFlags().BoolVar(&VersionFlag, "version", false, "Print version and exit")

	OstentCmd.Flags().VarP(&OstentBind, "bind", "b", "Bind `address`")
	OstentCmd.Flags().Var(&DelayFlags.Max, "max-delay", "Maximum for display `delay`")
	OstentCmd.Flags().VarP(&DelayFlags.Min, "min-delay", "d", "Collection and display minimum `delay`")

	/* TODO have one func init; rconfig is not set until then
	for _, v := range []struct {
		pointer *string
		name    string
		usage   string
	}{
		{rconfig.Inputs.CPU.Interval, "input-interval-cpu", "Interval for input: cpu"},
		// ...
	} {
		var dvalue string
		if v.pointer != nil || *v.pointer != "" {
			dvalue = *v.pointer
		} else {
			dvalue = cconfig.Agent.Interval.Duration.String()
		}
		OstentCmd.Flags().StringVar(v.pointer, v.name, dvalue, v.usage)
	}
	*/

	/* TODO
	   - remove `*d` parameters
	     - all corresponding inputs have interval input config values
	     - MAYBE add flags for these
	   - remove `--max-delay` flag as there won't be `*d` params any more
	   - the `-d` aka `--min-delay` is to be replaced by `--interval` which maps to interval agent config value
	*/

	oneSecond := internal.Duration{time.Second}
	// TODO Not to change these and go with defaults: 10s/10s
	cconfig.Agent.Interval = oneSecond
	cconfig.Agent.FlushInterval = oneSecond

	preRuns.add(func() error {
		if DelayFlags.Max.Duration < DelayFlags.Min.Duration {
			DelayFlags.Max.Duration = DelayFlags.Min.Duration
		}
		return nil
	})

	preRuns.add(func() error { return cconfig.LoadInterface("/internal/config", rconfig) })
	var elisting ostent.ExportingListing

	if gends := params.NewGraphiteEndpoints(10*time.Second, flags.NewBind("127.0.0.1", 2003)); true {
		preRuns.add(func() error { return GraphiteRun(&elisting, cconfig, gends) })
		OstentCmd.Flags().Var(&gends, "graphite", "Graphite exporting `endpoint(s)`")
		OstentCmd.Example += "Graphite params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &gends.Default // shortcut, f does not alter it
			// f.Var(&param.ServerAddr, "0", "Graphite server `host[:port]`")
			f.Var(&param.Delay, "1", "Graphite exporting `delay`")
		})
	}

	if iends := params.NewInfluxEndpoints(10*time.Second, "ostent"); true {
		preRuns.add(func() error { return InfluxRun(&elisting, cconfig, iends) })
		OstentCmd.Flags().Var(&iends, "influxdb", "InfluxDB exporting `endpoint(s)`")
		OstentCmd.Example += "InfluxDB params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &iends.Default // shortcut, f does not alter it
			// f.Var(&param.ServerAddr, "0", "InfluxDB server `address`")
			f.Var(&param.Delay, "1", "InfluxDB exporting `delay`")
			f.StringVar(&param.Database, "2", param.Database, "InfluxDB `database`")
			f.StringVar(&param.Username, "3", param.Username, "InfluxDB `username`")
			f.StringVar(&param.Password, "4", param.Password, "InfluxDB `password`")
		}) + "  Any extra parameters become tags in every metrics post to InfluxDB server.\n"
	}

	hostname, _ := os.Hostname()
	if lends := params.NewLibratoEndpoints(10*time.Second, hostname); true {
		preRuns.add(func() error { return LibratoRun(&elisting, cconfig, lends) })
		OstentCmd.Flags().Var(&lends, "librato", "Librato exporting `parameter(s)`")
		OstentCmd.Example += "Librato params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &lends.Default // shortcut, f does not alter it
			f.Var(&param.Delay, "1", "Librato exporting `delay`")
			f.StringVar(&param.Source, "2", param.Source, "Librato `source`")
			f.StringVar(&param.Email, "3", param.Email, "Librato `email`")
			f.StringVar(&param.Token, "4", param.Token, "Librato `token`")
		})
	}
	OstentCmd.Example = strings.TrimRight(OstentCmd.Example, "\n")
	preRuns.add(func() error {
		ostent.Exporting = elisting.ExportingList
		sort.Stable(ostent.Exporting)
		return nil
	})

	// /*
	ostent.AddBackground(func() {
		if err := agent.Run(cconfig); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}) // */
}

/*
// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" { // enable ability to specify config file via flag
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".ostent") // name of config file (without extension)
	viper.AddConfigPath("$HOME")  // adding home directory as first search path
	viper.AutomaticEnv()          // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
*/

// ParamsUsage returns formatted usage of a FlagSet set by setf.
// All flags assumed to be params thus formatting trims dashes.
// The flag names supposed to be digits so it strips them likewise.
func ParamsUsage(setf func(*pflag.FlagSet)) string {
	cmd := cobra.Command{}
	setf(cmd.Flags())
	lines := strings.Split(cmd.NonInheritedFlags().FlagUsages(), "\n")
	for i := range lines {
		lines[i] = strings.TrimPrefix(lines[i], "      --")
		lines[i] = strings.TrimLeftFunc(lines[i], unicode.IsDigit)
		if lines[i] != "" {
			lines[i] = " " + lines[i]
		}
	}
	return strings.Join(lines, "\n")
}

type inputConfig struct {
	Interval *string `toml:",omitempty"`
}

type diskInput struct {
	inputConfig
	IgnoreFs []string
}

var rconfig struct {
	Outputs outputs
	Inputs  inputs
}

type outputs struct{ Ostent struct{} }

func init() {
	on := inputConfig{}
	rconfig = struct {
		Outputs outputs
		Inputs  inputs
	}{
		Outputs: outputs{Ostent: struct{}{}},
		Inputs: inputs{
			SystemOstent: struct{ Interval string }{"1s"},

			// skipped fields are omitted from config
			CPU:            &on,
			Disk:           &diskInput{IgnoreFs: []string{"tmpfs", "devtmpfs"}},
			Mem:            &on,
			NetOstent:      &on,
			ProcstatOstent: &on,
			Swap:           &on,
		}}
}

type inputs struct {
	SystemOstent struct{ Interval string }

	// later fields are pointers with omitempty
	CPU            *inputConfig `toml:",omitempty"`
	Disk           *diskInput   `toml:",omitempty"`
	Mem            *inputConfig `toml:",omitempty"`
	NetOstent      *inputConfig `toml:",omitempty"`
	ProcstatOstent *inputConfig `toml:",omitempty"`
	Swap           *inputConfig `toml:",omitempty"`
}

type namedrop []string

var commonNamedrop = namedrop{
	"system_ostent",
	"procstat_ostent",
}
<commit_msg>flags for inputs interval values. not final<commit_after>package cmd

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
	"time"
	"unicode"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	// "github.com/spf13/viper"

	"github.com/ostrost/ostent/flags"
	"github.com/ostrost/ostent/internal"
	"github.com/ostrost/ostent/internal/agent"
	"github.com/ostrost/ostent/internal/config"
	"github.com/ostrost/ostent/ostent"
	"github.com/ostrost/ostent/params"

	// plugging outputs:
	_ "github.com/influxdata/telegraf/plugins/outputs/graphite"
	_ "github.com/influxdata/telegraf/plugins/outputs/influxdb"
	_ "github.com/influxdata/telegraf/plugins/outputs/librato"

	_ "github.com/ostrost/ostent/internal/plugins/outputs/ostent" // "ostent" output

	// plugging inputs:
	_ "github.com/influxdata/telegraf/plugins/inputs/system" // "{cpu,disk,mem,swap}" inputs

	_ "github.com/ostrost/ostent/procstat_ostent" // "procstat_ostent" input
	_ "github.com/ostrost/ostent/system_ostent"   // "{net,system}_ostent" inputs
)

var (
	persistentPostRuns runs // a list of funcs to be cobra.Command's PersistentPostRunE.
	persistentPreRuns  runs // a list of funcs to be cobra.Command's PersistentPreRunEE.
	preRuns            runs // a list of funcs to be cobra.Command's PreRunE.
)

type runs struct {
	mutex sync.Mutex     // protect everything e.g. list
	list  []func() error // the list to have
}

func (rs *runs) add(f func() error) {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	rs.list = append(rs.list, f)
}

func (rs *runs) runE(*cobra.Command, []string) error {
	rs.mutex.Lock()
	defer rs.mutex.Unlock()
	for _, run := range rs.list {
		if err := run(); err != nil {
			return err
		}
	}
	return nil
}

var (
	cconfig = config.NewConfig()
	// DelayFlags sets min and max for any delay.
	DelayFlags = flags.DelayBounds{
		Max: flags.Delay{Duration: 10 * time.Minute},
		Min: flags.Delay{Duration: time.Second},
		// 10m and 1s are corresponding defaults
	}

	// OstentBind is the flag value.
	OstentBind = flags.NewBind("", 8050)

	// OstentCmd represents the base command when called without any subcommands
	OstentCmd = &cobra.Command{
		SilenceUsage: true,
		Use:          "ostent",
		Short:        "Ostent is a metrics tool.",
		Long: `Ostent collects system metrics and put them on display.
Optionally exports them to metrics servers.

To continuously export collected metrics use graphite, influxdb and/or librato flags.
Use multiple flags and/or use comma separated endpoints for the same kind.`,
		Example: `ostent --graphite 10.0.0.1,10.0.0.2:2004\?delay=30s
ostent --influxdb http://10.0.0.3\?delay=60s
ostent --librato \?email=EMAIL\&token=TOKEN

`,

		PersistentPostRunE: persistentPostRuns.runE,
		PersistentPreRunE:  persistentPreRuns.runE,
		PreRunE:            preRuns.runE,
	}
)

func init() {
	on := func() *inputConfig { return &inputConfig{} }
	rconfig := struct {
		Outputs outputs
		Inputs  inputs
	}{
		Outputs: outputs{Ostent: struct{}{}},
		Inputs: inputs{
			SystemOstent: struct{ Interval string }{"1s"},

			// skipped fields are omitted from config
			CPU:            on(),
			Disk:           &diskInput{IgnoreFs: []string{"tmpfs", "devtmpfs"}},
			Mem:            on(),
			NetOstent:      on(),
			ProcstatOstent: on(),
			Swap:           on(),
		}}

	// cobra.OnInitialize(initConfig)

	// Here you will define your flags and configuration settings.
	// Cobra supports Persistent Flags, which, if defined here,
	// will be global for your application.

	// OstentCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ostent.yaml)")
	// Cobra also supports local flags, which will only run
	// when this action is called directly.

	persistentPreRuns.add(OstentVersionRun)
	OstentCmd.PersistentFlags().BoolVar(&VersionFlag, "version", false, "Print version and exit")

	OstentCmd.Flags().VarP(&OstentBind, "bind", "b", "Bind `address`")
	OstentCmd.Flags().Var(&DelayFlags.Max, "max-delay", "Maximum for display `delay`")
	OstentCmd.Flags().VarP(&DelayFlags.Min, "min-delay", "d", "Collection and display minimum `delay`")

	defaultInterval := cconfig.Agent.Interval.Duration.String()
	intervals := struct{ CPU, Disk, Mem, NetOstent, ProcstatOstent, Swap string }{}
	for _, v := range []struct {
		pointer *string
		name    string
		usage   string
	}{
		{&intervals.CPU, "input-interval-cpu", "Interval for input: cpu"},
		{&intervals.Disk, "input-interval-disk", "Interval for input: disk"},
		{&intervals.Mem, "input-interval-mem", "Interval for input: mem"},
		{&intervals.NetOstent, "input-interval-net-ostent", "Interval for input: net_ostent"},
		{&intervals.ProcstatOstent, "input-interval-procstat-ostent", "Interval for input: procstat_ostent"},
		{&intervals.Swap, "input-interval-swap", "Interval for input: swap"},
		// ...
	} {
		OstentCmd.Flags().StringVar(v.pointer, v.name, defaultInterval, v.usage)
	}

	/* TODO
	   - remove `*d` parameters
	     - all corresponding inputs have interval input config values
	     - MAYBE add flags for these
	   - remove `--max-delay` flag as there won't be `*d` params any more
	   - the `-d` aka `--min-delay` is to be replaced by `--interval` which maps to interval agent config value
	*/

	oneSecond := internal.Duration{time.Second}
	// TODO Not to change these and go with defaults: 10s/10s
	cconfig.Agent.Interval = oneSecond
	cconfig.Agent.FlushInterval = oneSecond

	preRuns.add(func() error {
		if DelayFlags.Max.Duration < DelayFlags.Min.Duration {
			DelayFlags.Max.Duration = DelayFlags.Min.Duration
		}
		return nil
	})

	preRuns.add(func() error {
		for _, v := range []struct {
			pointer **string
			value   string
		}{
			{&rconfig.Inputs.CPU.Interval, intervals.CPU},
			{&rconfig.Inputs.Disk.Interval, intervals.Disk},
			{&rconfig.Inputs.Mem.Interval, intervals.Mem},
			{&rconfig.Inputs.NetOstent.Interval, intervals.NetOstent},
			{&rconfig.Inputs.ProcstatOstent.Interval, intervals.ProcstatOstent},
			{&rconfig.Inputs.Swap.Interval, intervals.Swap},
		} {
			if v.value != defaultInterval {
				*v.pointer = new(string)
				**v.pointer = v.value
			}
		}
		return cconfig.LoadInterface("/internal/config", rconfig)
	})
	var elisting ostent.ExportingListing

	if gends := params.NewGraphiteEndpoints(10*time.Second, flags.NewBind("127.0.0.1", 2003)); true {
		preRuns.add(func() error { return GraphiteRun(&elisting, cconfig, gends) })
		OstentCmd.Flags().Var(&gends, "graphite", "Graphite exporting `endpoint(s)`")
		OstentCmd.Example += "Graphite params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &gends.Default // shortcut, f does not alter it
			// f.Var(&param.ServerAddr, "0", "Graphite server `host[:port]`")
			f.Var(&param.Delay, "1", "Graphite exporting `delay`")
		})
	}

	if iends := params.NewInfluxEndpoints(10*time.Second, "ostent"); true {
		preRuns.add(func() error { return InfluxRun(&elisting, cconfig, iends) })
		OstentCmd.Flags().Var(&iends, "influxdb", "InfluxDB exporting `endpoint(s)`")
		OstentCmd.Example += "InfluxDB params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &iends.Default // shortcut, f does not alter it
			// f.Var(&param.ServerAddr, "0", "InfluxDB server `address`")
			f.Var(&param.Delay, "1", "InfluxDB exporting `delay`")
			f.StringVar(&param.Database, "2", param.Database, "InfluxDB `database`")
			f.StringVar(&param.Username, "3", param.Username, "InfluxDB `username`")
			f.StringVar(&param.Password, "4", param.Password, "InfluxDB `password`")
		}) + "  Any extra parameters become tags in every metrics post to InfluxDB server.\n"
	}

	hostname, _ := os.Hostname()
	if lends := params.NewLibratoEndpoints(10*time.Second, hostname); true {
		preRuns.add(func() error { return LibratoRun(&elisting, cconfig, lends) })
		OstentCmd.Flags().Var(&lends, "librato", "Librato exporting `parameter(s)`")
		OstentCmd.Example += "Librato params:\n" + ParamsUsage(func(f *pflag.FlagSet) {
			param := &lends.Default // shortcut, f does not alter it
			f.Var(&param.Delay, "1", "Librato exporting `delay`")
			f.StringVar(&param.Source, "2", param.Source, "Librato `source`")
			f.StringVar(&param.Email, "3", param.Email, "Librato `email`")
			f.StringVar(&param.Token, "4", param.Token, "Librato `token`")
		})
	}
	OstentCmd.Example = strings.TrimRight(OstentCmd.Example, "\n")
	preRuns.add(func() error {
		ostent.Exporting = elisting.ExportingList
		sort.Stable(ostent.Exporting)
		return nil
	})

	// /*
	ostent.AddBackground(func() {
		if err := agent.Run(cconfig); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}) // */
}

/*
// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" { // enable ability to specify config file via flag
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".ostent") // name of config file (without extension)
	viper.AddConfigPath("$HOME")  // adding home directory as first search path
	viper.AutomaticEnv()          // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
*/

// ParamsUsage returns formatted usage of a FlagSet set by setf.
// All flags assumed to be params thus formatting trims dashes.
// The flag names supposed to be digits so it strips them likewise.
func ParamsUsage(setf func(*pflag.FlagSet)) string {
	cmd := cobra.Command{}
	setf(cmd.Flags())
	lines := strings.Split(cmd.NonInheritedFlags().FlagUsages(), "\n")
	for i := range lines {
		lines[i] = strings.TrimPrefix(lines[i], "      --")
		lines[i] = strings.TrimLeftFunc(lines[i], unicode.IsDigit)
		if lines[i] != "" {
			lines[i] = " " + lines[i]
		}
	}
	return strings.Join(lines, "\n")
}

type inputConfig struct {
	Interval *string `toml:",omitempty"`
}

type diskInput struct {
	inputConfig
	IgnoreFs []string
}

type outputs struct{ Ostent struct{} }

type inputs struct {
	SystemOstent struct{ Interval string }

	// later fields are pointers with omitempty
	CPU            *inputConfig `toml:",omitempty"`
	Disk           *diskInput   `toml:",omitempty"`
	Mem            *inputConfig `toml:",omitempty"`
	NetOstent      *inputConfig `toml:",omitempty"`
	ProcstatOstent *inputConfig `toml:",omitempty"`
	Swap           *inputConfig `toml:",omitempty"`
}

type namedrop []string

var commonNamedrop = namedrop{
	"system_ostent",
	"procstat_ostent",
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/wasanx25/sreq/manager"

	"github.com/spf13/cobra"
)

var (
	editor string
	lynx   bool
	sort   string
)

var searchCmd = &cobra.Command{
	Use:     "search",
	Aliases: []string{"s"},
	Short:   "Search on Qiita (short-cut alias: \"s\")",
	Run: func(cmd *cobra.Command, args []string) {
		m := manager.New(strings.Join(args, ","), sort)
		err := m.Run()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	},
}

func init() {
	RootCmd.AddCommand(searchCmd)
	searchCmd.Flags().StringVar(&sort, "sort", "rel", "Select rel or created or stock for sort")
}
<commit_msg>Remove unused options<commit_after>package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/wasanx25/sreq/manager"

	"github.com/spf13/cobra"
)

var sort string

var searchCmd = &cobra.Command{
	Use:     "search",
	Aliases: []string{"s"},
	Short:   "Search on Qiita (short-cut alias: \"s\")",
	Run: func(cmd *cobra.Command, args []string) {
		m := manager.New(strings.Join(args, ","), sort)
		err := m.Run()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	},
}

func init() {
	RootCmd.AddCommand(searchCmd)
	searchCmd.Flags().StringVar(&sort, "sort", "rel", "Select rel or created or stock for sort")
}
<|endoftext|>


<commit_before>// Copyright 2017 Elliott Polk. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/elliottpolk/confgr/datastore"
	"github.com/elliottpolk/confgr/log"
	"github.com/elliottpolk/confgr/service"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

const (
	DefaultStdPort string = "8080"
	DefaultTlsPort string = "8443"

	EnvStdPort string = "CONFGR_STD_PORT"
	EnvTlsPort string = "CONFGR_TLS_PORT"
)

func Serve(context *cli.Context) {
	context.Command.VisibleFlags()

	dsf := context.String(Simplify(DatastoreFlag.Name))
	if len(dsf) < 1 {
		log.NewError("a valid datastore value must be provided")
		return
	}

	//	ensure the expected directory exists
	dir := filepath.Dir(dsf)
	if _, err := os.Stat(dir); err != nil {
		if !os.IsNotExist(err) {
			log.Error(err, "unable to access datastore directory")
			return
		}

		if err := os.MkdirAll(dir, 0644); err != nil {
			log.Error(err, "unable to create datastore directory")
			return
		}
	}

	ds, err := datastore.Open(dsf)
	if err != nil {
		log.Fatal(err)
	}
	defer ds.Close(false)

	log.Debug("confgr datastore opened")

	http.HandleFunc(service.PathFind, service.Find)
	http.HandleFunc(service.PathSet, service.Set)
	http.HandleFunc(service.PathRemove, service.Remove)

	go func() {
		cert := context.String(TlsCertFlag.Name)
		key := context.String(TlsKeyFlag.Name)

		if len(cert) < 1 || len(key) < 1 {
			return
		}

		if _, err := os.Stat(cert); err != nil {
			log.Error(err, "unable to access TLS cert file")
			return
		}

		if _, err := os.Stat(key); err != nil {
			log.Error(err, "unable to access TLS key file")
			return
		}

		addr := fmt.Sprintf(":%s", DefaultTlsPort)
		if p := os.Getenv(EnvTlsPort); len(p) > 0 {
			addr = fmt.Sprintf(":%s", p)
		}

		log.Debug("starting HTTPS listener")
		log.Fatal(http.ListenAndServeTLS(addr, cert, key, nil))
	}()

	addr := fmt.Sprintf(":%s", DefaultStdPort)
	if p := os.Getenv(EnvStdPort); len(p) > 0 {
		addr = fmt.Sprintf(":%s", p)
	}

	log.Debug("starting HTTP listener")
	log.Fatal(http.ListenAndServe(addr, nil))
}

func asURL(addr, path, params string) string {
	scheme := "http"
	if https := "https"; strings.HasPrefix(addr, https) {
		addr = strings.TrimPrefix(addr, fmt.Sprintf("%s://", https))
		scheme = https
	}

	//	attempt to scrub the scheme if it was not handled above
	addr = strings.TrimPrefix(addr, "http://")

	return (&url.URL{
		Scheme:   scheme,
		Host:     addr,
		Path:     path,
		RawQuery: params,
	}).String()
}

func retrieve(from string) (string, error) {
	res, err := http.Get(from)
	if err != nil {
		return "", errors.Wrap(err, "unable to call service")
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "unable to read service response body")
	}

	if code := res.StatusCode; code != http.StatusOK {
		return "", errors.Errorf("confgr service responded with status code %d and message %s", code, string(b))
	}

	return string(b), nil
}

func send(to, body string) (string, error) {
	res, err := http.Post(to, http.DetectContentType([]byte(body)), strings.NewReader(body))
	if err != nil {
		return "", errors.Wrap(err, "unable to post config")
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "unable read service response body")
	}

	if code := res.StatusCode; code != http.StatusOK {
		return "", errors.Errorf("confgr service responded with status code %d and message %s", code, string(b))
	}

	return string(b), nil
}
<commit_msg>fixes file permissions<commit_after>// Copyright 2017 Elliott Polk. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/elliottpolk/confgr/datastore"
	"github.com/elliottpolk/confgr/log"
	"github.com/elliottpolk/confgr/service"

	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

const (
	DefaultStdPort string = "8080"
	DefaultTlsPort string = "8443"

	EnvStdPort string = "CONFGR_STD_PORT"
	EnvTlsPort string = "CONFGR_TLS_PORT"
)

func Serve(context *cli.Context) {
	context.Command.VisibleFlags()

	dsf := context.String(Simplify(DatastoreFlag.Name))
	if len(dsf) < 1 {
		log.NewError("a valid datastore value must be provided")
		return
	}

	//	ensure the expected directory exists
	dir := filepath.Dir(dsf)
	if _, err := os.Stat(dir); err != nil {
		if !os.IsNotExist(err) {
			log.Error(err, "unable to access datastore directory")
			return
		}

		if err := os.MkdirAll(dir, 0766); err != nil {
			log.Error(err, "unable to create datastore directory")
			return
		}
	}

	ds, err := datastore.Open(dsf)
	if err != nil {
		log.Fatal(err)
	}
	defer ds.Close(false)

	log.Debug("confgr datastore opened")

	http.HandleFunc(service.PathFind, service.Find)
	http.HandleFunc(service.PathSet, service.Set)
	http.HandleFunc(service.PathRemove, service.Remove)

	go func() {
		cert := context.String(TlsCertFlag.Name)
		key := context.String(TlsKeyFlag.Name)

		if len(cert) < 1 || len(key) < 1 {
			return
		}

		if _, err := os.Stat(cert); err != nil {
			log.Error(err, "unable to access TLS cert file")
			return
		}

		if _, err := os.Stat(key); err != nil {
			log.Error(err, "unable to access TLS key file")
			return
		}

		addr := fmt.Sprintf(":%s", DefaultTlsPort)
		if p := os.Getenv(EnvTlsPort); len(p) > 0 {
			addr = fmt.Sprintf(":%s", p)
		}

		log.Debug("starting HTTPS listener")
		log.Fatal(http.ListenAndServeTLS(addr, cert, key, nil))
	}()

	addr := fmt.Sprintf(":%s", DefaultStdPort)
	if p := os.Getenv(EnvStdPort); len(p) > 0 {
		addr = fmt.Sprintf(":%s", p)
	}

	log.Debug("starting HTTP listener")
	log.Fatal(http.ListenAndServe(addr, nil))
}

func asURL(addr, path, params string) string {
	scheme := "http"
	if https := "https"; strings.HasPrefix(addr, https) {
		addr = strings.TrimPrefix(addr, fmt.Sprintf("%s://", https))
		scheme = https
	}

	//	attempt to scrub the scheme if it was not handled above
	addr = strings.TrimPrefix(addr, "http://")

	return (&url.URL{
		Scheme:   scheme,
		Host:     addr,
		Path:     path,
		RawQuery: params,
	}).String()
}

func retrieve(from string) (string, error) {
	res, err := http.Get(from)
	if err != nil {
		return "", errors.Wrap(err, "unable to call service")
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "unable to read service response body")
	}

	if code := res.StatusCode; code != http.StatusOK {
		return "", errors.Errorf("confgr service responded with status code %d and message %s", code, string(b))
	}

	return string(b), nil
}

func send(to, body string) (string, error) {
	res, err := http.Post(to, http.DetectContentType([]byte(body)), strings.NewReader(body))
	if err != nil {
		return "", errors.Wrap(err, "unable to post config")
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", errors.Wrap(err, "unable read service response body")
	}

	if code := res.StatusCode; code != http.StatusOK {
		return "", errors.Errorf("confgr service responded with status code %d and message %s", code, string(b))
	}

	return string(b), nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"github.com/spf13/cobra"
)

type workerDone struct{}

type requestStat struct {
	duration int64 //nanoseconds
}
type requestStatSummary struct {
	avgQps      float64 //per nanoseconds
	avgDuration int64   //nanoseconds
	maxDuration int64   //nanoseconds
	minDuration int64   //nanoseconds
}

//verbose levels
const (
	VerboseNone = iota
	VerboseLow
	VerboseMedium
	VerboseHigh
)

//flags
var (
	numTests      int
	timeout       int
	concurrency   int
	requestMethod string
	verboseLevel  int
)

func init() {
	RootCmd.AddCommand(stressCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// stressCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// stressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

	stressCmd.Flags().IntVarP(&numTests, "num", "n", 1, "Number of requests to make")
	stressCmd.Flags().IntVarP(&concurrency, "concurrent", "c", 1, "Number of multiple requests to make")
	stressCmd.Flags().IntVarP(&timeout, "timeout", "t", 0, "Maximum seconds to wait for response. 0 means unlimited")
	stressCmd.Flags().StringVarP(&requestMethod, "requestMethod", "X", "GET", "Request type. GET, HEAD, POST, PUT, etc.")
	stressCmd.Flags().IntVarP(&verboseLevel, "verbose", "v", 0, "Level of verbosity ("+strconv.Itoa(VerboseNone)+"-"+strconv.Itoa(VerboseHigh)+")")
}

// stressCmd represents the stress command
var stressCmd = &cobra.Command{
	Use:   "stress http[s]://hostname[:port]/path",
	Short: "Run predefined load of requests",
	Long:  `Run predefined load of requests`,
	RunE:  runStress,
}

func runStress(cmd *cobra.Command, args []string) error {
	//checks
	if len(args) != 1 {
		return errors.New("needs URL")
	}
	if numTests <= 0 {
		return errors.New("number of requests must be one or more")
	}
	if concurrency <= 0 {
		return errors.New("concurrency must be one or more")
	}
	if timeout < 0 {
		return errors.New("timeout must be zero or more")
	}
	if concurrency > numTests {
		return errors.New("concurrency must be higher than number of requests")
	}
	if verboseLevel < VerboseNone || verboseLevel > VerboseHigh {
		return errors.New("verbose level must be between " + strconv.Itoa(VerboseNone) + " and " + strconv.Itoa(VerboseHigh))
	}

	url := args[0]

	fmt.Println("Stress testing " + url + "...")

	//setup the request
	req, err := http.NewRequest(requestMethod, url, nil)
	if err != nil {
		return errors.New("failed to create request: " + err.Error())
	}

	//info about the request
	if verboseLevel >= VerboseMedium {
		fmt.Println(req.Proto)
		fmt.Println("Method: " + req.Method)
		fmt.Println("Host: " + req.Host)
	}

	//setup the queue of requests
	requestChan := make(chan *http.Request, numTests)
	for i := 0; i < numTests; i++ {
		requestChan <- req
	}
	close(requestChan)

	workerDoneChan := make(chan workerDone)   //workers use this to indicate they are done
	requestStatChan := make(chan requestStat) //workers communicate each requests' info

	//workers
	totalStartTime := time.Now()
	for i := 0; i < concurrency; i++ {
		//TODO handle the returned errors from this
		go func() error {
			client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
			for {
				select {
				case req, ok := <-requestChan:
					if !ok {
						workerDoneChan <- workerDone{}
						return nil
					}
					//run the acutal request
					reqStartTime := time.Now()
					response, err := client.Do((*http.Request)(req))
					reqEndTime := time.Now()
					if err != nil {
						return errors.New("Failed to make request:" + err.Error())
					}
					reqTimeNs := (reqEndTime.UnixNano() - reqStartTime.UnixNano())

					var requestData string
					if verboseLevel >= VerboseLow {
						requestData = "--------\n\n"
					}
					if verboseLevel >= VerboseLow {
						//request timing
						requestData = requestData + fmt.Sprintf("Request took %dms\n\n", reqTimeNs/1000000)
					}
					if verboseLevel >= VerboseMedium {
						//reponse metadata
						requestData = requestData + fmt.Sprintf("Response:\n%+v\n\n", response)
					}
					if verboseLevel >= VerboseHigh {
						//reponse body
						defer response.Body.Close()
						body, err := ioutil.ReadAll(response.Body)
						if err != nil {
							return errors.New("Failed to read response body:" + err.Error())
						}
						requestData = requestData + fmt.Sprintf("Body:\n%s\n\n", body)
					}

					if requestData != "" {
						fmt.Print(requestData)
					}
					requestStatChan <- requestStat{duration: reqTimeNs}
				}
			}
		}()
	}

	allRequestStats := make([]requestStat, numTests)
	requestsCompleteCount := 0
	workersDoneCount := 0
	//wait for all workers to finish
	for {
		select {
		case <-workerDoneChan:
			workersDoneCount++
			if workersDoneCount == concurrency {
				//all workers are done
				totalEndTime := time.Now()

				totalTimeNs := totalEndTime.UnixNano() - totalStartTime.UnixNano()
				reqStats := createRequestsStats(allRequestStats, totalTimeNs)
				fmt.Println(createTextSummary(reqStats, totalTimeNs))
				return nil
			}
		case requestStat := <-requestStatChan:
			allRequestStats[requestsCompleteCount] = requestStat
			requestsCompleteCount++
		}
	}
}

//create statistical summary of all requests
func createRequestsStats(requestStats []requestStat, totalTimeNs int64) requestStatSummary {
	if len(requestStats) == 0 {
		return requestStatSummary{}
	}

	summary := requestStatSummary{maxDuration: requestStats[0].duration, minDuration: requestStats[0].duration}
	var totalDurations int64
	totalDurations = 0 //total time of all requests (concurrent is counted)
	for i := 0; i < len(requestStats); i++ {
		if requestStats[i].duration > summary.maxDuration {
			summary.maxDuration = requestStats[i].duration
		}
		if requestStats[i].duration < summary.minDuration {
			summary.minDuration = requestStats[i].duration
		}
		totalDurations += requestStats[i].duration
	}
	summary.avgDuration = totalDurations / int64(len(requestStats))
	summary.avgQps = float64(len(requestStats)) / float64(totalTimeNs)
	return summary
}

//creates nice readable summary of entire stress test
func createTextSummary(reqStatSummary requestStatSummary, totalTimeNs int64) string {
	summary := "\n"

	summary = summary + "Runtime Statistics:\n"
	summary = summary + "Total time:  " + strconv.Itoa(int(totalTimeNs/1000000)) + " ms\n"
	summary = summary + "Mean QPS:    " + fmt.Sprintf("%.2f", reqStatSummary.avgQps*1000000000) + " req/sec\n"

	summary = summary + "\nQuery Statistics\n"
	summary = summary + "Mean query:     " + strconv.Itoa(int(reqStatSummary.avgDuration/1000000)) + " ms\n"
	summary = summary + "Fastest query:  " + strconv.Itoa(int(reqStatSummary.minDuration/1000000)) + " ms\n"
	summary = summary + "Slowest query:  " + strconv.Itoa(int(reqStatSummary.maxDuration/1000000)) + " ms\n"
	return summary
}
<commit_msg>Adding support for request body as a string<commit_after>package cmd

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

	"github.com/spf13/cobra"
)

type workerDone struct{}

type requestStat struct {
	duration int64 //nanoseconds
}
type requestStatSummary struct {
	avgQps      float64 //per nanoseconds
	avgDuration int64   //nanoseconds
	maxDuration int64   //nanoseconds
	minDuration int64   //nanoseconds
}

//verbose levels
const (
	VerboseNone = iota
	VerboseLow
	VerboseMedium
	VerboseHigh
)

//flags
var (
	numTests      int
	timeout       int
	concurrency   int
	requestMethod string
	requestBody   string
	verboseLevel  int
)

func init() {
	RootCmd.AddCommand(stressCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// stressCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// stressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

	stressCmd.Flags().IntVarP(&numTests, "num", "n", 1, "Number of requests to make")
	stressCmd.Flags().IntVarP(&concurrency, "concurrent", "c", 1, "Number of multiple requests to make")
	stressCmd.Flags().IntVarP(&timeout, "timeout", "t", 0, "Maximum seconds to wait for response. 0 means unlimited")
	stressCmd.Flags().StringVarP(&requestMethod, "requestMethod", "X", "GET", "Request type. GET, HEAD, POST, PUT, etc.")
	stressCmd.Flags().StringVarP(&requestBody, "body", "", "", "String to use as request body e.g. POST body.")
	stressCmd.Flags().IntVarP(&verboseLevel, "verbose", "v", 0, "Level of verbosity ("+strconv.Itoa(VerboseNone)+"-"+strconv.Itoa(VerboseHigh)+")")
}

// stressCmd represents the stress command
var stressCmd = &cobra.Command{
	Use:   "stress http[s]://hostname[:port]/path",
	Short: "Run predefined load of requests",
	Long:  `Run predefined load of requests`,
	RunE:  runStress,
}

func runStress(cmd *cobra.Command, args []string) error {
	//checks
	if len(args) != 1 {
		return errors.New("needs URL")
	}
	if numTests <= 0 {
		return errors.New("number of requests must be one or more")
	}
	if concurrency <= 0 {
		return errors.New("concurrency must be one or more")
	}
	if timeout < 0 {
		return errors.New("timeout must be zero or more")
	}
	if concurrency > numTests {
		return errors.New("concurrency must be higher than number of requests")
	}
	if verboseLevel < VerboseNone || verboseLevel > VerboseHigh {
		return errors.New("verbose level must be between " + strconv.Itoa(VerboseNone) + " and " + strconv.Itoa(VerboseHigh))
	}

	url := args[0]

	fmt.Println("Stress testing " + url + "...")

	//setup the request
	var req *http.Request
	var err error
	if requestBody != "" {
		req, err = http.NewRequest(requestMethod, url, bytes.NewBuffer([]byte(requestBody)))
	} else {
		req, err = http.NewRequest(requestMethod, url, nil)
	}
	if err != nil {
		return errors.New("failed to create request: " + err.Error())
	}

	//info about the request
	if verboseLevel >= VerboseMedium {
		fmt.Println(req.Proto)
		fmt.Println("Method: " + req.Method)
		fmt.Println("Host: " + req.Host)
	}

	//setup the queue of requests
	requestChan := make(chan *http.Request, numTests)
	for i := 0; i < numTests; i++ {
		requestChan <- req
	}
	close(requestChan)

	workerDoneChan := make(chan workerDone)   //workers use this to indicate they are done
	requestStatChan := make(chan requestStat) //workers communicate each requests' info

	//workers
	totalStartTime := time.Now()
	for i := 0; i < concurrency; i++ {
		//TODO handle the returned errors from this
		go func() error {
			client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
			for {
				select {
				case req, ok := <-requestChan:
					if !ok {
						workerDoneChan <- workerDone{}
						return nil
					}
					//run the acutal request
					reqStartTime := time.Now()
					response, err := client.Do((*http.Request)(req))
					reqEndTime := time.Now()
					if err != nil {
						return errors.New("Failed to make request:" + err.Error())
					}
					reqTimeNs := (reqEndTime.UnixNano() - reqStartTime.UnixNano())

					var requestData string
					if verboseLevel >= VerboseLow {
						requestData = "--------\n\n"
					}
					if verboseLevel >= VerboseLow {
						//request timing
						requestData = requestData + fmt.Sprintf("Request took %dms\n\n", reqTimeNs/1000000)
					}
					if verboseLevel >= VerboseMedium {
						//reponse metadata
						requestData = requestData + fmt.Sprintf("Response:\n%+v\n\n", response)
					}
					if verboseLevel >= VerboseHigh {
						//reponse body
						defer response.Body.Close()
						body, err := ioutil.ReadAll(response.Body)
						if err != nil {
							return errors.New("Failed to read response body:" + err.Error())
						}
						requestData = requestData + fmt.Sprintf("Body:\n%s\n\n", body)
					}

					if requestData != "" {
						fmt.Print(requestData)
					}
					requestStatChan <- requestStat{duration: reqTimeNs}
				}
			}
		}()
	}

	allRequestStats := make([]requestStat, numTests)
	requestsCompleteCount := 0
	workersDoneCount := 0
	//wait for all workers to finish
	for {
		select {
		case <-workerDoneChan:
			workersDoneCount++
			if workersDoneCount == concurrency {
				//all workers are done
				totalEndTime := time.Now()

				totalTimeNs := totalEndTime.UnixNano() - totalStartTime.UnixNano()
				reqStats := createRequestsStats(allRequestStats, totalTimeNs)
				fmt.Println(createTextSummary(reqStats, totalTimeNs))
				return nil
			}
		case requestStat := <-requestStatChan:
			allRequestStats[requestsCompleteCount] = requestStat
			requestsCompleteCount++
		}
	}
}

//create statistical summary of all requests
func createRequestsStats(requestStats []requestStat, totalTimeNs int64) requestStatSummary {
	if len(requestStats) == 0 {
		return requestStatSummary{}
	}

	summary := requestStatSummary{maxDuration: requestStats[0].duration, minDuration: requestStats[0].duration}
	var totalDurations int64
	totalDurations = 0 //total time of all requests (concurrent is counted)
	for i := 0; i < len(requestStats); i++ {
		if requestStats[i].duration > summary.maxDuration {
			summary.maxDuration = requestStats[i].duration
		}
		if requestStats[i].duration < summary.minDuration {
			summary.minDuration = requestStats[i].duration
		}
		totalDurations += requestStats[i].duration
	}
	summary.avgDuration = totalDurations / int64(len(requestStats))
	summary.avgQps = float64(len(requestStats)) / float64(totalTimeNs)
	return summary
}

//creates nice readable summary of entire stress test
func createTextSummary(reqStatSummary requestStatSummary, totalTimeNs int64) string {
	summary := "\n"

	summary = summary + "Runtime Statistics:\n"
	summary = summary + "Total time:  " + strconv.Itoa(int(totalTimeNs/1000000)) + " ms\n"
	summary = summary + "Mean QPS:    " + fmt.Sprintf("%.2f", reqStatSummary.avgQps*1000000000) + " req/sec\n"

	summary = summary + "\nQuery Statistics\n"
	summary = summary + "Mean query:     " + strconv.Itoa(int(reqStatSummary.avgDuration/1000000)) + " ms\n"
	summary = summary + "Fastest query:  " + strconv.Itoa(int(reqStatSummary.minDuration/1000000)) + " ms\n"
	summary = summary + "Slowest query:  " + strconv.Itoa(int(reqStatSummary.maxDuration/1000000)) + " ms\n"
	return summary
}
<|endoftext|>