<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 kubeadm

import (
	"fmt"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/pmezard/go-difflib/difflib"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/cruntime"
	"k8s.io/minikube/pkg/util"
)

func TestGenerateKubeletConfig(t *testing.T) {
	tests := []struct {
		description string
		cfg         config.KubernetesConfig
		expected    string
		shouldErr   bool
	}{
		{
			description: "old docker",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.OldestKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "docker",
			},
			expected: `
[Unit]
Wants=docker.socket

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --allow-privileged=true --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cadvisor-port=0 --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=docker --fail-swap-on=false --hostname-override=minikube --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests

[Install]
`,
		},
		{
			description: "newest cri runtime",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.NewestKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "cri-o",
			},
			expected: `
[Unit]
Wants=crio.service

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --fail-swap-on=false --hostname-override=minikube --image-service-endpoint=/var/run/crio/crio.sock --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests --runtime-request-timeout=15m

[Install]
`,
		},
		{
			description: "docker with custom image repository",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.DefaultKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "docker",
				ImageRepository:   "docker-proxy-image.io/google_containers",
			},
			expected: `
[Unit]
Wants=docker.socket

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=docker --fail-swap-on=false --hostname-override=minikube --kubeconfig=/etc/kubernetes/kubelet.conf --pod-infra-container-image=docker-proxy-image.io/google_containers/pause:3.1 --pod-manifest-path=/etc/kubernetes/manifests

[Install]
`,
		},
	}

	for _, tc := range tests {
		t.Run(tc.description, func(t *testing.T) {
			runtime, err := cruntime.New(cruntime.Config{Type: tc.cfg.ContainerRuntime})
			if err != nil {
				t.Fatalf("runtime: %v", err)
			}

			got, err := NewKubeletConfig(tc.cfg, runtime)
			if err != nil && !tc.shouldErr {
				t.Errorf("got unexpected error generating config: %v", err)
				return
			}
			if err == nil && tc.shouldErr {
				t.Errorf("expected error but got none, config: %s", got)
				return
			}

			diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
				A:        difflib.SplitLines(tc.expected),
				B:        difflib.SplitLines(got),
				FromFile: "Expected",
				ToFile:   "Got",
				Context:  1,
			})
			if err != nil {
				t.Fatalf("diff error: %v", err)
			}
			if diff != "" {
				t.Errorf("unexpected diff:\n%s", diff)
			}
		})
	}
}

func TestGenerateConfig(t *testing.T) {
	extraOpts := util.ExtraOptionSlice{
		util.ExtraOption{
			Component: Apiserver,
			Key:       "fail-no-swap",
			Value:     "true",
		},
		util.ExtraOption{
			Component: ControllerManager,
			Key:       "kube-api-burst",
			Value:     "32",
		},
		util.ExtraOption{
			Component: Scheduler,
			Key:       "scheduler-name",
			Value:     "mini-scheduler",
		},
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "ignore-preflight-errors",
			Value:     "true",
		},
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "dry-run",
			Value:     "true",
		},
	}

	extraOptsPodCidr := util.ExtraOptionSlice{
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "pod-network-cidr",
			Value:     "192.168.32.0/20",
		},
	}

	// test the 6 most recent releases
	versions := []string{"v1.15", "v1.14", "v1.13", "v1.12", "v1.11", "v1.10"}
	foundNewest := false
	foundDefault := false

	for _, v := range versions {
		if strings.HasPrefix(constants.NewestKubernetesVersion, v) {
			foundNewest = true
		}
		if strings.HasPrefix(constants.DefaultKubernetesVersion, v) {
			foundDefault = true
		}
	}

	if !foundNewest {
		t.Errorf("No tests exist yet for newest minor version: %s", constants.NewestKubernetesVersion)
	}

	if !foundDefault {
		t.Errorf("No tests exist yet for default minor version: %s", constants.DefaultKubernetesVersion)
	}

	tests := []struct {
		name      string
		runtime   string
		shouldErr bool
		cfg       config.KubernetesConfig
	}{
		{"default", "docker", false, config.KubernetesConfig{}},
		{"containerd", "containerd", false, config.KubernetesConfig{}},
		{"crio", "crio", false, config.KubernetesConfig{}},
		{"options", "docker", false, config.KubernetesConfig{ExtraOptions: extraOpts}},
		{"crio-options-gates", "crio", false, config.KubernetesConfig{ExtraOptions: extraOpts, FeatureGates: "a=b"}},
		{"unknown-component", "docker", true, config.KubernetesConfig{ExtraOptions: util.ExtraOptionSlice{util.ExtraOption{Component: "not-a-real-component", Key: "killswitch", Value: "true"}}}},
		{"containerd-api-port", "containerd", false, config.KubernetesConfig{NodePort: 12345}},
		{"containerd-pod-network-cidr", "containerd", false, config.KubernetesConfig{ExtraOptions: extraOptsPodCidr}},
		{"image-repository", "docker", false, config.KubernetesConfig{ImageRepository: "test/repo"}},
	}
	for _, version := range versions {
		for _, tc := range tests {
			runtime, err := cruntime.New(cruntime.Config{Type: tc.runtime})
			if err != nil {
				t.Fatalf("runtime: %v", err)
			}
			tname := tc.name + "_" + version
			t.Run(tname, func(t *testing.T) {
				cfg := tc.cfg
				cfg.NodeIP = "1.1.1.1"
				cfg.NodeName = "mk"
				cfg.KubernetesVersion = version + ".0"

				got, err := generateConfig(cfg, runtime)
				if err != nil && !tc.shouldErr {
					t.Fatalf("got unexpected error generating config: %v", err)
				}
				if err == nil && tc.shouldErr {
					t.Fatalf("expected error but got none, config: %s", got)
				}
				if tc.shouldErr {
					return
				}
				expected, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s/%s.yaml", version, tc.name))
				if err != nil {
					t.Fatalf("unable to read testdata: %v", err)
				}
				diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
					A:        difflib.SplitLines(string(expected)),
					B:        difflib.SplitLines(got),
					FromFile: "Expected",
					ToFile:   "Got",
					Context:  1,
				})
				if err != nil {
					t.Fatalf("diff error: %v", err)
				}
				if diff != "" {
					t.Errorf("unexpected diff:\n%s\n===== [RAW OUTPUT] =====\n%s", diff, got)
				}
			})
		}
	}
}
<commit_msg>Run goimports<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 kubeadm

import (
	"fmt"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/pmezard/go-difflib/difflib"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/cruntime"
	"k8s.io/minikube/pkg/util"
)

func TestGenerateKubeletConfig(t *testing.T) {
	tests := []struct {
		description string
		cfg         config.KubernetesConfig
		expected    string
		shouldErr   bool
	}{
		{
			description: "old docker",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.OldestKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "docker",
			},
			expected: `
[Unit]
Wants=docker.socket

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --allow-privileged=true --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cadvisor-port=0 --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=docker --fail-swap-on=false --hostname-override=minikube --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests

[Install]
`,
		},
		{
			description: "newest cri runtime",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.NewestKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "cri-o",
			},
			expected: `
[Unit]
Wants=crio.service

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --fail-swap-on=false --hostname-override=minikube --image-service-endpoint=/var/run/crio/crio.sock --kubeconfig=/etc/kubernetes/kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests --runtime-request-timeout=15m

[Install]
`,
		},
		{
			description: "docker with custom image repository",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: constants.DefaultKubernetesVersion,
				NodeName:          "minikube",
				ContainerRuntime:  "docker",
				ImageRepository:   "docker-proxy-image.io/google_containers",
			},
			expected: `
[Unit]
Wants=docker.socket

[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --authorization-mode=Webhook --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --cgroup-driver=cgroupfs --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --container-runtime=docker --fail-swap-on=false --hostname-override=minikube --kubeconfig=/etc/kubernetes/kubelet.conf --pod-infra-container-image=docker-proxy-image.io/google_containers/pause:3.1 --pod-manifest-path=/etc/kubernetes/manifests

[Install]
`,
		},
	}

	for _, tc := range tests {
		t.Run(tc.description, func(t *testing.T) {
			runtime, err := cruntime.New(cruntime.Config{Type: tc.cfg.ContainerRuntime})
			if err != nil {
				t.Fatalf("runtime: %v", err)
			}

			got, err := NewKubeletConfig(tc.cfg, runtime)
			if err != nil && !tc.shouldErr {
				t.Errorf("got unexpected error generating config: %v", err)
				return
			}
			if err == nil && tc.shouldErr {
				t.Errorf("expected error but got none, config: %s", got)
				return
			}

			diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
				A:        difflib.SplitLines(tc.expected),
				B:        difflib.SplitLines(got),
				FromFile: "Expected",
				ToFile:   "Got",
				Context:  1,
			})
			if err != nil {
				t.Fatalf("diff error: %v", err)
			}
			if diff != "" {
				t.Errorf("unexpected diff:\n%s", diff)
			}
		})
	}
}

func TestGenerateConfig(t *testing.T) {
	extraOpts := util.ExtraOptionSlice{
		util.ExtraOption{
			Component: Apiserver,
			Key:       "fail-no-swap",
			Value:     "true",
		},
		util.ExtraOption{
			Component: ControllerManager,
			Key:       "kube-api-burst",
			Value:     "32",
		},
		util.ExtraOption{
			Component: Scheduler,
			Key:       "scheduler-name",
			Value:     "mini-scheduler",
		},
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "ignore-preflight-errors",
			Value:     "true",
		},
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "dry-run",
			Value:     "true",
		},
	}

	extraOptsPodCidr := util.ExtraOptionSlice{
		util.ExtraOption{
			Component: Kubeadm,
			Key:       "pod-network-cidr",
			Value:     "192.168.32.0/20",
		},
	}

	// test the 6 most recent releases
	versions := []string{"v1.15", "v1.14", "v1.13", "v1.12", "v1.11", "v1.10"}
	foundNewest := false
	foundDefault := false

	for _, v := range versions {
		if strings.HasPrefix(constants.NewestKubernetesVersion, v) {
			foundNewest = true
		}
		if strings.HasPrefix(constants.DefaultKubernetesVersion, v) {
			foundDefault = true
		}
	}

	if !foundNewest {
		t.Errorf("No tests exist yet for newest minor version: %s", constants.NewestKubernetesVersion)
	}

	if !foundDefault {
		t.Errorf("No tests exist yet for default minor version: %s", constants.DefaultKubernetesVersion)
	}

	tests := []struct {
		name      string
		runtime   string
		shouldErr bool
		cfg       config.KubernetesConfig
	}{
		{"default", "docker", false, config.KubernetesConfig{}},
		{"containerd", "containerd", false, config.KubernetesConfig{}},
		{"crio", "crio", false, config.KubernetesConfig{}},
		{"options", "docker", false, config.KubernetesConfig{ExtraOptions: extraOpts}},
		{"crio-options-gates", "crio", false, config.KubernetesConfig{ExtraOptions: extraOpts, FeatureGates: "a=b"}},
		{"unknown-component", "docker", true, config.KubernetesConfig{ExtraOptions: util.ExtraOptionSlice{util.ExtraOption{Component: "not-a-real-component", Key: "killswitch", Value: "true"}}}},
		{"containerd-api-port", "containerd", false, config.KubernetesConfig{NodePort: 12345}},
		{"containerd-pod-network-cidr", "containerd", false, config.KubernetesConfig{ExtraOptions: extraOptsPodCidr}},
		{"image-repository", "docker", false, config.KubernetesConfig{ImageRepository: "test/repo"}},
	}
	for _, version := range versions {
		for _, tc := range tests {
			runtime, err := cruntime.New(cruntime.Config{Type: tc.runtime})
			if err != nil {
				t.Fatalf("runtime: %v", err)
			}
			tname := tc.name + "_" + version
			t.Run(tname, func(t *testing.T) {
				cfg := tc.cfg
				cfg.NodeIP = "1.1.1.1"
				cfg.NodeName = "mk"
				cfg.KubernetesVersion = version + ".0"

				got, err := generateConfig(cfg, runtime)
				if err != nil && !tc.shouldErr {
					t.Fatalf("got unexpected error generating config: %v", err)
				}
				if err == nil && tc.shouldErr {
					t.Fatalf("expected error but got none, config: %s", got)
				}
				if tc.shouldErr {
					return
				}
				expected, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s/%s.yaml", version, tc.name))
				if err != nil {
					t.Fatalf("unable to read testdata: %v", err)
				}
				diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
					A:        difflib.SplitLines(string(expected)),
					B:        difflib.SplitLines(got),
					FromFile: "Expected",
					ToFile:   "Got",
					Context:  1,
				})
				if err != nil {
					t.Fatalf("diff error: %v", err)
				}
				if diff != "" {
					t.Errorf("unexpected diff:\n%s\n===== [RAW OUTPUT] =====\n%s", diff, got)
				}
			})
		}
	}
}
<|endoftext|>


<commit_before>package golang

import (
	"fmt"
	"io"
	"net"

	"os"

	"time"

	"strings"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/ledgerchain/types"
	"github.com/ncodes/cocoon/core/stubs/golang/config"
	"github.com/ncodes/cocoon/core/stubs/golang/proto"
	"github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	"google.golang.org/grpc"
)

var serverPort = util.Env("COCOON_CODE_PORT", "8000")
var defaultServer *stubServer
var log *logging.Logger
var serverDone chan bool

const (
	// GlobalLedger represents the name of the global ledger
	GlobalLedger = "global"

	// TxCreateLedger represents a message to create a ledger
	TxCreateLedger = "CREATE_LEDGER"

	// TxPut represents a message to create a transaction
	TxPut = "PUT"
)

// The default ledger is the global ledger.
var (
	defaultLedger = util.Sha256(GlobalLedger)

	// txChannels holds the channels to send transaction responses to
	txRespChannels = cmap.New()

	// ErrOperationTimeout represents a timeout error that occurs when response
	// is not received from orderer in time.
	ErrOperationTimeout = fmt.Errorf("operation timed out")

	// ErrNotConnected represents an error about the cocoon code not
	// having an active connection with the connector.
	ErrNotConnected = fmt.Errorf("not connected to the connector")

	// Flag to help tell whether cocoon code is running
	running = false

	// The cocoon code currently running
	ccode CocoonCode
)

func init() {
	defaultServer = new(stubServer)
	config.ConfigureLogger()
	log = logging.MustGetLogger("ccode.stub")
}

// StubServer defines the services of the stub's GRPC connection
type stubServer struct {
	port   int
	stream proto.Stub_TransactServer
}

// stripRPCErrorPrefix takes an error return from the RPC client and removes the
// prefixed `rpc error: code = 2 desc =` statement
func stripRPCErrorPrefix(err []byte) []byte {
	return []byte(strings.TrimSpace(strings.Replace(string(err), "rpc error: code = 2 desc =", "", -1)))
}

// Transact listens and process invoke and response transactions from
// the connector.
func (s *stubServer) Transact(stream proto.Stub_TransactServer) error {
	s.stream = stream
	for {

		in, err := stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			return fmt.Errorf("failed to read message from connector. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from connector", in.GetId())
				if err = s.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from connector", in.GetId())
			go func() {
				if err = s.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (s *stubServer) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case "function":
		if !running {
			return fmt.Errorf("cocoon code is not running. Did you call the Run() method?")
		}

		functionName := tx.GetParams()[0]
		result, err := ccode.Invoke(tx.GetId(), functionName, tx.GetParams()[1:])
		if err != nil {
			return err
		}

		// coerce result to json
		resultJSON, err := util.ToJSON(result)
		if err != nil {
			return fmt.Errorf("failed to coerce cocoon code Invoke() result to json string. %s", err)
		}

		return s.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   200,
			Body:     resultJSON,
		})

	default:
		return fmt.Errorf("Unsupported invoke transaction (%s)", tx.GetName())
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (s *stubServer) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// Run starts the stub server, takes a cocoon code and attempts to initialize it..
func Run(cc CocoonCode) {

	if running {
		log.Info("cocoon code is already running")
		return
	}

	serverDone = make(chan bool, 1)

	lis, err := net.Listen("tcp", fmt.Sprintf(":%s", serverPort))
	if err != nil {
		log.Fatalf("failed to listen on port=%s", serverPort)
	}

	log.Infof("Started stub service at port=%s", serverPort)
	server := grpc.NewServer()
	proto.RegisterStubServer(server, defaultServer)
	go server.Serve(lis)

	if err = cc.Init(); err != nil {
		log.Errorf("cocoode Init() returned error: %s", err)
		Stop(1)
	}

	running = true
	ccode = cc

	<-serverDone
	log.Info("Cocoon code stopped")
	os.Exit(0)
}

// GetLogger returns the stubs logger.
func GetLogger() *logging.Logger {
	return log
}

// sendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func sendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := defaultServer.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		log.Errorf("failed to send transaction [%s] to connector. %s", tx.GetId(), err)
		return err
	}
	log.Debugf("Successfully sent transaction [%s] to connector", tx.GetId())
	return nil
}

// Stop stub and cocoon code
func Stop(exitCode int) {
	defaultServer.stream = nil
	serverDone <- true
	log.Info("Cocoon code exiting with exit code %d", exitCode)
	os.Exit(exitCode)
}

// AwaitTxChan takes a response channel and waits to receive a response
// from it. If no error occurs, it returns the response. It
// returns ErrOperationTimeout if it waited 5 minutes and got no response.
func AwaitTxChan(ch chan *proto.Tx) (*proto.Tx, error) {
	for {
		select {
		case r := <-ch:
			return r, nil
		case <-time.After(5 * time.Minute):
			return nil, ErrOperationTimeout
		}
	}
}

// isConnected checks if connection with the connector
// is active.
func isConnected() bool {
	return defaultServer.stream != nil
}

// CreateLedger creates a new ledger by sending an
// invoke transaction (TxCreateLedger) to the connector.
// The final name of the ledger is a sha256 hash of
// the cocoon code id and the name (e.g SHA256(ccode_id.name))
func CreateLedger(name string, public bool) (*types.Ledger, error) {

	// TODO: prevent use of ledger name with punctuations (execept an underscore)

	if !isConnected() {
		return nil, ErrNotConnected
	}

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   TxCreateLedger,
		Params: []string{name, fmt.Sprintf("%t", public)},
	}, respCh)
	if err != nil {
		return nil, fmt.Errorf("failed to create ledger. %s", err)
	}

	resp, err := AwaitTxChan(respCh)
	if err != nil {
		return nil, err
	}
	if resp.Status != 200 {
		return nil, fmt.Errorf("%s", stripRPCErrorPrefix(resp.Body))
	}

	var ledger types.Ledger
	if err = util.FromJSON(resp.Body, &ledger); err != nil {
		return nil, fmt.Errorf("failed to unmarshall response data")
	}

	return &ledger, nil
}

// SetDefault ledger
func SetDefault(name string) {
	defaultLedger = name
}

// GetDefaultLedger returns the name of the default ledger.
func GetDefaultLedger() string {
	return defaultLedger
}

// PutIn adds a new transaction to a ledger
func PutIn(ledgerName string, key string, value []byte) (*types.Transaction, error) {

	if !isConnected() {
		return nil, ErrNotConnected
	}

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   TxPut,
		Params: []string{GetDefaultLedger(), txID, key, string(value)},
	}, respCh)
	if err != nil {
		return nil, fmt.Errorf("failed to put transaction. %s", err)
	}

	resp, err := AwaitTxChan(respCh)
	if err != nil {
		return nil, err
	}
	if resp.Status != 200 {
		return nil, fmt.Errorf("%s", stripRPCErrorPrefix(resp.Body))
	}

	var tx types.Transaction
	if err = util.FromJSON(resp.Body, &tx); err != nil {
		return nil, fmt.Errorf("failed to unmarshall response data")
	}

	return &tx, nil
}

// Put adds a new transaction into the default ledger
func Put(key string, value []byte) (*types.Transaction, error) {
	return PutIn(GetDefaultLedger(), key, value)
}
<commit_msg>Uses the ledger name provided in the arguments<commit_after>package golang

import (
	"fmt"
	"io"
	"net"

	"os"

	"time"

	"strings"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/ledgerchain/types"
	"github.com/ncodes/cocoon/core/stubs/golang/config"
	"github.com/ncodes/cocoon/core/stubs/golang/proto"
	"github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	"google.golang.org/grpc"
)

var serverPort = util.Env("COCOON_CODE_PORT", "8000")
var defaultServer *stubServer
var log *logging.Logger
var serverDone chan bool

const (
	// GlobalLedger represents the name of the global ledger
	GlobalLedger = "global"

	// TxCreateLedger represents a message to create a ledger
	TxCreateLedger = "CREATE_LEDGER"

	// TxPut represents a message to create a transaction
	TxPut = "PUT"
)

// The default ledger is the global ledger.
var (
	defaultLedger = util.Sha256(GlobalLedger)

	// txChannels holds the channels to send transaction responses to
	txRespChannels = cmap.New()

	// ErrOperationTimeout represents a timeout error that occurs when response
	// is not received from orderer in time.
	ErrOperationTimeout = fmt.Errorf("operation timed out")

	// ErrNotConnected represents an error about the cocoon code not
	// having an active connection with the connector.
	ErrNotConnected = fmt.Errorf("not connected to the connector")

	// Flag to help tell whether cocoon code is running
	running = false

	// The cocoon code currently running
	ccode CocoonCode
)

func init() {
	defaultServer = new(stubServer)
	config.ConfigureLogger()
	log = logging.MustGetLogger("ccode.stub")
}

// StubServer defines the services of the stub's GRPC connection
type stubServer struct {
	port   int
	stream proto.Stub_TransactServer
}

// stripRPCErrorPrefix takes an error return from the RPC client and removes the
// prefixed `rpc error: code = 2 desc =` statement
func stripRPCErrorPrefix(err []byte) []byte {
	return []byte(strings.TrimSpace(strings.Replace(string(err), "rpc error: code = 2 desc =", "", -1)))
}

// Transact listens and process invoke and response transactions from
// the connector.
func (s *stubServer) Transact(stream proto.Stub_TransactServer) error {
	s.stream = stream
	for {

		in, err := stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			return fmt.Errorf("failed to read message from connector. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from connector", in.GetId())
				if err = s.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from connector", in.GetId())
			go func() {
				if err = s.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (s *stubServer) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case "function":
		if !running {
			return fmt.Errorf("cocoon code is not running. Did you call the Run() method?")
		}

		functionName := tx.GetParams()[0]
		result, err := ccode.Invoke(tx.GetId(), functionName, tx.GetParams()[1:])
		if err != nil {
			return err
		}

		// coerce result to json
		resultJSON, err := util.ToJSON(result)
		if err != nil {
			return fmt.Errorf("failed to coerce cocoon code Invoke() result to json string. %s", err)
		}

		return s.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   200,
			Body:     resultJSON,
		})

	default:
		return fmt.Errorf("Unsupported invoke transaction (%s)", tx.GetName())
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (s *stubServer) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// Run starts the stub server, takes a cocoon code and attempts to initialize it..
func Run(cc CocoonCode) {

	if running {
		log.Info("cocoon code is already running")
		return
	}

	serverDone = make(chan bool, 1)

	lis, err := net.Listen("tcp", fmt.Sprintf(":%s", serverPort))
	if err != nil {
		log.Fatalf("failed to listen on port=%s", serverPort)
	}

	log.Infof("Started stub service at port=%s", serverPort)
	server := grpc.NewServer()
	proto.RegisterStubServer(server, defaultServer)
	go server.Serve(lis)

	if err = cc.Init(); err != nil {
		log.Errorf("cocoode Init() returned error: %s", err)
		Stop(1)
	}

	running = true
	ccode = cc

	<-serverDone
	log.Info("Cocoon code stopped")
	os.Exit(0)
}

// GetLogger returns the stubs logger.
func GetLogger() *logging.Logger {
	return log
}

// sendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func sendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := defaultServer.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		log.Errorf("failed to send transaction [%s] to connector. %s", tx.GetId(), err)
		return err
	}
	log.Debugf("Successfully sent transaction [%s] to connector", tx.GetId())
	return nil
}

// Stop stub and cocoon code
func Stop(exitCode int) {
	defaultServer.stream = nil
	serverDone <- true
	log.Info("Cocoon code exiting with exit code %d", exitCode)
	os.Exit(exitCode)
}

// AwaitTxChan takes a response channel and waits to receive a response
// from it. If no error occurs, it returns the response. It
// returns ErrOperationTimeout if it waited 5 minutes and got no response.
func AwaitTxChan(ch chan *proto.Tx) (*proto.Tx, error) {
	for {
		select {
		case r := <-ch:
			return r, nil
		case <-time.After(5 * time.Minute):
			return nil, ErrOperationTimeout
		}
	}
}

// isConnected checks if connection with the connector
// is active.
func isConnected() bool {
	return defaultServer.stream != nil
}

// CreateLedger creates a new ledger by sending an
// invoke transaction (TxCreateLedger) to the connector.
// The final name of the ledger is a sha256 hash of
// the cocoon code id and the name (e.g SHA256(ccode_id.name))
func CreateLedger(name string, public bool) (*types.Ledger, error) {

	// TODO: prevent use of ledger name with punctuations (execept an underscore)

	if !isConnected() {
		return nil, ErrNotConnected
	}

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   TxCreateLedger,
		Params: []string{name, fmt.Sprintf("%t", public)},
	}, respCh)
	if err != nil {
		return nil, fmt.Errorf("failed to create ledger. %s", err)
	}

	resp, err := AwaitTxChan(respCh)
	if err != nil {
		return nil, err
	}
	if resp.Status != 200 {
		return nil, fmt.Errorf("%s", stripRPCErrorPrefix(resp.Body))
	}

	var ledger types.Ledger
	if err = util.FromJSON(resp.Body, &ledger); err != nil {
		return nil, fmt.Errorf("failed to unmarshall response data")
	}

	return &ledger, nil
}

// SetDefault ledger
func SetDefault(name string) {
	defaultLedger = name
}

// GetDefaultLedger returns the name of the default ledger.
func GetDefaultLedger() string {
	return defaultLedger
}

// PutIn adds a new transaction to a ledger
func PutIn(ledgerName string, key string, value []byte) (*types.Transaction, error) {

	if !isConnected() {
		return nil, ErrNotConnected
	}

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   TxPut,
		Params: []string{ledgerName, txID, key, string(value)},
	}, respCh)
	if err != nil {
		return nil, fmt.Errorf("failed to put transaction. %s", err)
	}

	resp, err := AwaitTxChan(respCh)
	if err != nil {
		return nil, err
	}
	if resp.Status != 200 {
		return nil, fmt.Errorf("%s", stripRPCErrorPrefix(resp.Body))
	}

	var tx types.Transaction
	if err = util.FromJSON(resp.Body, &tx); err != nil {
		return nil, fmt.Errorf("failed to unmarshall response data")
	}

	return &tx, nil
}

// Put adds a new transaction into the default ledger
func Put(key string, value []byte) (*types.Transaction, error) {
	return PutIn(GetDefaultLedger(), key, value)
}
<|endoftext|>


<commit_before>package core

import (
	"testing"

	"github.com/af83/edwig/model"
)

func Test_Subscription_Id(t *testing.T) {
	subscription := Subscription{
		id: "6ba7b814-9dad-11d1-0-00c04fd430c8",
	}

	if subscription.Id() != "6ba7b814-9dad-11d1-0-00c04fd430c8" {
		t.Errorf("subscription.Id() returns wrong value, got: %s, required: %s", subscription.Id(), "6ba7b814-9dad-11d1-0-00c04fd430c8")
	}
}

func Test_subscription_MarshalJSON(t *testing.T) {
	subscription := NewSubscription()
	subscription.id = "6ba7b814-9dad-11d1-0-00c04fd430c8"
	subscription.kind = "salut"
	subscription.CreateAddNewResource(*model.NewReference(model.NewObjectID("test", "value")))

	expected := `{"Id":"6ba7b814-9dad-11d1-0-00c04fd430c8","Kind":"salut","Resources":[{"Reference":{"ObjectId":{"test":"value"}},"RetryCount":0,"SubscribedAt":"0001-01-01T00:00:00Z","SubscribedUntil":"1984-04-04T00:01:00Z"}]}`
	jsonBytes, err := subscription.MarshalJSON()
	if err != nil {
		t.Fatal(err)
	}

	jsonString := string(jsonBytes)
	if jsonString != expected {
		t.Errorf("subscription.MarshalJSON() returns wrong json:\n got: %s\n want: %s", jsonString, expected)
	}
}

func Test_MemorySubscription_New(t *testing.T) {
	subcriptions := NewMemorySubscriptions(NewPartner())

	subcription := subcriptions.New()
	if subcription.Id() != "6ba7b814-9dad-11d1-7-00c04fd430c8" {
		t.Errorf("New subcription identifier should be an empty string, got: %s", subcription.Id())
	}
}

func Test_MemorySubscriptions_Find_NotFound(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())
	_, ok := subscriptions.Find("6ba7b814-9dad-11d1-0-00c04fd430c8")
	if ok {
		t.Errorf("Find should return false when Subscription isn't found")
	}
}

func Test_MemorySubscriptions_Find(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())

	existingSubscription := subscriptions.New()
	subscriptions.Save(&existingSubscription)

	subscriptionId := existingSubscription.Id()

	subscription, ok := subscriptions.Find(subscriptionId)
	if !ok {
		t.Errorf("Find should return true when subscription is found")
	}
	if subscription.Id() != subscriptionId {
		t.Errorf("Find should return a subscription with the given Id")
	}
}

func Test_MemorySubscriptions_FindAll(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())

	for i := 0; i < 5; i++ {
		existingSubscription := subscriptions.New()
		subscriptions.Save(&existingSubscription)
	}

	foundSubscriptions := subscriptions.FindAll()

	if len(foundSubscriptions) != 5 {
		t.Errorf("FindAll should return all subscriptions")
	}
}

func Test_MemorySubscriptions_Delete(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())
	existingSubscription := subscriptions.New()
	subscriptions.Save(&existingSubscription)

	subscriptions.Delete(&existingSubscription)

	_, ok := subscriptions.Find(existingSubscription.Id())
	if ok {
		t.Errorf("Deleted subscription should not be findable")
	}
}
<commit_msg>Small modification, just testing if the id is not empty instead an exact match with another id.<commit_after>package core

import (
	"testing"

	"github.com/af83/edwig/model"
)

func Test_Subscription_Id(t *testing.T) {
	subscription := Subscription{
		id: "6ba7b814-9dad-11d1-0-00c04fd430c8",
	}

	if subscription.Id() != "6ba7b814-9dad-11d1-0-00c04fd430c8" {
		t.Errorf("subscription.Id() returns wrong value, got: %s, required: %s", subscription.Id(), "6ba7b814-9dad-11d1-0-00c04fd430c8")
	}
}

func Test_subscription_MarshalJSON(t *testing.T) {
	subscription := NewSubscription()
	subscription.id = "6ba7b814-9dad-11d1-0-00c04fd430c8"
	subscription.kind = "salut"
	subscription.CreateAddNewResource(*model.NewReference(model.NewObjectID("test", "value")))

	expected := `{"Id":"6ba7b814-9dad-11d1-0-00c04fd430c8","Kind":"salut","Resources":[{"Reference":{"ObjectId":{"test":"value"}},"RetryCount":0,"SubscribedAt":"0001-01-01T00:00:00Z","SubscribedUntil":"1984-04-04T00:01:00Z"}]}`
	jsonBytes, err := subscription.MarshalJSON()
	if err != nil {
		t.Fatal(err)
	}

	jsonString := string(jsonBytes)
	if jsonString != expected {
		t.Errorf("subscription.MarshalJSON() returns wrong json:\n got: %s\n want: %s", jsonString, expected)
	}
}

func Test_MemorySubscription_New(t *testing.T) {
	subcriptions := NewMemorySubscriptions(NewPartner())

	subcription := subcriptions.New()
	if subcription.Id() == "" {
		t.Errorf("New subcription identifier should be an empty string, got: %s", subcription.Id())
	}
}

func Test_MemorySubscriptions_Find_NotFound(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())
	_, ok := subscriptions.Find("6ba7b814-9dad-11d1-0-00c04fd430c8")
	if ok {
		t.Errorf("Find should return false when Subscription isn't found")
	}
}

func Test_MemorySubscriptions_Find(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())

	existingSubscription := subscriptions.New()
	subscriptions.Save(&existingSubscription)

	subscriptionId := existingSubscription.Id()

	subscription, ok := subscriptions.Find(subscriptionId)
	if !ok {
		t.Errorf("Find should return true when subscription is found")
	}
	if subscription.Id() != subscriptionId {
		t.Errorf("Find should return a subscription with the given Id")
	}
}

func Test_MemorySubscriptions_FindAll(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())

	for i := 0; i < 5; i++ {
		existingSubscription := subscriptions.New()
		subscriptions.Save(&existingSubscription)
	}

	foundSubscriptions := subscriptions.FindAll()

	if len(foundSubscriptions) != 5 {
		t.Errorf("FindAll should return all subscriptions")
	}
}

func Test_MemorySubscriptions_Delete(t *testing.T) {
	subscriptions := NewMemorySubscriptions(NewPartner())
	existingSubscription := subscriptions.New()
	subscriptions.Save(&existingSubscription)

	subscriptions.Delete(&existingSubscription)

	_, ok := subscriptions.Find(existingSubscription.Id())
	if ok {
		t.Errorf("Deleted subscription should not be findable")
	}
}
<|endoftext|>


<commit_before>package countries

import (
	"log"
)

func CountryRegionGroup(country, region string) string {

	if country != "us" {
		return ""
	}

	regions := map[string]string{
		"us-ak": "us-west",
		"us-az": "us-west",
		"us-ca": "us-west",
		"us-co": "us-west",
		"us-hi": "us-west",
		"us-id": "us-west",
		"us-mt": "us-west",
		"us-nm": "us-west",
		"us-nv": "us-west",
		"us-or": "us-west",
		"us-ut": "us-west",
		"us-wa": "us-west",
		"us-wy": "us-west",

		"us-ar": "us-central",
		"us-ia": "us-central",
		"us-in": "us-central",
		"us-ks": "us-central",
		"us-la": "us-central",
		"us-mn": "us-central",
		"us-mo": "us-central",
		"us-nd": "us-central",
		"us-ne": "us-central",
		"us-ok": "us-central",
		"us-sd": "us-central",
		"us-tx": "us-central",
		"us-wi": "us-central",

		"us-al": "us-east",
		"us-ct": "us-east",
		"us-dc": "us-east",
		"us-de": "us-east",
		"us-fl": "us-east",
		"us-ga": "us-east",
		"us-ky": "us-east",
		"us-ma": "us-east",
		"us-md": "us-east",
		"us-me": "us-east",
		"us-mi": "us-east",
		"us-ms": "us-east",
		"us-nc": "us-east",
		"us-nh": "us-east",
		"us-nj": "us-east",
		"us-ny": "us-east",
		"us-oh": "us-east",
		"us-pa": "us-east",
		"us-ri": "us-east",
		"us-sc": "us-east",
		"us-tn": "us-east",
		"us-va": "us-east",
		"us-vt": "us-east",
		"us-wv": "us-east",
	}

	if group, ok := regions[region]; ok {
		return group
	}

	log.Printf("Did not find a region group for '%s'/'%s'", country, region)
	return ""
}
<commit_msg>Add illinois to region groups<commit_after>package countries

import (
	"log"
)

func CountryRegionGroup(country, region string) string {

	if country != "us" {
		return ""
	}

	regions := map[string]string{
		"us-ak": "us-west",
		"us-az": "us-west",
		"us-ca": "us-west",
		"us-co": "us-west",
		"us-hi": "us-west",
		"us-id": "us-west",
		"us-mt": "us-west",
		"us-nm": "us-west",
		"us-nv": "us-west",
		"us-or": "us-west",
		"us-ut": "us-west",
		"us-wa": "us-west",
		"us-wy": "us-west",

		"us-ar": "us-central",
		"us-ia": "us-central",
		"us-il": "us-central",
		"us-in": "us-central",
		"us-ks": "us-central",
		"us-la": "us-central",
		"us-mn": "us-central",
		"us-mo": "us-central",
		"us-nd": "us-central",
		"us-ne": "us-central",
		"us-ok": "us-central",
		"us-sd": "us-central",
		"us-tx": "us-central",
		"us-wi": "us-central",

		"us-al": "us-east",
		"us-ct": "us-east",
		"us-dc": "us-east",
		"us-de": "us-east",
		"us-fl": "us-east",
		"us-ga": "us-east",
		"us-ky": "us-east",
		"us-ma": "us-east",
		"us-md": "us-east",
		"us-me": "us-east",
		"us-mi": "us-east",
		"us-ms": "us-east",
		"us-nc": "us-east",
		"us-nh": "us-east",
		"us-nj": "us-east",
		"us-ny": "us-east",
		"us-oh": "us-east",
		"us-pa": "us-east",
		"us-ri": "us-east",
		"us-sc": "us-east",
		"us-tn": "us-east",
		"us-va": "us-east",
		"us-vt": "us-east",
		"us-wv": "us-east",
	}

	if group, ok := regions[region]; ok {
		return group
	}

	log.Printf("Did not find a region group for '%s'/'%s'", country, region)
	return ""
}
<|endoftext|>


<commit_before>package images

import (
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/pagination"
)

// Image model
// Does not include the literal image data; just metadata.
// returned by listing images, and by fetching a specific image.
type Image struct {
	// ID is the image UUID
	ID string `json:"id"`

	// Name is the human-readable display name for the image.
	Name string `json:"name"`

	// Status is the image status. It can be "queued" or "active"
	// See imageservice/v2/images/type.go
	Status ImageStatus `json:"status"`

	// Tags is a list of image tags. Tags are arbitrarily defined strings
	// attached to an image.
	Tags []string `json:"tags"`

	// ContainerFormat is the format of the container.
	// Valid values are ami, ari, aki, bare, and ovf.
	ContainerFormat string `json:"container_format"`

	// DiskFormat is the format of the disk.
	// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso.
	DiskFormat string `json:"disk_format"`

	// MinDiskGigabytes is the amount of disk space in GB that is required to boot the image.
	MinDiskGigabytes int `json:"min_disk"`

	// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to boot the image.
	MinRAMMegabytes int `json:"min_ram"`

	// Owner is the tenant the image belongs to.
	Owner string `json:"owner"`

	// Protected is whether the image is deletable or not.
	Protected bool `json:"protected"`

	// Visibility defines who can see/use the image.
	Visibility ImageVisibility `json:"visibility"`

	// Checksum is the checksum of the data that's associated with the image
	Checksum string `json:"checksum"`

	// SizeBytes is the size of the data that's associated with the image.
	SizeBytes int64 `json:"size"`

	// Metadata is a set of metadata associated with the image.
	// Image metadata allow for meaningfully define the image properties
	// and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
	Metadata map[string]string `json:"metadata"`

	// Properties is a set of key-value pairs, if any, that are associated with the image.
	Properties map[string]string `json:"properties"`

	// CreatedAt is the date when the image has been created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is the date when the last change has been made to the image or it's properties.
	UpdatedAt time.Time `json:"updated_at"`

	// File is the trailing path after the glance endpoint that represent the location
	// of the image or the path to retrieve it.
	File string `json:"file"`

	// Schema is the path to the JSON-schema that represent the image or image entity.
	Schema string `json:"schema"`
}

func (r *Image) UnmarshalJSON(b []byte) error {
	type tmp Image
	var s struct {
		tmp
		SizeBytes interface{} `json:"size"`
	}
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}
	*r = Image(s.tmp)

	switch t := s.SizeBytes.(type) {
	case nil:
		return nil
	case float32:
		r.SizeBytes = int64(t)
	case float64:
		r.SizeBytes = int64(t)
	default:
		return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t)
	}

	return err
}

type commonResult struct {
	gophercloud.Result
}

// Extract interprets any commonResult as an Image.
func (r commonResult) Extract() (*Image, error) {
	var s *Image
	err := r.ExtractInto(&s)
	return s, err
}

// CreateResult represents the result of a Create operation
type CreateResult struct {
	commonResult
}

// UpdateResult represents the result of an Update operation
type UpdateResult struct {
	commonResult
}

// GetResult represents the result of a Get operation
type GetResult struct {
	commonResult
}

//DeleteResult model
type DeleteResult struct {
	gophercloud.ErrResult
}

// ImagePage represents page
type ImagePage struct {
	pagination.LinkedPageBase
}

// IsEmpty returns true if a page contains no Images results.
func (r ImagePage) IsEmpty() (bool, error) {
	images, err := ExtractImages(r)
	return len(images) == 0, err
}

// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (r ImagePage) NextPageURL() (string, error) {
	var s struct {
		Next string `json:"next"`
	}
	err := r.ExtractInto(&s)
	if err != nil {
		return "", err
	}
	return nextPageURL(r.URL.String(), s.Next), nil
}

// ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities.
func ExtractImages(r pagination.Page) ([]Image, error) {
	var s struct {
		Images []Image `json:"images"`
	}
	err := (r.(ImagePage)).ExtractInto(&s)
	return s.Images, err
}
<commit_msg>ImageService v2: Fix paging when next URL is blank (#254)<commit_after>package images

import (
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/pagination"
)

// Image model
// Does not include the literal image data; just metadata.
// returned by listing images, and by fetching a specific image.
type Image struct {
	// ID is the image UUID
	ID string `json:"id"`

	// Name is the human-readable display name for the image.
	Name string `json:"name"`

	// Status is the image status. It can be "queued" or "active"
	// See imageservice/v2/images/type.go
	Status ImageStatus `json:"status"`

	// Tags is a list of image tags. Tags are arbitrarily defined strings
	// attached to an image.
	Tags []string `json:"tags"`

	// ContainerFormat is the format of the container.
	// Valid values are ami, ari, aki, bare, and ovf.
	ContainerFormat string `json:"container_format"`

	// DiskFormat is the format of the disk.
	// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso.
	DiskFormat string `json:"disk_format"`

	// MinDiskGigabytes is the amount of disk space in GB that is required to boot the image.
	MinDiskGigabytes int `json:"min_disk"`

	// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to boot the image.
	MinRAMMegabytes int `json:"min_ram"`

	// Owner is the tenant the image belongs to.
	Owner string `json:"owner"`

	// Protected is whether the image is deletable or not.
	Protected bool `json:"protected"`

	// Visibility defines who can see/use the image.
	Visibility ImageVisibility `json:"visibility"`

	// Checksum is the checksum of the data that's associated with the image
	Checksum string `json:"checksum"`

	// SizeBytes is the size of the data that's associated with the image.
	SizeBytes int64 `json:"size"`

	// Metadata is a set of metadata associated with the image.
	// Image metadata allow for meaningfully define the image properties
	// and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
	Metadata map[string]string `json:"metadata"`

	// Properties is a set of key-value pairs, if any, that are associated with the image.
	Properties map[string]string `json:"properties"`

	// CreatedAt is the date when the image has been created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is the date when the last change has been made to the image or it's properties.
	UpdatedAt time.Time `json:"updated_at"`

	// File is the trailing path after the glance endpoint that represent the location
	// of the image or the path to retrieve it.
	File string `json:"file"`

	// Schema is the path to the JSON-schema that represent the image or image entity.
	Schema string `json:"schema"`
}

func (r *Image) UnmarshalJSON(b []byte) error {
	type tmp Image
	var s struct {
		tmp
		SizeBytes interface{} `json:"size"`
	}
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}
	*r = Image(s.tmp)

	switch t := s.SizeBytes.(type) {
	case nil:
		return nil
	case float32:
		r.SizeBytes = int64(t)
	case float64:
		r.SizeBytes = int64(t)
	default:
		return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t)
	}

	return err
}

type commonResult struct {
	gophercloud.Result
}

// Extract interprets any commonResult as an Image.
func (r commonResult) Extract() (*Image, error) {
	var s *Image
	err := r.ExtractInto(&s)
	return s, err
}

// CreateResult represents the result of a Create operation
type CreateResult struct {
	commonResult
}

// UpdateResult represents the result of an Update operation
type UpdateResult struct {
	commonResult
}

// GetResult represents the result of a Get operation
type GetResult struct {
	commonResult
}

//DeleteResult model
type DeleteResult struct {
	gophercloud.ErrResult
}

// ImagePage represents page
type ImagePage struct {
	pagination.LinkedPageBase
}

// IsEmpty returns true if a page contains no Images results.
func (r ImagePage) IsEmpty() (bool, error) {
	images, err := ExtractImages(r)
	return len(images) == 0, err
}

// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
func (r ImagePage) NextPageURL() (string, error) {
	var s struct {
		Next string `json:"next"`
	}
	err := r.ExtractInto(&s)
	if err != nil {
		return "", err
	}

	if s.Next == "" {
		return "", nil
	}

	return nextPageURL(r.URL.String(), s.Next), nil
}

// ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities.
func ExtractImages(r pagination.Page) ([]Image, error) {
	var s struct {
		Images []Image `json:"images"`
	}
	err := (r.(ImagePage)).ExtractInto(&s)
	return s.Images, err
}
<|endoftext|>


<commit_before>package subnets

import (
	"fmt"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// AdminState gives users a solid type to work with for create and update
// operations. It is recommended that users use the `Up` and `Down` enums.
type AdminState *bool

// Convenience vars for AdminStateUp values.
var (
	iTrue  = true
	iFalse = false

	Up   AdminState = &iTrue
	Down AdminState = &iFalse
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToSubnetListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the subnet attributes you want to see returned. SortKey allows you to sort
// by a particular subnet attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
	Name       string `q:"name"`
	EnableDHCP *bool  `q:"enable_dhcp"`
	NetworkID  string `q:"network_id"`
	TenantID   string `q:"tenant_id"`
	IPVersion  int    `q:"ip_version"`
	GatewayIP  string `q:"gateway_ip"`
	CIDR       string `q:"cidr"`
	ID         string `q:"id"`
	Limit      int    `q:"limit"`
	Marker     string `q:"marker"`
	SortKey    string `q:"sort_key"`
	SortDir    string `q:"sort_dir"`
}

// ToSubnetListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSubnetListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// subnets. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those subnets that are owned by the tenant
// who submits the request, unless the request is submitted by a user with
// administrative rights.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToSubnetListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
		return SubnetPage{pagination.LinkedPageBase{PageResult: r}}
	})
}

// Get retrieves a specific subnet based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
	var res GetResult
	_, res.Err = c.Get(getURL(c, id), &res.Body, nil)
	return res
}

// Valid IP types
const (
	IPv4 = 4
	IPv6 = 6
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToSubnetCreateMap() (map[string]interface{}, error)
}

// CreateOpts represents the attributes used when creating a new subnet.
type CreateOpts struct {
	// Required
	NetworkID string
	CIDR      string
	// Optional
	Name            string
	TenantID        string
	AllocationPools []AllocationPool
	GatewayIP       string
	IPVersion       int
	EnableDHCP      *bool
	DNSNameservers  []string
	HostRoutes      []HostRoute
}

// ToSubnetCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.NetworkID == "" {
		return nil, errNetworkIDRequired
	}
	if opts.CIDR == "" {
		return nil, errCIDRRequired
	}
	if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
		return nil, errInvalidIPType
	}

	s["network_id"] = opts.NetworkID
	s["cidr"] = opts.CIDR

	if opts.EnableDHCP != nil {
		s["enable_dhcp"] = &opts.EnableDHCP
	}
	if opts.Name != "" {
		s["name"] = opts.Name
	}
	if opts.GatewayIP != "" {
		s["gateway_ip"] = opts.GatewayIP
	}
	if opts.TenantID != "" {
		s["tenant_id"] = opts.TenantID
	}
	if opts.IPVersion != 0 {
		s["ip_version"] = opts.IPVersion
	}
	if len(opts.AllocationPools) != 0 {
		s["allocation_pools"] = opts.AllocationPools
	}
	if len(opts.DNSNameservers) != 0 {
		s["dns_nameservers"] = opts.DNSNameservers
	}
	if len(opts.HostRoutes) != 0 {
		s["host_routes"] = opts.HostRoutes
	}

	return map[string]interface{}{"subnet": s}, nil
}

// Create accepts a CreateOpts struct and creates a new subnet using the values
// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToSubnetCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Post(createURL(c), reqBody, &res.Body, nil)
	return res
}

// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
	ToSubnetUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts represents the attributes used when updating an existing subnet.
type UpdateOpts struct {
	Name           string
	GatewayIP      string
	DNSNameservers []string
	HostRoutes     []HostRoute
	EnableDHCP     *bool
}

// ToSubnetUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.EnableDHCP != nil {
		s["enable_dhcp"] = &opts.EnableDHCP
	}
	if opts.Name != "" {
		s["name"] = opts.Name
	}
	if opts.GatewayIP != "" {
		s["gateway_ip"] = opts.GatewayIP
	}
	if len(opts.DNSNameservers) != 0 {
		s["dns_nameservers"] = opts.DNSNameservers
	}
	if len(opts.HostRoutes) != 0 {
		s["host_routes"] = opts.HostRoutes
	}

	return map[string]interface{}{"subnet": s}, nil
}

// Update accepts a UpdateOpts struct and updates an existing subnet using the
// values provided.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToSubnetUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Put(updateURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
		OkCodes: []int{200, 201},
	})

	return res
}

// Delete accepts a unique ID and deletes the subnet associated with it.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
	var res DeleteResult
	_, res.Err = c.Delete(deleteURL(c, id), nil)
	return res
}

// IDFromName is a convienience function that returns a subnet's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
	subnetCount := 0
	subnetID := ""
	if name == "" {
		return "", fmt.Errorf("A subnet name must be provided.")
	}
	pager := List(client, nil)
	pager.EachPage(func(page pagination.Page) (bool, error) {
		subnetList, err := ExtractSubnets(page)
		if err != nil {
			return false, err
		}

		for _, s := range subnetList {
			if s.Name == name {
				subnetCount++
				subnetID = s.ID
			}
		}
		return true, nil
	})

	switch subnetCount {
	case 0:
		return "", fmt.Errorf("Unable to find subnet: %s", name)
	case 1:
		return subnetID, nil
	default:
		return "", fmt.Errorf("Found %d subnets matching %s", subnetCount, name)
	}
}
<commit_msg>fix typo in comment<commit_after>package subnets

import (
	"fmt"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// AdminState gives users a solid type to work with for create and update
// operations. It is recommended that users use the `Up` and `Down` enums.
type AdminState *bool

// Convenience vars for AdminStateUp values.
var (
	iTrue  = true
	iFalse = false

	Up   AdminState = &iTrue
	Down AdminState = &iFalse
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToSubnetListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the subnet attributes you want to see returned. SortKey allows you to sort
// by a particular subnet attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
	Name       string `q:"name"`
	EnableDHCP *bool  `q:"enable_dhcp"`
	NetworkID  string `q:"network_id"`
	TenantID   string `q:"tenant_id"`
	IPVersion  int    `q:"ip_version"`
	GatewayIP  string `q:"gateway_ip"`
	CIDR       string `q:"cidr"`
	ID         string `q:"id"`
	Limit      int    `q:"limit"`
	Marker     string `q:"marker"`
	SortKey    string `q:"sort_key"`
	SortDir    string `q:"sort_dir"`
}

// ToSubnetListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToSubnetListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// subnets. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those subnets that are owned by the tenant
// who submits the request, unless the request is submitted by a user with
// administrative rights.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToSubnetListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
		return SubnetPage{pagination.LinkedPageBase{PageResult: r}}
	})
}

// Get retrieves a specific subnet based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
	var res GetResult
	_, res.Err = c.Get(getURL(c, id), &res.Body, nil)
	return res
}

// Valid IP types
const (
	IPv4 = 4
	IPv6 = 6
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToSubnetCreateMap() (map[string]interface{}, error)
}

// CreateOpts represents the attributes used when creating a new subnet.
type CreateOpts struct {
	// Required
	NetworkID string
	CIDR      string
	// Optional
	Name            string
	TenantID        string
	AllocationPools []AllocationPool
	GatewayIP       string
	IPVersion       int
	EnableDHCP      *bool
	DNSNameservers  []string
	HostRoutes      []HostRoute
}

// ToSubnetCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.NetworkID == "" {
		return nil, errNetworkIDRequired
	}
	if opts.CIDR == "" {
		return nil, errCIDRRequired
	}
	if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
		return nil, errInvalidIPType
	}

	s["network_id"] = opts.NetworkID
	s["cidr"] = opts.CIDR

	if opts.EnableDHCP != nil {
		s["enable_dhcp"] = &opts.EnableDHCP
	}
	if opts.Name != "" {
		s["name"] = opts.Name
	}
	if opts.GatewayIP != "" {
		s["gateway_ip"] = opts.GatewayIP
	}
	if opts.TenantID != "" {
		s["tenant_id"] = opts.TenantID
	}
	if opts.IPVersion != 0 {
		s["ip_version"] = opts.IPVersion
	}
	if len(opts.AllocationPools) != 0 {
		s["allocation_pools"] = opts.AllocationPools
	}
	if len(opts.DNSNameservers) != 0 {
		s["dns_nameservers"] = opts.DNSNameservers
	}
	if len(opts.HostRoutes) != 0 {
		s["host_routes"] = opts.HostRoutes
	}

	return map[string]interface{}{"subnet": s}, nil
}

// Create accepts a CreateOpts struct and creates a new subnet using the values
// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToSubnetCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Post(createURL(c), reqBody, &res.Body, nil)
	return res
}

// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
	ToSubnetUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts represents the attributes used when updating an existing subnet.
type UpdateOpts struct {
	Name           string
	GatewayIP      string
	DNSNameservers []string
	HostRoutes     []HostRoute
	EnableDHCP     *bool
}

// ToSubnetUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.EnableDHCP != nil {
		s["enable_dhcp"] = &opts.EnableDHCP
	}
	if opts.Name != "" {
		s["name"] = opts.Name
	}
	if opts.GatewayIP != "" {
		s["gateway_ip"] = opts.GatewayIP
	}
	if len(opts.DNSNameservers) != 0 {
		s["dns_nameservers"] = opts.DNSNameservers
	}
	if len(opts.HostRoutes) != 0 {
		s["host_routes"] = opts.HostRoutes
	}

	return map[string]interface{}{"subnet": s}, nil
}

// Update accepts a UpdateOpts struct and updates an existing subnet using the
// values provided.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToSubnetUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Put(updateURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
		OkCodes: []int{200, 201},
	})

	return res
}

// Delete accepts a unique ID and deletes the subnet associated with it.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
	var res DeleteResult
	_, res.Err = c.Delete(deleteURL(c, id), nil)
	return res
}

// IDFromName is a convenience function that returns a subnet's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
	subnetCount := 0
	subnetID := ""
	if name == "" {
		return "", fmt.Errorf("A subnet name must be provided.")
	}
	pager := List(client, nil)
	pager.EachPage(func(page pagination.Page) (bool, error) {
		subnetList, err := ExtractSubnets(page)
		if err != nil {
			return false, err
		}

		for _, s := range subnetList {
			if s.Name == name {
				subnetCount++
				subnetID = s.ID
			}
		}
		return true, nil
	})

	switch subnetCount {
	case 0:
		return "", fmt.Errorf("Unable to find subnet: %s", name)
	case 1:
		return subnetID, nil
	default:
		return "", fmt.Errorf("Found %d subnets matching %s", subnetCount, name)
	}
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "errors"
  "strconv"
  "encoding/json"
  "github.com/fatih/color"
  "github.com/parnurzeal/gorequest"
)

/////////////////// MANUAL DEPLOYMENT ///////////////////

/*
 * {
 *   "datacenter": "perryman",
 *   "namespace": "stage",
 *   "serviceType": "cassandra",
 *   "version": "1.2.3,
 *   "hash": "abcd1234",
 *   "description": "a cassandra for great good",
 *   "port": 1234
 * }
 */
type ManualDeploymentRequest struct {
  Datacenter  string `json:"datacenter"`
  Namespace   string `json:"namespace"`
  ServiceType string `json:"service_type"`
  Version     string `json:"version"`
  Hash        string `json:"hash"`
  Port        int64 `json:"port"`
  Description string `json:"description"`
}

func RegisterManualDeployment(
  req ManualDeploymentRequest,
  http *gorequest.SuperAgent, cfg *Config) (string, []error) {

  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/deployments"), cfg).Send(req).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("Unexpected response from Nelson server"))
    return resp, errs
  } else {
    return "Manual stack has been registered.", errs
  }
}

/////////////////// INSPECT ///////////////////

/*
 * {
 *   "timestamp": "2016-06-28T20:02:34.449Z",
 *   "message": "instructing perryman's chronos to handle job container",
 *   "status": "deploying"
 * }
 */
type StackStatus struct {
  Timestamp string `json:"timestamp"`
  Message string `json:"message"`
  Status string `json:"status"`
}

/*
 *   "dependencies": {
 *     "outbound": [
 *      ...
 *     ],
 *     "inbound": []
 *   },
 */
type StackDependencies struct {
  Outbound []Stack `json:"outbound"`
  Inbound []Stack `json:"inbound"`
}

/*
 * {
 *   "workflow": "pulsar",
 *   "guid": "e4184c271bb9",
 *   "statuses": [
 *     {
 *       "timestamp": "2016-07-14T22:30:22.358Z",
 *       "message": "inventory-inventory deployed to perryman",
 *       "status": "ready"
 *     },
 *     ...
 *   ],
 *   "stack_name": "inventory-inventory--2-0-11--8gufie2b",
 *   "deployed_at": 1468535384221,
 *   "unit": "inventory-inventory",
 *   "type": "service",
 *   "expiration": 1469928212871,
 *   "dependencies": {
 *     "outbound": [
 *       {
 *         "workflow": "manual",
 *         "guid": "1a69395e919d",
 *         "stack_name": "dev-iptv-cass-dev--4-8-4--mtq2odqzndc0mg",
 *         "deployed_at": 1468518896093,
 *         "unit": "dev-iptv-cass-dev",
 *         "type": "service"
 *       }
 *     ],
 *     "inbound": []
 *   },
 *   "namespace": "devel"
 * }
 */
type StackSummary struct {
  Workflow string `json:"workflow"`
  Guid string `json:"guid"`
  StackName string `json:"stack_name"`
  DeployedAt int64 `json:"deployed_at"`
  UnitName string `json:"unit"`
  Type string `json:"type"`
  NamespaceRef string `json:"namespace"`
  Expiration int64 `json:"expiration"`
  Statuses []StackStatus `json:"statuses"`
  Dependencies StackDependencies `json:"dependencies"`
}

func InspectStack(guid string, http *gorequest.SuperAgent, cfg *Config) (result StackSummary, err []error){
  r, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+"/v1/deployments/"+guid), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    errs = append(errs, errors.New("Bad response from Nelson server"))
    // zomg, constructor just nulls *all* the fields, because who cares about correctness!
    return StackSummary { }, errs
  } else {
    var result StackSummary
    if err := json.Unmarshal(bytes, &result); err != nil {
      panic(err)
    }
    return result, errs
  }
}

func PrintInspectStack(s StackSummary){
  //>>>>>>>>>>> status history
  var tabulized = [][]string {}
  tabulized = append(tabulized, []string{ "GUID:", s.Guid })
  tabulized = append(tabulized, []string{ "STACK NAME:", s.StackName })
  tabulized = append(tabulized, []string{ "NAMESPACE:", s.NamespaceRef })
  tabulized = append(tabulized, []string{ "WORKFLOW:", s.Workflow })
  tabulized = append(tabulized, []string{ "TYPE:", s.Type })
  tabulized = append(tabulized, []string{ "DEPLOYED AT:", JavaEpochToDateStr(s.DeployedAt) })
  tabulized = append(tabulized, []string{ "EXPIRES AT:", JavaEpochToDateStr(s.Expiration) })
  fmt.Println("===>> Stack Information")
  RenderTableToStdout([]string{ "Paramater", "Value" }, tabulized)

  //>>>>>>>>>>> dependency information

  green  := color.New(color.FgGreen).SprintFunc()
  yellow := color.New(color.FgYellow).SprintFunc()

  fmt.Println("") // give us a new line for spacing
  fmt.Println("===>> Dependencies")
  var dependencies = [][]string {}

  if len(s.Dependencies.Outbound) != 0 {
    for _,w := range s.Dependencies.Outbound {
      dependencies = append(dependencies,[]string{ w.Guid, w.StackName, w.Type, w.Workflow, JavaEpochToDateStr(w.DeployedAt), yellow("OUTBOUND") })
    }
  }
  if len(s.Dependencies.Inbound) != 0 {
    for _,w := range s.Dependencies.Inbound {
      dependencies = append(dependencies,[]string{ w.Guid, w.StackName, w.Type, w.Workflow, JavaEpochToDateStr(w.DeployedAt), green("INBOUND") })
    }
  }
  RenderTableToStdout([]string{ "GUID", "Stack", "Type", "Workflow", "Deployed At", "Direction" }, dependencies)

  //>>>>>>>>>>> status history
  fmt.Println("") // give us a new line for spacing
  fmt.Println("===>> Status History")
  var statuslines = [][]string {}
  for _,o := range s.Statuses {
    statuslines = append(statuslines,[]string{ o.Status, o.Timestamp, o.Message })
  }
  RenderTableToStdout([]string{ "Status", "Timestamp", "Message" }, statuslines)
}

/////////////////// DEPRECATION ///////////////////

/*
 * {
 *   "service_type": "heydiddlyho-http",
 *   "version":{
 *     "major":1,
 *     "minor":33
 *   }
 * }
 */
type DeprecationRequest struct {
  ServiceType string `json:"service_type"`
  Version FeatureVersion `json:"version"`
}

func Deprecate(req DeprecationRequest, http *gorequest.SuperAgent, cfg *Config) (str string, err []error){
  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/deployments/deprecate"), cfg).Send(req).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("Unexpected response from Nelson server"))
    return resp, errs
  } else {
    return "Requested deprecation of "+req.ServiceType+" "+strconv.Itoa(req.Version.Major)+"."+strconv.Itoa(req.Version.Minor), errs
  }
}

/////////////////// REDEPLOYMENT ///////////////////

func Redeploy(guid string, http *gorequest.SuperAgent, cfg *Config) (str string, err []error){
  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/deployments/"+guid+"/redeploy"), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("bad response from Nelson server"))
    return resp, errs
  } else {
    return "Redeployment requested.", errs
  }
}

/////////////////// LISTING STACKs ///////////////////

/**
 * {
 *   "workflow": "quasar",
 *   "guid": "67e04d28d6ab",
 *   "stack_name": "blobstore-testsuite--0-1-55--kbqg9nff",
 *   "deployed_at": 1467225866870,
 *   "unit": "blobstore-testsuite",
 *   "type": "job",
 *   "namespace": "devel"
 * }
 */
type Stack struct {
  Workflow string `json:"workflow"`
  Guid string `json:"guid"`
  StackName string `json:"stack_name"`
  DeployedAt int64 `json:"deployed_at"`
  UnitName string `json:"unit"`
  Type string `json:"type"`
  NamespaceRef string `json:"namespace,omitempty"`
}

func ListStacks(delimitedDcs string, delimitedNamespaces string, delimitedStatuses string, http *gorequest.SuperAgent, cfg *Config) (list []Stack, err []error){
  uri := "/v1/deployments?"
  // set the datacenters if specified
  if (isValidCommaDelimitedList(delimitedDcs)){
    uri = uri+"dc="+delimitedDcs+"&"
  }
  if (isValidCommaDelimitedList(delimitedStatuses)){
    uri = uri+"status="+delimitedStatuses+"&"
  } else {
    // if the user didnt specify statuses, they probally only want ready units.
    uri = uri+"status=ready,warming,deprecated&"
  }
  if (isValidCommaDelimitedList(delimitedNamespaces)){
    uri = uri+"ns="+delimitedNamespaces
  } else {
    uri = uri+"ns=devel,qa,prod"
  }

  r, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+uri), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    errs = append(errs, errors.New("Bad response from Nelson server"))
    return nil, errs
  } else {
    var list []Stack
    if err := json.Unmarshal(bytes, &list); err != nil {
      panic(err)
    }
    return list, errs
  }
}

func PrintListStacks(stacks []Stack){
  var tabulized = [][]string {}
  for _,s := range stacks {
    tabulized = append(tabulized,[]string{ s.Guid, s.NamespaceRef, s.StackName, s.Type, s.Workflow, JavaEpochToDateStr(s.DeployedAt) })
  }

  RenderTableToStdout([]string{ "GUID", "Namespace", "Stack", "Type", "Workflow", "Deployed At" }, tabulized)
}

/////////////////// DEPLOYMENT LOG ///////////////////

type StackLog struct {
  Content []string `json:"content"`
  Offset int `json:"offset"`
}

// v1/deployments/:id/log
func GetDeploymentLog(guid string, http *gorequest.SuperAgent, cfg *Config){
  _, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+"/v1/deployments/"+guid+"/log"), cfg).EndBytes()

  if (len(errs) > 0) {
    panic(errs)
  }

  var logs StackLog
  if err := json.Unmarshal(bytes, &logs); err != nil {
    panic(err)
  }

  fmt.Println("===>> logs for stack "+ guid)

  for _,l := range logs.Content {
    fmt.Println(l)
  }
}<commit_msg>fix a broken URL in the deprecation function <commit_after>package main

import (
  "fmt"
  "errors"
  "strconv"
  "encoding/json"
  "github.com/fatih/color"
  "github.com/parnurzeal/gorequest"
)

/////////////////// MANUAL DEPLOYMENT ///////////////////

/*
 * {
 *   "datacenter": "perryman",
 *   "namespace": "stage",
 *   "serviceType": "cassandra",
 *   "version": "1.2.3,
 *   "hash": "abcd1234",
 *   "description": "a cassandra for great good",
 *   "port": 1234
 * }
 */
type ManualDeploymentRequest struct {
  Datacenter  string `json:"datacenter"`
  Namespace   string `json:"namespace"`
  ServiceType string `json:"service_type"`
  Version     string `json:"version"`
  Hash        string `json:"hash"`
  Port        int64 `json:"port"`
  Description string `json:"description"`
}

func RegisterManualDeployment(
  req ManualDeploymentRequest,
  http *gorequest.SuperAgent, cfg *Config) (string, []error) {

  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/deployments"), cfg).Send(req).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("Unexpected response from Nelson server"))
    return resp, errs
  } else {
    return "Manual stack has been registered.", errs
  }
}

/////////////////// INSPECT ///////////////////

/*
 * {
 *   "timestamp": "2016-06-28T20:02:34.449Z",
 *   "message": "instructing perryman's chronos to handle job container",
 *   "status": "deploying"
 * }
 */
type StackStatus struct {
  Timestamp string `json:"timestamp"`
  Message string `json:"message"`
  Status string `json:"status"`
}

/*
 *   "dependencies": {
 *     "outbound": [
 *      ...
 *     ],
 *     "inbound": []
 *   },
 */
type StackDependencies struct {
  Outbound []Stack `json:"outbound"`
  Inbound []Stack `json:"inbound"`
}

/*
 * {
 *   "workflow": "pulsar",
 *   "guid": "e4184c271bb9",
 *   "statuses": [
 *     {
 *       "timestamp": "2016-07-14T22:30:22.358Z",
 *       "message": "inventory-inventory deployed to perryman",
 *       "status": "ready"
 *     },
 *     ...
 *   ],
 *   "stack_name": "inventory-inventory--2-0-11--8gufie2b",
 *   "deployed_at": 1468535384221,
 *   "unit": "inventory-inventory",
 *   "type": "service",
 *   "expiration": 1469928212871,
 *   "dependencies": {
 *     "outbound": [
 *       {
 *         "workflow": "manual",
 *         "guid": "1a69395e919d",
 *         "stack_name": "dev-iptv-cass-dev--4-8-4--mtq2odqzndc0mg",
 *         "deployed_at": 1468518896093,
 *         "unit": "dev-iptv-cass-dev",
 *         "type": "service"
 *       }
 *     ],
 *     "inbound": []
 *   },
 *   "namespace": "devel"
 * }
 */
type StackSummary struct {
  Workflow string `json:"workflow"`
  Guid string `json:"guid"`
  StackName string `json:"stack_name"`
  DeployedAt int64 `json:"deployed_at"`
  UnitName string `json:"unit"`
  Type string `json:"type"`
  NamespaceRef string `json:"namespace"`
  Expiration int64 `json:"expiration"`
  Statuses []StackStatus `json:"statuses"`
  Dependencies StackDependencies `json:"dependencies"`
}

func InspectStack(guid string, http *gorequest.SuperAgent, cfg *Config) (result StackSummary, err []error){
  r, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+"/v1/deployments/"+guid), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    errs = append(errs, errors.New("Bad response from Nelson server"))
    // zomg, constructor just nulls *all* the fields, because who cares about correctness!
    return StackSummary { }, errs
  } else {
    var result StackSummary
    if err := json.Unmarshal(bytes, &result); err != nil {
      panic(err)
    }
    return result, errs
  }
}

func PrintInspectStack(s StackSummary){
  //>>>>>>>>>>> status history
  var tabulized = [][]string {}
  tabulized = append(tabulized, []string{ "GUID:", s.Guid })
  tabulized = append(tabulized, []string{ "STACK NAME:", s.StackName })
  tabulized = append(tabulized, []string{ "NAMESPACE:", s.NamespaceRef })
  tabulized = append(tabulized, []string{ "WORKFLOW:", s.Workflow })
  tabulized = append(tabulized, []string{ "TYPE:", s.Type })
  tabulized = append(tabulized, []string{ "DEPLOYED AT:", JavaEpochToDateStr(s.DeployedAt) })
  tabulized = append(tabulized, []string{ "EXPIRES AT:", JavaEpochToDateStr(s.Expiration) })
  fmt.Println("===>> Stack Information")
  RenderTableToStdout([]string{ "Paramater", "Value" }, tabulized)

  //>>>>>>>>>>> dependency information

  green  := color.New(color.FgGreen).SprintFunc()
  yellow := color.New(color.FgYellow).SprintFunc()

  fmt.Println("") // give us a new line for spacing
  fmt.Println("===>> Dependencies")
  var dependencies = [][]string {}

  if len(s.Dependencies.Outbound) != 0 {
    for _,w := range s.Dependencies.Outbound {
      dependencies = append(dependencies,[]string{ w.Guid, w.StackName, w.Type, w.Workflow, JavaEpochToDateStr(w.DeployedAt), yellow("OUTBOUND") })
    }
  }
  if len(s.Dependencies.Inbound) != 0 {
    for _,w := range s.Dependencies.Inbound {
      dependencies = append(dependencies,[]string{ w.Guid, w.StackName, w.Type, w.Workflow, JavaEpochToDateStr(w.DeployedAt), green("INBOUND") })
    }
  }
  RenderTableToStdout([]string{ "GUID", "Stack", "Type", "Workflow", "Deployed At", "Direction" }, dependencies)

  //>>>>>>>>>>> status history
  fmt.Println("") // give us a new line for spacing
  fmt.Println("===>> Status History")
  var statuslines = [][]string {}
  for _,o := range s.Statuses {
    statuslines = append(statuslines,[]string{ o.Status, o.Timestamp, o.Message })
  }
  RenderTableToStdout([]string{ "Status", "Timestamp", "Message" }, statuslines)
}

/////////////////// DEPRECATION ///////////////////

/*
 * {
 *   "service_type": "heydiddlyho-http",
 *   "version":{
 *     "major":1,
 *     "minor":33
 *   }
 * }
 */
type DeprecationRequest struct {
  ServiceType string `json:"service_type"`
  Version FeatureVersion `json:"version"`
}

func Deprecate(req DeprecationRequest, http *gorequest.SuperAgent, cfg *Config) (str string, err []error){
  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/units/deprecate"), cfg).Send(req).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("Unexpected response from Nelson server"))
    return resp, errs
  } else {
    return "Requested deprecation of "+req.ServiceType+" "+strconv.Itoa(req.Version.Major)+"."+strconv.Itoa(req.Version.Minor), errs
  }
}

/////////////////// REDEPLOYMENT ///////////////////

func Redeploy(guid string, http *gorequest.SuperAgent, cfg *Config) (str string, err []error){
  r, body, errs := AugmentRequest(
    http.Post(cfg.Endpoint+"/v1/deployments/"+guid+"/redeploy"), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    resp := string(body[:])
    errs = append(errs, errors.New("bad response from Nelson server"))
    return resp, errs
  } else {
    return "Redeployment requested.", errs
  }
}

/////////////////// LISTING STACKs ///////////////////

/**
 * {
 *   "workflow": "quasar",
 *   "guid": "67e04d28d6ab",
 *   "stack_name": "blobstore-testsuite--0-1-55--kbqg9nff",
 *   "deployed_at": 1467225866870,
 *   "unit": "blobstore-testsuite",
 *   "type": "job",
 *   "namespace": "devel"
 * }
 */
type Stack struct {
  Workflow string `json:"workflow"`
  Guid string `json:"guid"`
  StackName string `json:"stack_name"`
  DeployedAt int64 `json:"deployed_at"`
  UnitName string `json:"unit"`
  Type string `json:"type"`
  NamespaceRef string `json:"namespace,omitempty"`
}

func ListStacks(delimitedDcs string, delimitedNamespaces string, delimitedStatuses string, http *gorequest.SuperAgent, cfg *Config) (list []Stack, err []error){
  uri := "/v1/deployments?"
  // set the datacenters if specified
  if (isValidCommaDelimitedList(delimitedDcs)){
    uri = uri+"dc="+delimitedDcs+"&"
  }
  if (isValidCommaDelimitedList(delimitedStatuses)){
    uri = uri+"status="+delimitedStatuses+"&"
  } else {
    // if the user didnt specify statuses, they probally only want ready units.
    uri = uri+"status=ready,warming,deprecated&"
  }
  if (isValidCommaDelimitedList(delimitedNamespaces)){
    uri = uri+"ns="+delimitedNamespaces
  } else {
    uri = uri+"ns=devel,qa,prod"
  }

  r, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+uri), cfg).EndBytes()

  if (r.StatusCode / 100 != 2){
    errs = append(errs, errors.New("Bad response from Nelson server"))
    return nil, errs
  } else {
    var list []Stack
    if err := json.Unmarshal(bytes, &list); err != nil {
      panic(err)
    }
    return list, errs
  }
}

func PrintListStacks(stacks []Stack){
  var tabulized = [][]string {}
  for _,s := range stacks {
    tabulized = append(tabulized,[]string{ s.Guid, s.NamespaceRef, s.StackName, s.Type, s.Workflow, JavaEpochToDateStr(s.DeployedAt) })
  }

  RenderTableToStdout([]string{ "GUID", "Namespace", "Stack", "Type", "Workflow", "Deployed At" }, tabulized)
}

/////////////////// DEPLOYMENT LOG ///////////////////

type StackLog struct {
  Content []string `json:"content"`
  Offset int `json:"offset"`
}

// v1/deployments/:id/log
func GetDeploymentLog(guid string, http *gorequest.SuperAgent, cfg *Config){
  _, bytes, errs := AugmentRequest(
    http.Get(cfg.Endpoint+"/v1/deployments/"+guid+"/log"), cfg).EndBytes()

  if (len(errs) > 0) {
    panic(errs)
  }

  var logs StackLog
  if err := json.Unmarshal(bytes, &logs); err != nil {
    panic(err)
  }

  fmt.Println("===>> logs for stack "+ guid)

  for _,l := range logs.Content {
    fmt.Println(l)
  }
}<|endoftext|>