<commit_before>package models

import "errors"

func (err Error) Error() string {
	return err.GetMessage()
}

const (
	InvalidDomain = "InvalidDomain"

	InvalidRecord          = "InvalidRecord"
	InvalidRequest         = "InvalidRequest"
	InvalidResponse        = "InvalidResponse"
	InvalidProtobufMessage = "InvalidProtobufMessage"
	InvalidJSON            = "InvalidJSON"

	UnknownError = "UnknownError"
	Unauthorized = "Unauthorized"

	ResourceConflict = "ResourceConflict"
	ResourceNotFound = "ResourceNotFound"
	RouterError      = "RouterError"

	ActualLRPCannotBeClaimed = "ActualLRPCannotBeClaimed"
	ActualLRPCannotBeStarted = "ActualLRPCannotBeStarted"
	ActualLRPCannotBeFailed  = "ActualLRPCannotBeFailed"
	ActualLRPCannotBeRemoved = "ActualLRPCannotBeRemoved"
)

var (
	ErrResourceNotFound = &Error{
		Type:    ResourceNotFound,
		Message: "the requested resource could not be found",
	}

	ErrBadRequest = &Error{
		Type:    InvalidRequest,
		Message: "the request received is invalid",
	}

	ErrUnknownError = &Error{
		Type:    UnknownError,
		Message: "the request failed for an unknown reason",
	}

	ErrSerializeJSON = &Error{
		Type:    InvalidJSON,
		Message: "could not serialize JSON",
	}

	ErrDeserializeJSON = &Error{
		Type:    InvalidJSON,
		Message: "could not deserialize JSON",
	}

	ErrActualLRPCannotBeClaimed = &Error{
		Type:    ActualLRPCannotBeClaimed,
		Message: "cannot claim actual LRP",
	}

	ErrActualLRPCannotBeStarted = &Error{
		Type:    ActualLRPCannotBeStarted,
		Message: "cannot start actual LRP",
	}

	ErrActualLRPCannotBeFailed = &Error{
		Type:    ActualLRPCannotBeFailed,
		Message: "cannot fail actual LRP",
	}

	ErrActualLRPCannotBeRemoved = &Error{
		Type:    ActualLRPCannotBeRemoved,
		Message: "cannot remove actual LRP",
	}
)

func (err *Error) Equal(other *Error) bool {
	return err.GetType() == other.GetType()
}

type ErrInvalidField struct {
	Field string
}

func (err ErrInvalidField) Error() string {
	return "Invalid field: " + err.Field
}

type ErrInvalidModification struct {
	InvalidField string
}

func (err ErrInvalidModification) Error() string {
	return "attempt to make invalid change to field: " + err.InvalidField
}

var ErrActualLRPGroupInvalid = errors.New("ActualLRPGroup invalid")
<commit_msg>Allow Error to compare against error<commit_after>package models

import "errors"

func (err Error) Error() string {
	return err.GetMessage()
}

const (
	InvalidDomain = "InvalidDomain"

	InvalidRecord          = "InvalidRecord"
	InvalidRequest         = "InvalidRequest"
	InvalidResponse        = "InvalidResponse"
	InvalidProtobufMessage = "InvalidProtobufMessage"
	InvalidJSON            = "InvalidJSON"

	UnknownError = "UnknownError"
	Unauthorized = "Unauthorized"

	ResourceConflict = "ResourceConflict"
	ResourceNotFound = "ResourceNotFound"
	RouterError      = "RouterError"

	ActualLRPCannotBeClaimed = "ActualLRPCannotBeClaimed"
	ActualLRPCannotBeStarted = "ActualLRPCannotBeStarted"
	ActualLRPCannotBeFailed  = "ActualLRPCannotBeFailed"
	ActualLRPCannotBeRemoved = "ActualLRPCannotBeRemoved"
)

var (
	ErrResourceNotFound = &Error{
		Type:    ResourceNotFound,
		Message: "the requested resource could not be found",
	}

	ErrBadRequest = &Error{
		Type:    InvalidRequest,
		Message: "the request received is invalid",
	}

	ErrUnknownError = &Error{
		Type:    UnknownError,
		Message: "the request failed for an unknown reason",
	}

	ErrSerializeJSON = &Error{
		Type:    InvalidJSON,
		Message: "could not serialize JSON",
	}

	ErrDeserializeJSON = &Error{
		Type:    InvalidJSON,
		Message: "could not deserialize JSON",
	}

	ErrActualLRPCannotBeClaimed = &Error{
		Type:    ActualLRPCannotBeClaimed,
		Message: "cannot claim actual LRP",
	}

	ErrActualLRPCannotBeStarted = &Error{
		Type:    ActualLRPCannotBeStarted,
		Message: "cannot start actual LRP",
	}

	ErrActualLRPCannotBeFailed = &Error{
		Type:    ActualLRPCannotBeFailed,
		Message: "cannot fail actual LRP",
	}

	ErrActualLRPCannotBeRemoved = &Error{
		Type:    ActualLRPCannotBeRemoved,
		Message: "cannot remove actual LRP",
	}
)

func (err *Error) Equal(other error) bool {
	if e, ok := other.(*Error); ok {
		return e.GetType() == err.GetType()
	}
	return false
}

type ErrInvalidField struct {
	Field string
}

func (err ErrInvalidField) Error() string {
	return "Invalid field: " + err.Field
}

type ErrInvalidModification struct {
	InvalidField string
}

func (err ErrInvalidModification) Error() string {
	return "attempt to make invalid change to field: " + err.InvalidField
}

var ErrActualLRPGroupInvalid = errors.New("ActualLRPGroup invalid")
<|endoftext|>


<commit_before>package simpleratelimit

import (
	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
)

func TestLimit(t *testing.T) {
	Convey("start testing limit", t, func() {
		rl := New(1, time.Second)
		for i := 0; i < 10; i++ {
			if i == 0 {
				So(rl.Limit(), ShouldEqual, false)
			} else {
				So(rl.Limit(), ShouldEqual, true)
			}
		}
	})
	Convey("start testing undo", t, func() {
		rl := New(1, time.Second)
		for i := 0; i < 10; i++ {
			if i == 0 {
				So(rl.Limit(), ShouldEqual, false)
			} else {
				rl.Undo()
				So(rl.Limit(), ShouldEqual, false)
			}
		}
	})
}

func BenchmarkLimit(b *testing.B) {
	Convey("start testing benchmark", b, func() {
		rl := New(1, time.Second)
		for i := 0; i < b.N; i++ {
			rl.Limit()
		}
	})
}
<commit_msg>test coverall<commit_after>package simpleratelimit

import (
	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
)

func TestLimit(t *testing.T) {
	Convey("start testing New(0, 0)", t, func() {
		rl := New(0, 0*time.Second)
		So(rl.Limit(), ShouldEqual, false)
	})
	Convey("start testing limit", t, func() {
		rl := New(1, time.Second)
		for i := 0; i < 10; i++ {
			if i == 0 {
				So(rl.Limit(), ShouldEqual, false)
			} else {
				So(rl.Limit(), ShouldEqual, true)
			}
		}
	})
	Convey("start testing updateRate", t, func() {
		rl := New(1, time.Second)
		for i := 0; i < 2; i++ {
			if i == 0 {
				So(rl.Limit(), ShouldEqual, false)
			} else {
				rl.UpdateRate(2)
				So(rl.Limit(), ShouldEqual, true)
			}
		}
	})
	Convey("start testing undo", t, func() {
		rl := New(1, time.Second)
		for i := 0; i < 10; i++ {
			if i == 0 {
				So(rl.Limit(), ShouldEqual, false)
			} else {
				rl.Undo()
				So(rl.Limit(), ShouldEqual, false)
			}
		}
		for i := 0; i < 10; i++ {
			rl.Limit()
		}
		rl.Undo()
	})
}

func BenchmarkLimit(b *testing.B) {
	Convey("start testing benchmark", b, func() {
		rl := New(1, time.Second)
		for i := 0; i < b.N; i++ {
			rl.Limit()
		}
	})
}
<|endoftext|>


<commit_before>package grpsink

import (
	"context"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stripe/veneur/ssf"
	"google.golang.org/grpc"
	"google.golang.org/grpc/connectivity"
	"google.golang.org/grpc/keepalive"
)

var tags = map[string]string{"foo": "bar"}

type MockSpanSinkServer struct {
	spans []*ssf.SSFSpan
	mut   sync.Mutex
	got   chan struct{}
}

// SendSpans mocks base method
func (m *MockSpanSinkServer) SendSpans(stream SpanSink_SendSpansServer) error {
	for {
		span, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return stream.SendMsg(&SpanResponse{
					Greeting: "fin",
				})
			}
			return err
		}
		m.mut.Lock()
		m.spans = append(m.spans, span)
		m.mut.Unlock()
		m.got <- struct{}{}
	}
}

// Extra method and locking to avoid a weird data race
func (m *MockSpanSinkServer) firstSpan() *ssf.SSFSpan {
	m.mut.Lock()
	defer m.mut.Unlock()
	if len(m.spans) == 0 {
		panic("no spans yet")
	}

	return m.spans[0]
}

func (m *MockSpanSinkServer) spanCount() int {
	m.mut.Lock()
	defer m.mut.Unlock()
	return len(m.spans)
}

func TestEndToEnd(t *testing.T) {
	log := logrus.New()
	log.SetLevel(logrus.ErrorLevel)

	testaddr := "127.0.0.1:15111"
	lis, err := net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}

	mock, srv := &MockSpanSinkServer{got: make(chan struct{})}, grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)

	block := make(chan struct{})
	go func() {
		<-block
		srv.Serve(lis)
	}()
	block <- struct{}{}

	sink, err := NewGRPCStreamingSpanSink(context.Background(), testaddr, "test1", tags, log, grpc.WithInsecure())
	require.NoError(t, err)
	assert.Equal(t, sink.commonTags, tags)
	assert.NotNil(t, sink.grpcConn)

	err = sink.Start(nil)
	require.NoError(t, err)

	start := time.Now()
	end := start.Add(2 * time.Second)
	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}

	err = sink.Ingest(testSpan)
	<-mock.got
	testSpan.Tags = map[string]string{
		"foo": "bar",
		"baz": "qux",
	}

	assert.NoError(t, err)
	assert.Equal(t, testSpan, mock.firstSpan())
	require.Equal(t, mock.spanCount(), 1)

	// Stoppage will happen in the background, and forward progress will be blocked by the wait sequence.
	go srv.Stop()
	waitThroughStateSequence(t, sink, 5*time.Second,
		connectivity.Ready,
		connectivity.TransientFailure,
	)

	err = sink.Ingest(testSpan)
	require.Equal(t, mock.spanCount(), 1)
	require.Error(t, err)

	// Set up new net listener and server; Stop() closes the listener we used before.
	srv = grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)
	lis, err = net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}
	go func() {
		<-block
		err = srv.Serve(lis)
		assert.NoError(t, err)
	}()
	block <- struct{}{}
	reconnectWithin(t, sink, 2*time.Second)

	err = sink.Ingest(testSpan)
	require.NoError(t, err)
	<-mock.got
	require.Equal(t, mock.spanCount(), 2)
}

// It should be nearly unreachable for an idle state to be reached by the
// channel, but this test ensures we handle it properly in the event that it
// does. It can be flaky, though (it's too dependent on sleep timings), so
// it's disabled, but preserved for future debugging purposes.
func TestClientIdleRecovery(t *testing.T) {
	testaddr := "127.0.0.1:15112"
	lis, err := net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}

	mock, srv := &MockSpanSinkServer{got: make(chan struct{})}, grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)

	block := make(chan struct{})
	go func() {
		<-block
		srv.Serve(lis)
	}()
	block <- struct{}{}

	sink, err := NewGRPCStreamingSpanSink(context.Background(),
		testaddr, "test1", tags, logrus.New(),
		grpc.WithInsecure(),
		// Very short timeout in order to ensure the channel becomes idle
		// almost immediately.
		grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: time.Millisecond}),
	)
	require.NoError(t, err)
	go func() {
		sink.maintainStream(context.Background())
	}()

	// Pre-mark the stream as in a bad state.
	atomic.StoreUint32(&sink.bad, 1)

	// SUT here is the channel and stream maintenance system, so express the
	// requirement as a series of states through which it should automatically
	// proceed.
	waitThroughFiniteStateSequence(t, sink, 5*time.Second,
		connectivity.Idle,
		connectivity.Connecting,
		connectivity.Ready,
	)

	reconnectWithin(t, sink, 5*time.Second)

	// Send a span, just to be sure.
	start := time.Now()
	end := start.Add(2 * time.Second)
	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}

	err = sink.Ingest(testSpan)
	<-mock.got
	testSpan.Tags = map[string]string{
		"foo": "bar",
		"baz": "qux",
	}

	assert.NoError(t, err)
	assert.Equal(t, testSpan, mock.firstSpan())
	require.Equal(t, mock.spanCount(), 1)
}
<commit_msg>Unflake GRPC end-to-end test (#437)<commit_after>package grpsink

import (
	"context"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stripe/veneur/ssf"
	"google.golang.org/grpc"
	"google.golang.org/grpc/connectivity"
	"google.golang.org/grpc/keepalive"
)

var tags = map[string]string{"foo": "bar"}

type MockSpanSinkServer struct {
	spans []*ssf.SSFSpan
	mut   sync.Mutex
	got   chan struct{}
}

// SendSpans mocks base method
func (m *MockSpanSinkServer) SendSpans(stream SpanSink_SendSpansServer) error {
	for {
		span, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return stream.SendMsg(&SpanResponse{
					Greeting: "fin",
				})
			}
			return err
		}
		m.mut.Lock()
		m.spans = append(m.spans, span)
		m.mut.Unlock()
		m.got <- struct{}{}
	}
}

// Extra method and locking to avoid a weird data race
func (m *MockSpanSinkServer) firstSpan() *ssf.SSFSpan {
	m.mut.Lock()
	defer m.mut.Unlock()
	if len(m.spans) == 0 {
		panic("no spans yet")
	}

	return m.spans[0]
}

func (m *MockSpanSinkServer) spanCount() int {
	m.mut.Lock()
	defer m.mut.Unlock()
	return len(m.spans)
}

func TestEndToEnd(t *testing.T) {
	log := logrus.New()
	log.SetLevel(logrus.ErrorLevel)

	testaddr := "127.0.0.1:0"
	lis, err := net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}
	testaddr = lis.Addr().String()

	mock, srv := &MockSpanSinkServer{got: make(chan struct{})}, grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)

	block := make(chan struct{})
	go func() {
		<-block
		srv.Serve(lis)
	}()
	block <- struct{}{}

	sink, err := NewGRPCStreamingSpanSink(context.Background(), testaddr, "test1", tags, log, grpc.WithInsecure())
	require.NoError(t, err)
	assert.Equal(t, sink.commonTags, tags)
	assert.NotNil(t, sink.grpcConn)

	err = sink.Start(nil)
	require.NoError(t, err)

	start := time.Now()
	end := start.Add(2 * time.Second)
	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}

	err = sink.Ingest(testSpan)
	<-mock.got
	testSpan.Tags = map[string]string{
		"foo": "bar",
		"baz": "qux",
	}

	assert.NoError(t, err)
	assert.Equal(t, testSpan, mock.firstSpan())
	require.Equal(t, mock.spanCount(), 1)

	// Stoppage will happen in the background, and forward progress will be blocked by the wait sequence.
	go srv.Stop()
	waitThroughStateSequence(t, sink, 5*time.Second,
		connectivity.Ready,
		connectivity.TransientFailure,
	)

	err = sink.Ingest(testSpan)
	require.Equal(t, mock.spanCount(), 1)
	require.Error(t, err)

	// Set up new net listener and server; Stop() closes the listener we used before.
	srv = grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)
	lis, err = net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}
	go func() {
		<-block
		err = srv.Serve(lis)
		assert.NoError(t, err)
	}()
	block <- struct{}{}
	reconnectWithin(t, sink, 2*time.Second)

	err = sink.Ingest(testSpan)
	require.NoError(t, err)
	<-mock.got
	require.Equal(t, mock.spanCount(), 2)
}

// It should be nearly unreachable for an idle state to be reached by the
// channel, but this test ensures we handle it properly in the event that it
// does. It can be flaky, though (it's too dependent on sleep timings), so
// it's disabled, but preserved for future debugging purposes.
func TestClientIdleRecovery(t *testing.T) {
	testaddr := "127.0.0.1:0"
	lis, err := net.Listen("tcp", testaddr)
	if err != nil {
		t.Fatalf("Failed to set up net listener with err %s", err)
	}
	testaddr = lis.Addr().String()

	mock, srv := &MockSpanSinkServer{got: make(chan struct{})}, grpc.NewServer()
	RegisterSpanSinkServer(srv, mock)

	block := make(chan struct{})
	go func() {
		<-block
		srv.Serve(lis)
	}()
	block <- struct{}{}

	sink, err := NewGRPCStreamingSpanSink(context.Background(),
		testaddr, "test1", tags, logrus.New(),
		grpc.WithInsecure(),
		// Very short timeout in order to ensure the channel becomes idle
		// almost immediately.
		grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: time.Millisecond}),
	)
	require.NoError(t, err)
	go func() {
		sink.maintainStream(context.Background())
	}()

	// Pre-mark the stream as in a bad state.
	atomic.StoreUint32(&sink.bad, 1)

	// SUT here is the channel and stream maintenance system, so express the
	// requirement as a series of states through which it should automatically
	// proceed.
	waitThroughFiniteStateSequence(t, sink, 5*time.Second,
		connectivity.Idle,
		connectivity.Connecting,
		connectivity.Ready,
	)

	reconnectWithin(t, sink, 5*time.Second)

	// Send a span, just to be sure.
	start := time.Now()
	end := start.Add(2 * time.Second)
	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}

	err = sink.Ingest(testSpan)
	<-mock.got
	testSpan.Tags = map[string]string{
		"foo": "bar",
		"baz": "qux",
	}

	assert.NoError(t, err)
	assert.Equal(t, testSpan, mock.firstSpan())
	require.Equal(t, mock.spanCount(), 1)
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"testing"

	mkr "github.com/mackerelio/mackerel-client-go"
)

func TestIsSameMonitor(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}
	b := &mkr.MonitorConnectivity{Name: "foo", Type: "connectivity"}

	_, ret := isSameMonitor(a, b, true)
	if ret != true {
		t.Error("should recognize same monitors")
	}

	_, ret = isSameMonitor(a, b, false)
	if ret == true {
		t.Error("should not recognize same monitors")
	}
}

func TestValidateRoles(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}

	ret, err := validateRules([](mkr.Monitor){a}, "test monitor")
	if ret != true {
		t.Errorf("should validate the rule: %s", err.Error())
	}

}

func TestDiffMonitors(t *testing.T) {
	const want = ` {
   "name": "foo",
-  "responseTimeCritical": 1000,
   "service": "bar",
   "type": "external",
   "url": "http://example.com"
 },`
	a := &mkr.MonitorExternalHTTP{ID: "12345", Name: "foo", Type: "external", URL: "http://example.com", Service: "bar", ResponseTimeCritical: 1000}
	b := &mkr.MonitorExternalHTTP{ID: "12345", Name: "foo", Type: "external", URL: "http://example.com", Service: "bar"}
	if got := diffMonitor(a, b); got != want {
		t.Errorf("diffMonitor: got\n%s\nwant \n%s", got, want)
	}
}

func TestMonitorSaveRules(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	a := &mkr.MonitorExternalHTTP{
		ID:                   "12345",
		Name:                 "foo",
		Type:                 "external",
		URL:                  "http://example.com",
		Service:              "bar",
		ResponseTimeCritical: 1000,
	}
	monitorSaveRules([]mkr.Monitor{a}, tmpFile.Name())

	byt, _ := ioutil.ReadFile(tmpFile.Name())
	content := string(byt)
	expected := `{
    "monitors": [
        {
            "id": "12345",
            "name": "foo",
            "type": "external",
            "url": "http://example.com",
            "service": "bar",
            "responseTimeCritical": 1000
        }
    ]
}
`
	if content != expected {
		t.Errorf("content should be:\n %s, but:\n %s", expected, content)
	}
}

func TestStringifyMonitor(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}
	expected := `+{
+  "id": "12345",
+  "name": "foo",
+  "type": "connectivity"
+},`

	r := stringifyMonitor(a, "+")
	if r != expected {
		t.Errorf("stringifyMonitor should be:\n%s\nbut:\n%s", expected, r)
	}
}

func TestDiffMonitorsWithScopes(t *testing.T) {
	a := &mkr.MonitorConnectivity{
		ID:   "12345",
		Name: "foo",
		Type: "connectivity",
	}
	b := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"sss: notebook"},
	}
	diff := diffMonitor(a, b)
	expected := ` {
   "name": "foo",
   "type": "connectivity"
+  "scopes": [
+    "sss: notebook"
+  ]
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	diff = diffMonitor(b, a)
	expected = ` {
   "name": "foo",
-  "scopes": [
-    "sss: notebook"
-  ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	c := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"sss: notebook", "ttt: notebook"},
	}

	diff = diffMonitor(b, c)
	expected = ` {
   "name": "foo",
   "scopes": [
     "sss: notebook"
+    "ttt: notebook"
   ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	d := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"ttt: notebook"},
	}
	diff = diffMonitor(b, d)
	expected = ` {
   "name": "foo",
   "scopes": [
-    "sss: notebook"
+    "ttt: notebook"
   ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}
}
<commit_msg>fix test according to mackerel-client-go changes of addding headers field<commit_after>package main

import (
	"io/ioutil"
	"testing"

	mkr "github.com/mackerelio/mackerel-client-go"
)

func TestIsSameMonitor(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}
	b := &mkr.MonitorConnectivity{Name: "foo", Type: "connectivity"}

	_, ret := isSameMonitor(a, b, true)
	if ret != true {
		t.Error("should recognize same monitors")
	}

	_, ret = isSameMonitor(a, b, false)
	if ret == true {
		t.Error("should not recognize same monitors")
	}
}

func TestValidateRoles(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}

	ret, err := validateRules([](mkr.Monitor){a}, "test monitor")
	if ret != true {
		t.Errorf("should validate the rule: %s", err.Error())
	}

}

func TestDiffMonitors(t *testing.T) {
	const want = ` {
   "headers": [
   ],
   "name": "foo",
-  "responseTimeCritical": 1000,
   "service": "bar",
   "type": "external",
   "url": "http://example.com"
 },`
	a := &mkr.MonitorExternalHTTP{ID: "12345", Name: "foo", Type: "external", URL: "http://example.com", Service: "bar", ResponseTimeCritical: 1000, Headers: []mkr.HeaderField{}}
	b := &mkr.MonitorExternalHTTP{ID: "12345", Name: "foo", Type: "external", URL: "http://example.com", Service: "bar", Headers: []mkr.HeaderField{}}
	if got := diffMonitor(a, b); got != want {
		t.Errorf("diffMonitor: got\n%s\nwant \n%s", got, want)
	}
}

func TestMonitorSaveRules(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	a := &mkr.MonitorExternalHTTP{
		ID:                   "12345",
		Name:                 "foo",
		Type:                 "external",
		URL:                  "http://example.com",
		Service:              "bar",
		ResponseTimeCritical: 1000,
		Headers:              []mkr.HeaderField{},
	}
	monitorSaveRules([]mkr.Monitor{a}, tmpFile.Name())

	byt, _ := ioutil.ReadFile(tmpFile.Name())
	content := string(byt)
	expected := `{
    "monitors": [
        {
            "id": "12345",
            "name": "foo",
            "type": "external",
            "url": "http://example.com",
            "service": "bar",
            "responseTimeCritical": 1000,
            "headers": []
        }
    ]
}
`
	if content != expected {
		t.Errorf("content should be:\n %s, but:\n %s", expected, content)
	}
}

func TestStringifyMonitor(t *testing.T) {
	a := &mkr.MonitorConnectivity{ID: "12345", Name: "foo", Type: "connectivity"}
	expected := `+{
+  "id": "12345",
+  "name": "foo",
+  "type": "connectivity"
+},`

	r := stringifyMonitor(a, "+")
	if r != expected {
		t.Errorf("stringifyMonitor should be:\n%s\nbut:\n%s", expected, r)
	}
}

func TestDiffMonitorsWithScopes(t *testing.T) {
	a := &mkr.MonitorConnectivity{
		ID:   "12345",
		Name: "foo",
		Type: "connectivity",
	}
	b := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"sss: notebook"},
	}
	diff := diffMonitor(a, b)
	expected := ` {
   "name": "foo",
   "type": "connectivity"
+  "scopes": [
+    "sss: notebook"
+  ]
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	diff = diffMonitor(b, a)
	expected = ` {
   "name": "foo",
-  "scopes": [
-    "sss: notebook"
-  ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	c := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"sss: notebook", "ttt: notebook"},
	}

	diff = diffMonitor(b, c)
	expected = ` {
   "name": "foo",
   "scopes": [
     "sss: notebook"
+    "ttt: notebook"
   ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}

	d := &mkr.MonitorConnectivity{
		ID:     "12345",
		Name:   "foo",
		Type:   "connectivity",
		Scopes: []string{"ttt: notebook"},
	}
	diff = diffMonitor(b, d)
	expected = ` {
   "name": "foo",
   "scopes": [
-    "sss: notebook"
+    "ttt: notebook"
   ],
   "type": "connectivity"
 },`
	if diff != expected {
		t.Errorf("expected:\n%s\n, output:\n%s\n", expected, diff)
	}
}
<|endoftext|>


<commit_before>// +build windows

/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package mount

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

	"github.com/golang/glog"
)

// Mounter provides the default implementation of mount.Interface
// for the windows platform.  This implementation assumes that the
// kubelet is running in the host's root mount namespace.
type Mounter struct {
	mounterPath string
}

// New returns a mount.Interface for the current system.
// It provides options to override the default mounter behavior.
// mounterPath allows using an alternative to `/bin/mount` for mounting.
func New(mounterPath string) Interface {
	return &Mounter{
		mounterPath: mounterPath,
	}
}

// Mount : mounts source to target as NTFS with given options.
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
	target = normalizeWindowsPath(target)

	if source == "tmpfs" {
		glog.V(3).Infof("azureMount: mounting source (%q), target (%q), with options (%q)", source, target, options)
		return os.MkdirAll(target, 0755)
	}

	parentDir := filepath.Dir(target)
	if err := os.MkdirAll(parentDir, 0755); err != nil {
		return err
	}

	glog.V(4).Infof("azureMount: mount options(%q) source:%q, target:%q, fstype:%q, begin to mount",
		options, source, target, fstype)
	bindSource := ""

	// tell it's going to mount azure disk or azure file according to options
	if bind, _ := isBind(options); bind {
		// mount azure disk
		bindSource = normalizeWindowsPath(source)
	} else {
		if len(options) < 2 {
			glog.Warningf("azureMount: mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
				options, len(options), source, target)
			return nil
		}

		// currently only SMB mount is supported
		cmdLine := fmt.Sprintf(`$User = "%s";$PWord = ConvertTo-SecureString -String "%s" -AsPlainText -Force;`+
			`$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord`,
			options[0], options[1])

		driverLetter, err := getAvailableDriveLetter()
		if err != nil {
			return err
		}
		bindSource = driverLetter + ":"
		cmdLine += fmt.Sprintf(";New-SmbGlobalMapping -LocalPath %s -RemotePath %s -Credential $Credential", bindSource, source)

		if output, err := exec.Command("powershell", "/c", cmdLine).CombinedOutput(); err != nil {
			// we don't return error here, even though New-SmbGlobalMapping failed, we still make it successful,
			// will return error when Windows 2016 RS3 is ready on azure
			glog.Errorf("azureMount: SmbGlobalMapping failed: %v, only SMB mount is supported now, output: %q", err, string(output))
			return os.MkdirAll(target, 0755)
		}
	}

	if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
		glog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
		return err
	}

	return nil
}

// Unmount unmounts the target.
func (mounter *Mounter) Unmount(target string) error {
	glog.V(4).Infof("azureMount: Unmount target (%q)", target)
	target = normalizeWindowsPath(target)
	if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
		glog.Errorf("rmdir failed: %v, output: %q", err, string(output))
		return err
	}
	return nil
}

// List returns a list of all mounted filesystems. todo
func (mounter *Mounter) List() ([]MountPoint, error) {
	return []MountPoint{}, nil
}

// IsMountPointMatch determines if the mountpoint matches the dir
func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
	return mp.Path == dir
}

// IsNotMountPoint determines if a directory is a mountpoint.
func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) {
	return IsNotMountPoint(mounter, dir)
}

// IsLikelyNotMountPoint determines if a directory is not a mountpoint.
func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
	stat, err := os.Lstat(file)
	if err != nil {
		return true, err
	}
	// If current file is a symlink, then it is a mountpoint.
	if stat.Mode()&os.ModeSymlink != 0 {
		return false, nil
	}

	return true, nil
}

// GetDeviceNameFromMount given a mnt point, find the device
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
	return getDeviceNameFromMount(mounter, mountPath, pluginDir)
}

// DeviceOpened determines if the device is in use elsewhere
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
	return false, nil
}

// PathIsDevice determines if a path is a device.
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
	return false, nil
}

// MakeRShared checks that given path is on a mount with 'rshared' mount
// propagation. Empty implementation here.
func (mounter *Mounter) MakeRShared(path string) error {
	return nil
}

func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
	// Try to mount the disk
	glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)

	if err := ValidateDiskNumber(source); err != nil {
		glog.Errorf("azureMount: formatAndMount failed, err: %v\n", err)
		return err
	}

	driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
	if err != nil {
		return err
	}
	driverPath := driveLetter + ":"
	target = normalizeWindowsPath(target)
	glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
	if output, err := mounter.Exec.Run("cmd", "/c", "mklink", "/D", target, driverPath); err != nil {
		glog.Errorf("mklink failed: %v, output: %q", err, string(output))
		return err
	}
	return nil
}

func normalizeWindowsPath(path string) string {
	normalizedPath := strings.Replace(path, "/", "\\", -1)
	if strings.HasPrefix(normalizedPath, "\\") {
		normalizedPath = "c:" + normalizedPath
	}
	return normalizedPath
}

func getAvailableDriveLetter() (string, error) {
	cmd := "$used = Get-PSDrive | Select-Object -Expand Name | Where-Object { $_.Length -eq 1 }"
	cmd += ";$drive = 67..90 | ForEach-Object { [string][char]$_ } | Where-Object { $used -notcontains $_ } | Select-Object -First 1;$drive"
	output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("getAvailableDriveLetter failed: %v, output: %q", err, string(output))
	}

	if len(output) == 0 {
		return "", fmt.Errorf("azureMount: there is no available drive letter now")
	}
	return string(output)[:1], nil
}

// ValidateDiskNumber : disk number should be a number in [0, 99]
func ValidateDiskNumber(disk string) error {
	diskNum, err := strconv.Atoi(disk)
	if err != nil {
		return fmt.Errorf("wrong disk number format: %q, err:%v", disk, err)
	}

	if diskNum < 0 || diskNum > 99 {
		return fmt.Errorf("disk number out of range: %q", disk)
	}

	return nil
}

// Get drive letter according to windows disk number
func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
	cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
	output, err := exec.Run("powershell", "/c", cmd)
	if err != nil {
		return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
	}
	if len(string(output)) < 1 {
		return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
	}
	return string(output)[:1], nil
}
<commit_msg>only allow cifs mount on windows node<commit_after>// +build windows

/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package mount

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

	"github.com/golang/glog"
)

// Mounter provides the default implementation of mount.Interface
// for the windows platform.  This implementation assumes that the
// kubelet is running in the host's root mount namespace.
type Mounter struct {
	mounterPath string
}

// New returns a mount.Interface for the current system.
// It provides options to override the default mounter behavior.
// mounterPath allows using an alternative to `/bin/mount` for mounting.
func New(mounterPath string) Interface {
	return &Mounter{
		mounterPath: mounterPath,
	}
}

// Mount : mounts source to target as NTFS with given options.
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
	target = normalizeWindowsPath(target)

	if source == "tmpfs" {
		glog.V(3).Infof("azureMount: mounting source (%q), target (%q), with options (%q)", source, target, options)
		return os.MkdirAll(target, 0755)
	}

	parentDir := filepath.Dir(target)
	if err := os.MkdirAll(parentDir, 0755); err != nil {
		return err
	}

	glog.V(4).Infof("azureMount: mount options(%q) source:%q, target:%q, fstype:%q, begin to mount",
		options, source, target, fstype)
	bindSource := ""

	// tell it's going to mount azure disk or azure file according to options
	if bind, _ := isBind(options); bind {
		// mount azure disk
		bindSource = normalizeWindowsPath(source)
	} else {
		if len(options) < 2 {
			glog.Warningf("azureMount: mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
				options, len(options), source, target)
			return nil
		}

		// currently only cifs mount is supported
		if strings.ToLower(fstype) != "cifs" {
			return fmt.Errorf("azureMount: only cifs mount is supported now, fstype: %q, mounting source (%q), target (%q), with options (%q)", fstype, source, target, options)
		}

		cmdLine := fmt.Sprintf(`$User = "%s";$PWord = ConvertTo-SecureString -String "%s" -AsPlainText -Force;`+
			`$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord`,
			options[0], options[1])

		driverLetter, err := getAvailableDriveLetter()
		if err != nil {
			return err
		}
		bindSource = driverLetter + ":"
		cmdLine += fmt.Sprintf(";New-SmbGlobalMapping -LocalPath %s -RemotePath %s -Credential $Credential", bindSource, source)

		if output, err := exec.Command("powershell", "/c", cmdLine).CombinedOutput(); err != nil {
			// we don't return error here, even though New-SmbGlobalMapping failed, we still make it successful,
			// will return error when Windows 2016 RS3 is ready on azure
			glog.Errorf("azureMount: SmbGlobalMapping failed: %v, only SMB mount is supported now, output: %q", err, string(output))
			return os.MkdirAll(target, 0755)
		}
	}

	if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
		glog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
		return err
	}

	return nil
}

// Unmount unmounts the target.
func (mounter *Mounter) Unmount(target string) error {
	glog.V(4).Infof("azureMount: Unmount target (%q)", target)
	target = normalizeWindowsPath(target)
	if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
		glog.Errorf("rmdir failed: %v, output: %q", err, string(output))
		return err
	}
	return nil
}

// List returns a list of all mounted filesystems. todo
func (mounter *Mounter) List() ([]MountPoint, error) {
	return []MountPoint{}, nil
}

// IsMountPointMatch determines if the mountpoint matches the dir
func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
	return mp.Path == dir
}

// IsNotMountPoint determines if a directory is a mountpoint.
func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) {
	return IsNotMountPoint(mounter, dir)
}

// IsLikelyNotMountPoint determines if a directory is not a mountpoint.
func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
	stat, err := os.Lstat(file)
	if err != nil {
		return true, err
	}
	// If current file is a symlink, then it is a mountpoint.
	if stat.Mode()&os.ModeSymlink != 0 {
		return false, nil
	}

	return true, nil
}

// GetDeviceNameFromMount given a mnt point, find the device
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
	return getDeviceNameFromMount(mounter, mountPath, pluginDir)
}

// DeviceOpened determines if the device is in use elsewhere
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
	return false, nil
}

// PathIsDevice determines if a path is a device.
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
	return false, nil
}

// MakeRShared checks that given path is on a mount with 'rshared' mount
// propagation. Empty implementation here.
func (mounter *Mounter) MakeRShared(path string) error {
	return nil
}

func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
	// Try to mount the disk
	glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)

	if err := ValidateDiskNumber(source); err != nil {
		glog.Errorf("azureMount: formatAndMount failed, err: %v\n", err)
		return err
	}

	driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
	if err != nil {
		return err
	}
	driverPath := driveLetter + ":"
	target = normalizeWindowsPath(target)
	glog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
	if output, err := mounter.Exec.Run("cmd", "/c", "mklink", "/D", target, driverPath); err != nil {
		glog.Errorf("mklink failed: %v, output: %q", err, string(output))
		return err
	}
	return nil
}

func normalizeWindowsPath(path string) string {
	normalizedPath := strings.Replace(path, "/", "\\", -1)
	if strings.HasPrefix(normalizedPath, "\\") {
		normalizedPath = "c:" + normalizedPath
	}
	return normalizedPath
}

func getAvailableDriveLetter() (string, error) {
	cmd := "$used = Get-PSDrive | Select-Object -Expand Name | Where-Object { $_.Length -eq 1 }"
	cmd += ";$drive = 67..90 | ForEach-Object { [string][char]$_ } | Where-Object { $used -notcontains $_ } | Select-Object -First 1;$drive"
	output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("getAvailableDriveLetter failed: %v, output: %q", err, string(output))
	}

	if len(output) == 0 {
		return "", fmt.Errorf("azureMount: there is no available drive letter now")
	}
	return string(output)[:1], nil
}

// ValidateDiskNumber : disk number should be a number in [0, 99]
func ValidateDiskNumber(disk string) error {
	diskNum, err := strconv.Atoi(disk)
	if err != nil {
		return fmt.Errorf("wrong disk number format: %q, err:%v", disk, err)
	}

	if diskNum < 0 || diskNum > 99 {
		return fmt.Errorf("disk number out of range: %q", disk)
	}

	return nil
}

// Get drive letter according to windows disk number
func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
	cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
	output, err := exec.Run("powershell", "/c", cmd)
	if err != nil {
		return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
	}
	if len(string(output)) < 1 {
		return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
	}
	return string(output)[:1], nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/bborbe/backup/constants"
	"github.com/bborbe/backup/date"
	"github.com/golang/glog"
	"io/ioutil"
	"os"
	"time"
)

type host struct {
	Active      bool   `json:"active"`
	User        string `json:"user"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	Directory   string `json:"dir"`
	ExcludeFrom string `json:"exclude_from"`
}

func (h *host) Backup(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create backup of %s on target: %s", h.Host, targetDirectory)

	if err := h.createCurrentDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create current failed: %v", err)
		return err
	}

	found, err := h.todayHasAlreadyBackup(targetDirectory)
	if err != nil {
		glog.V(2).Infof("search for existing backups failed: %v", err)
		return err
	}
	if found {
		glog.V(2).Infof("skip backup => already exists")
		return nil
	}

	if err := h.createToDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create target directory failed: %v", err)
		return err
	}

	if err := h.rsync(targetDirectory); err != nil {
		glog.V(2).Infof("rsync failed: %v", err)
		return err
	}

	backupDate := time.Now()

	if err := h.renameIncompleteToDate(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("rename incomplete to date failed: %v", err)
		return err
	}

	if err := h.deleteCurrentSymlink(targetDirectory); err != nil {
		glog.V(2).Infof("delete current symlink failed: %v", err)
		return err
	}

	if err := h.createCurrentSymlink(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("create new current symlink failed: %v", err)
		return err
	}

	if err := h.deleteEmpty(targetDirectory); err != nil {
		glog.V(2).Infof("delete empty dir failed: %v", err)
		return err
	}

	glog.V(2).Infof("backup completed")
	return nil
}

func (h *host) rsync(targetDirectory targetDirectory) error {
	return runRsync(
		"-azP",
		"--no-p",
		"-e",
		fmt.Sprintf("ssh -o StrictHostKeyChecking=no -p %d", h.Port),
		"--delete",
		"--delete-excluded",
		fmt.Sprintf("--port=%d", h.Port),
		fmt.Sprintf("--exclude-from=%s", h.ExcludeFrom),
		fmt.Sprintf("--link-dest=%s", h.linkDest(targetDirectory)),
		h.from(),
		h.to(targetDirectory),
	)
}

func (h *host) renameIncompleteToDate(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("rename incomplete on target: %s", targetDirectory)
	return os.Rename(h.incomplete(targetDirectory), h.date(targetDirectory, date))
}

func (h *host) deleteCurrentSymlink(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete current symlink on target: %s", targetDirectory)
	return os.Remove(h.current(targetDirectory))
}

func (h *host) createCurrentSymlink(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("create current symlink on target: %s", targetDirectory)
	if err := os.Symlink(date.Format(constants.DATEFORMAT), h.current(targetDirectory)); err != nil {
		glog.V(2).Infof("create symlink to current failed: %v", err)
		return err
	}
	return nil
}

func (h *host) deleteEmpty(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete empty directory on target: %s", targetDirectory)
	os.RemoveAll(h.empty(targetDirectory))
	return nil
}

func (h *host) createCurrentDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create current directory on target: %s", targetDirectory)
	_, err := os.Stat(h.current(targetDirectory))
	if os.IsNotExist(err) {
		glog.V(2).Infof("current not existing")
		if err := os.MkdirAll(h.empty(targetDirectory)+h.Directory, 0700); err != nil {
			glog.V(2).Infof("create empty directory failed: %v", err)
			return err
		}
		if err := os.Symlink("empty", h.current(targetDirectory)); err != nil {
			glog.V(2).Infof("create symlink from empty to current failed: %v", err)
			return err
		}
	}
	glog.V(2).Infof("create current completed")
	return nil
}

func (h *host) todayHasAlreadyBackup(targetDirectory targetDirectory) (bool, error) {
	glog.V(2).Infof("search if backup already exists on target: %s", targetDirectory)
	hostDirectory := fmt.Sprintf("%s/%s", targetDirectory, h.Host)
	dirs, err := ioutil.ReadDir(hostDirectory)
	if err != nil {
		glog.V(2).Infof("read directory %s failed: %v", targetDirectory, err)
		return false, err
	}
	for _, dir := range dirs {
		timeOfDir, err := time.Parse(constants.DATEFORMAT, dir.Name())
		if err != nil {
			glog.V(2).Infof("parse date of dir failed")
			continue
		}
		if date.DayEqual(timeOfDir, time.Now()) {
			glog.V(2).Infof("found backup for today")
			return true, nil
		}
	}
	glog.V(2).Infof("found no backup for today")
	return false, nil
}

func (h *host) createToDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create to directory on target: %s", targetDirectory)
	return os.MkdirAll(h.to(targetDirectory), 0700)
}

func (h *host) from() string {
	return fmt.Sprintf("%s@%s:%s", h.User, h.Host, h.Directory)
}

func (h *host) to(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) incomplete(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete", targetDirectory, h.Host)
}

func (h *host) current(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current", targetDirectory, h.Host)
}

func (h *host) empty(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/empty", targetDirectory, h.Host)
}

func (h *host) date(targetDirectory targetDirectory, date time.Time) string {
	return fmt.Sprintf("%s/%s/%s", targetDirectory, h.Host, date.Format(constants.DATEFORMAT))
}

func (h *host) linkDest(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) Validate() error {
	glog.V(2).Infof("validate host: %s", h.Host)
	if len(h.User) == 0 {
		return fmt.Errorf("user invalid")
	}
	if len(h.Host) == 0 {
		return fmt.Errorf("host invalid")
	}
	if h.Port <= 0 {
		return fmt.Errorf("port invalid")
	}
	if len(h.Directory) == 0 {
		return fmt.Errorf("directory invalid")
	}
	if h.Directory[len(h.Directory)-1:] != "/" {
		return fmt.Errorf("directory not ending with '/'")
	}
	return nil
}
<commit_msg>update rsync/ssh args<commit_after>package main

import (
	"fmt"
	"github.com/bborbe/backup/constants"
	"github.com/bborbe/backup/date"
	"github.com/golang/glog"
	"io/ioutil"
	"os"
	"time"
)

type host struct {
	Active      bool   `json:"active"`
	User        string `json:"user"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	Directory   string `json:"dir"`
	ExcludeFrom string `json:"exclude_from"`
}

func (h *host) Backup(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create backup of %s on target: %s", h.Host, targetDirectory)

	if err := h.createCurrentDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create current failed: %v", err)
		return err
	}

	found, err := h.todayHasAlreadyBackup(targetDirectory)
	if err != nil {
		glog.V(2).Infof("search for existing backups failed: %v", err)
		return err
	}
	if found {
		glog.V(2).Infof("skip backup => already exists")
		return nil
	}

	if err := h.createToDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create target directory failed: %v", err)
		return err
	}

	if err := h.rsync(targetDirectory); err != nil {
		glog.V(2).Infof("rsync failed: %v", err)
		return err
	}

	backupDate := time.Now()

	if err := h.renameIncompleteToDate(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("rename incomplete to date failed: %v", err)
		return err
	}

	if err := h.deleteCurrentSymlink(targetDirectory); err != nil {
		glog.V(2).Infof("delete current symlink failed: %v", err)
		return err
	}

	if err := h.createCurrentSymlink(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("create new current symlink failed: %v", err)
		return err
	}

	if err := h.deleteEmpty(targetDirectory); err != nil {
		glog.V(2).Infof("delete empty dir failed: %v", err)
		return err
	}

	glog.V(2).Infof("backup completed")
	return nil
}

func (h *host) rsync(targetDirectory targetDirectory) error {
	return runRsync(
		"-azP",
		"--no-p",
		"--numeric-ids",
		"-e",
		fmt.Sprintf("ssh -T -x -o StrictHostKeyChecking=no -p %d", h.Port),
		"--delete",
		"--delete-excluded",
		fmt.Sprintf("--port=%d", h.Port),
		fmt.Sprintf("--exclude-from=%s", h.ExcludeFrom),
		fmt.Sprintf("--link-dest=%s", h.linkDest(targetDirectory)),
		h.from(),
		h.to(targetDirectory),
	)
}

func (h *host) renameIncompleteToDate(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("rename incomplete on target: %s", targetDirectory)
	return os.Rename(h.incomplete(targetDirectory), h.date(targetDirectory, date))
}

func (h *host) deleteCurrentSymlink(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete current symlink on target: %s", targetDirectory)
	return os.Remove(h.current(targetDirectory))
}

func (h *host) createCurrentSymlink(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("create current symlink on target: %s", targetDirectory)
	if err := os.Symlink(date.Format(constants.DATEFORMAT), h.current(targetDirectory)); err != nil {
		glog.V(2).Infof("create symlink to current failed: %v", err)
		return err
	}
	return nil
}

func (h *host) deleteEmpty(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete empty directory on target: %s", targetDirectory)
	os.RemoveAll(h.empty(targetDirectory))
	return nil
}

func (h *host) createCurrentDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create current directory on target: %s", targetDirectory)
	_, err := os.Stat(h.current(targetDirectory))
	if os.IsNotExist(err) {
		glog.V(2).Infof("current not existing")
		if err := os.MkdirAll(h.empty(targetDirectory)+h.Directory, 0700); err != nil {
			glog.V(2).Infof("create empty directory failed: %v", err)
			return err
		}
		if err := os.Symlink("empty", h.current(targetDirectory)); err != nil {
			glog.V(2).Infof("create symlink from empty to current failed: %v", err)
			return err
		}
	}
	glog.V(2).Infof("create current completed")
	return nil
}

func (h *host) todayHasAlreadyBackup(targetDirectory targetDirectory) (bool, error) {
	glog.V(2).Infof("search if backup already exists on target: %s", targetDirectory)
	hostDirectory := fmt.Sprintf("%s/%s", targetDirectory, h.Host)
	dirs, err := ioutil.ReadDir(hostDirectory)
	if err != nil {
		glog.V(2).Infof("read directory %s failed: %v", targetDirectory, err)
		return false, err
	}
	for _, dir := range dirs {
		timeOfDir, err := time.Parse(constants.DATEFORMAT, dir.Name())
		if err != nil {
			glog.V(2).Infof("parse date of dir failed")
			continue
		}
		if date.DayEqual(timeOfDir, time.Now()) {
			glog.V(2).Infof("found backup for today")
			return true, nil
		}
	}
	glog.V(2).Infof("found no backup for today")
	return false, nil
}

func (h *host) createToDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create to directory on target: %s", targetDirectory)
	return os.MkdirAll(h.to(targetDirectory), 0700)
}

func (h *host) from() string {
	return fmt.Sprintf("%s@%s:%s", h.User, h.Host, h.Directory)
}

func (h *host) to(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) incomplete(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete", targetDirectory, h.Host)
}

func (h *host) current(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current", targetDirectory, h.Host)
}

func (h *host) empty(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/empty", targetDirectory, h.Host)
}

func (h *host) date(targetDirectory targetDirectory, date time.Time) string {
	return fmt.Sprintf("%s/%s/%s", targetDirectory, h.Host, date.Format(constants.DATEFORMAT))
}

func (h *host) linkDest(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) Validate() error {
	glog.V(2).Infof("validate host: %s", h.Host)
	if len(h.User) == 0 {
		return fmt.Errorf("user invalid")
	}
	if len(h.Host) == 0 {
		return fmt.Errorf("host invalid")
	}
	if h.Port <= 0 {
		return fmt.Errorf("port invalid")
	}
	if len(h.Directory) == 0 {
		return fmt.Errorf("directory invalid")
	}
	if h.Directory[len(h.Directory)-1:] != "/" {
		return fmt.Errorf("directory not ending with '/'")
	}
	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/aws/awserr"
	"github.com/awslabs/aws-sdk-go/service/autoscaling"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
	var group autoscaling.Group
	var lc autoscaling.LaunchConfiguration

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSAutoScalingGroupHealthyCapacity(&group, 2),
					testAccCheckAWSAutoScalingGroupAttributes(&group),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "name", "foobar3-terraform-test"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "max_size", "5"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "min_size", "2"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "health_check_grace_period", "300"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "health_check_type", "ELB"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "desired_capacity", "4"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "force_delete", "true"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "termination_policies.912102603", "OldestInstance"),
				),
			},

			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.new", &lc),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "desired_capacity", "5"),
					testLaunchConfigurationName("aws_autoscaling_group.bar", &lc),
					testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{
						"value":               "bar-foo",
						"propagate_at_launch": true,
					}),
				),
			},
		},
	})
}

func TestAccAWSAutoScalingGroup_tags(t *testing.T) {
	var group autoscaling.Group

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAutoscalingTags(&group.Tags, "Foo", map[string]interface{}{
						"value":               "foo-bar",
						"propagate_at_launch": true,
					}),
				),
			},

			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAutoscalingTagNotExists(&group.Tags, "Foo"),
					testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{
						"value":               "bar-foo",
						"propagate_at_launch": true,
					}),
				),
			},
		},
	})
}

func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) {
	var group autoscaling.Group

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigWithLoadBalancer,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSAutoScalingGroupAttributesLoadBalancer(&group),
				),
			},
		},
	})
}

func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).autoscalingconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_autoscaling_group" {
			continue
		}

		// Try to find the Group
		describeGroups, err := conn.DescribeAutoScalingGroups(
			&autoscaling.DescribeAutoScalingGroupsInput{
				AutoScalingGroupNames: []*string{aws.String(rs.Primary.ID)},
			})

		if err == nil {
			if len(describeGroups.AutoScalingGroups) != 0 &&
				*describeGroups.AutoScalingGroups[0].AutoScalingGroupName == rs.Primary.ID {
				return fmt.Errorf("AutoScaling Group still exists")
			}
		}

		// Verify the error
		ec2err, ok := err.(awserr.Error)
		if !ok {
			return err
		}
		if ec2err.Code() != "InvalidGroup.NotFound" {
			return err
		}
	}

	return nil
}

func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if *group.AvailabilityZones[0] != "us-west-2a" {
			return fmt.Errorf("Bad availability_zones: %#v", group.AvailabilityZones[0])
		}

		if *group.AutoScalingGroupName != "foobar3-terraform-test" {
			return fmt.Errorf("Bad name: %s", *group.AutoScalingGroupName)
		}

		if *group.MaxSize != 5 {
			return fmt.Errorf("Bad max_size: %d", *group.MaxSize)
		}

		if *group.MinSize != 2 {
			return fmt.Errorf("Bad max_size: %d", *group.MinSize)
		}

		if *group.HealthCheckType != "ELB" {
			return fmt.Errorf("Bad health_check_type,\nexpected: %s\ngot: %s", "ELB", *group.HealthCheckType)
		}

		if *group.HealthCheckGracePeriod != 300 {
			return fmt.Errorf("Bad health_check_grace_period: %d", *group.HealthCheckGracePeriod)
		}

		if *group.DesiredCapacity != 4 {
			return fmt.Errorf("Bad desired_capacity: %d", *group.DesiredCapacity)
		}

		if *group.LaunchConfigurationName == "" {
			return fmt.Errorf("Bad launch configuration name: %s", *group.LaunchConfigurationName)
		}

		t := &autoscaling.TagDescription{
			Key:               aws.String("Foo"),
			Value:             aws.String("foo-bar"),
			PropagateAtLaunch: aws.Boolean(true),
			ResourceType:      aws.String("auto-scaling-group"),
			ResourceID:        group.AutoScalingGroupName,
		}

		if !reflect.DeepEqual(group.Tags[0], t) {
			return fmt.Errorf(
				"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
				group.Tags[0],
				t)
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupAttributesLoadBalancer(group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if *group.LoadBalancerNames[0] != "foobar-terraform-test" {
			return fmt.Errorf("Bad load_balancers: %#v", group.LoadBalancerNames[0])
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupExists(n string, group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No AutoScaling Group ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn

		describeGroups, err := conn.DescribeAutoScalingGroups(
			&autoscaling.DescribeAutoScalingGroupsInput{
				AutoScalingGroupNames: []*string{aws.String(rs.Primary.ID)},
			})

		if err != nil {
			return err
		}

		if len(describeGroups.AutoScalingGroups) != 1 ||
			*describeGroups.AutoScalingGroups[0].AutoScalingGroupName != rs.Primary.ID {
			return fmt.Errorf("AutoScaling Group not found")
		}

		*group = *describeGroups.AutoScalingGroups[0]

		return nil
	}
}

func testLaunchConfigurationName(n string, lc *autoscaling.LaunchConfiguration) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if *lc.LaunchConfigurationName != rs.Primary.Attributes["launch_configuration"] {
			return fmt.Errorf("Launch configuration names do not match")
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupHealthyCapacity(
	g *autoscaling.Group, exp int) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		healthy := 0
		for _, i := range g.Instances {
			if i.HealthStatus == nil {
				continue
			}
			if strings.EqualFold(*i.HealthStatus, "Healthy") {
				healthy++
			}
		}
		if healthy < exp {
			return fmt.Errorf("Expected at least %d healthy, got %d.", exp, healthy)
		}
		return nil
	}
}

const testAccAWSAutoScalingGroupConfig = `
resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar3-terraform-test"
  max_size = 5
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 4
  force_delete = true
  termination_policies = ["OldestInstance"]

  launch_configuration = "${aws_launch_configuration.foobar.name}"

  tag {
    key = "Foo"
    value = "foo-bar"
    propagate_at_launch = true
  }
}
`

const testAccAWSAutoScalingGroupConfigUpdate = `
resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_launch_configuration" "new" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar3-terraform-test"
  max_size = 5
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 5
  force_delete = true

  launch_configuration = "${aws_launch_configuration.new.name}"

  tag {
    key = "Bar"
    value = "bar-foo"
    propagate_at_launch = true
  }
}
`

const testAccAWSAutoScalingGroupConfigWithLoadBalancer = `
resource "aws_elb" "bar" {
  name = "foobar-terraform-test"
  availability_zones = ["us-west-2a"]

  listener {
    instance_port = 80
    instance_protocol = "http"
    lb_port = 80
    lb_protocol = "http"
  }

  health_check {
    healthy_threshold = 2
    unhealthy_threshold = 2
    target = "HTTP:80/"
    interval = 5
    timeout = 2
  }
}

resource "aws_launch_configuration" "foobar" {
  // need an AMI that listens on :80 at boot, this is:
  // bitnami-nginxstack-1.6.1-0-linux-ubuntu-14.04.1-x86_64-hvm-ebs-ami-99f5b1a9-3
  image_id = "ami-b5b3fc85"
  instance_type = "t2.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar3-terraform-test"
  max_size = 2
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  min_elb_capacity = 1
  force_delete = true

  launch_configuration = "${aws_launch_configuration.foobar.name}"
  load_balancers = ["${aws_elb.bar.name}"]
}
`
<commit_msg>provider/aws: remove asg test dependence on default SG<commit_after>package aws

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/aws/awserr"
	"github.com/awslabs/aws-sdk-go/service/autoscaling"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
	var group autoscaling.Group
	var lc autoscaling.LaunchConfiguration

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSAutoScalingGroupHealthyCapacity(&group, 2),
					testAccCheckAWSAutoScalingGroupAttributes(&group),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "name", "foobar3-terraform-test"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "max_size", "5"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "min_size", "2"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "health_check_grace_period", "300"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "health_check_type", "ELB"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "desired_capacity", "4"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "force_delete", "true"),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "termination_policies.912102603", "OldestInstance"),
				),
			},

			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.new", &lc),
					resource.TestCheckResourceAttr(
						"aws_autoscaling_group.bar", "desired_capacity", "5"),
					testLaunchConfigurationName("aws_autoscaling_group.bar", &lc),
					testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{
						"value":               "bar-foo",
						"propagate_at_launch": true,
					}),
				),
			},
		},
	})
}

func TestAccAWSAutoScalingGroup_tags(t *testing.T) {
	var group autoscaling.Group

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAutoscalingTags(&group.Tags, "Foo", map[string]interface{}{
						"value":               "foo-bar",
						"propagate_at_launch": true,
					}),
				),
			},

			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAutoscalingTagNotExists(&group.Tags, "Foo"),
					testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{
						"value":               "bar-foo",
						"propagate_at_launch": true,
					}),
				),
			},
		},
	})
}

func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) {
	var group autoscaling.Group

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSAutoScalingGroupConfigWithLoadBalancer,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
					testAccCheckAWSAutoScalingGroupAttributesLoadBalancer(&group),
				),
			},
		},
	})
}

func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).autoscalingconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_autoscaling_group" {
			continue
		}

		// Try to find the Group
		describeGroups, err := conn.DescribeAutoScalingGroups(
			&autoscaling.DescribeAutoScalingGroupsInput{
				AutoScalingGroupNames: []*string{aws.String(rs.Primary.ID)},
			})

		if err == nil {
			if len(describeGroups.AutoScalingGroups) != 0 &&
				*describeGroups.AutoScalingGroups[0].AutoScalingGroupName == rs.Primary.ID {
				return fmt.Errorf("AutoScaling Group still exists")
			}
		}

		// Verify the error
		ec2err, ok := err.(awserr.Error)
		if !ok {
			return err
		}
		if ec2err.Code() != "InvalidGroup.NotFound" {
			return err
		}
	}

	return nil
}

func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if *group.AvailabilityZones[0] != "us-west-2a" {
			return fmt.Errorf("Bad availability_zones: %#v", group.AvailabilityZones[0])
		}

		if *group.AutoScalingGroupName != "foobar3-terraform-test" {
			return fmt.Errorf("Bad name: %s", *group.AutoScalingGroupName)
		}

		if *group.MaxSize != 5 {
			return fmt.Errorf("Bad max_size: %d", *group.MaxSize)
		}

		if *group.MinSize != 2 {
			return fmt.Errorf("Bad max_size: %d", *group.MinSize)
		}

		if *group.HealthCheckType != "ELB" {
			return fmt.Errorf("Bad health_check_type,\nexpected: %s\ngot: %s", "ELB", *group.HealthCheckType)
		}

		if *group.HealthCheckGracePeriod != 300 {
			return fmt.Errorf("Bad health_check_grace_period: %d", *group.HealthCheckGracePeriod)
		}

		if *group.DesiredCapacity != 4 {
			return fmt.Errorf("Bad desired_capacity: %d", *group.DesiredCapacity)
		}

		if *group.LaunchConfigurationName == "" {
			return fmt.Errorf("Bad launch configuration name: %s", *group.LaunchConfigurationName)
		}

		t := &autoscaling.TagDescription{
			Key:               aws.String("Foo"),
			Value:             aws.String("foo-bar"),
			PropagateAtLaunch: aws.Boolean(true),
			ResourceType:      aws.String("auto-scaling-group"),
			ResourceID:        group.AutoScalingGroupName,
		}

		if !reflect.DeepEqual(group.Tags[0], t) {
			return fmt.Errorf(
				"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
				group.Tags[0],
				t)
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupAttributesLoadBalancer(group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if *group.LoadBalancerNames[0] != "foobar-terraform-test" {
			return fmt.Errorf("Bad load_balancers: %#v", group.LoadBalancerNames[0])
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupExists(n string, group *autoscaling.Group) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No AutoScaling Group ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).autoscalingconn

		describeGroups, err := conn.DescribeAutoScalingGroups(
			&autoscaling.DescribeAutoScalingGroupsInput{
				AutoScalingGroupNames: []*string{aws.String(rs.Primary.ID)},
			})

		if err != nil {
			return err
		}

		if len(describeGroups.AutoScalingGroups) != 1 ||
			*describeGroups.AutoScalingGroups[0].AutoScalingGroupName != rs.Primary.ID {
			return fmt.Errorf("AutoScaling Group not found")
		}

		*group = *describeGroups.AutoScalingGroups[0]

		return nil
	}
}

func testLaunchConfigurationName(n string, lc *autoscaling.LaunchConfiguration) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if *lc.LaunchConfigurationName != rs.Primary.Attributes["launch_configuration"] {
			return fmt.Errorf("Launch configuration names do not match")
		}

		return nil
	}
}

func testAccCheckAWSAutoScalingGroupHealthyCapacity(
	g *autoscaling.Group, exp int) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		healthy := 0
		for _, i := range g.Instances {
			if i.HealthStatus == nil {
				continue
			}
			if strings.EqualFold(*i.HealthStatus, "Healthy") {
				healthy++
			}
		}
		if healthy < exp {
			return fmt.Errorf("Expected at least %d healthy, got %d.", exp, healthy)
		}
		return nil
	}
}

const testAccAWSAutoScalingGroupConfig = `
resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar3-terraform-test"
  max_size = 5
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 4
  force_delete = true
  termination_policies = ["OldestInstance"]

  launch_configuration = "${aws_launch_configuration.foobar.name}"

  tag {
    key = "Foo"
    value = "foo-bar"
    propagate_at_launch = true
  }
}
`

const testAccAWSAutoScalingGroupConfigUpdate = `
resource "aws_launch_configuration" "foobar" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_launch_configuration" "new" {
  image_id = "ami-21f78e11"
  instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["us-west-2a"]
  name = "foobar3-terraform-test"
  max_size = 5
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  desired_capacity = 5
  force_delete = true

  launch_configuration = "${aws_launch_configuration.new.name}"

  tag {
    key = "Bar"
    value = "bar-foo"
    propagate_at_launch = true
  }
}
`

const testAccAWSAutoScalingGroupConfigWithLoadBalancer = `
resource "aws_vpc" "foo" {
  cidr_block = "10.1.0.0/16"
	tags { Name = "tf-asg-test" }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.foo.id}"
}

resource "aws_subnet" "foo" {
	cidr_block = "10.1.1.0/24"
	vpc_id = "${aws_vpc.foo.id}"
}

resource "aws_security_group" "foo" {
  vpc_id="${aws_vpc.foo.id}"

  ingress {
    protocol = "-1"
    from_port = 0
    to_port = 0
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    protocol = "-1"
    from_port = 0
    to_port = 0
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_elb" "bar" {
  name = "foobar-terraform-test"
  subnets = ["${aws_subnet.foo.id}"]
	security_groups = ["${aws_security_group.foo.id}"]

  listener {
    instance_port = 80
    instance_protocol = "http"
    lb_port = 80
    lb_protocol = "http"
  }

  health_check {
    healthy_threshold = 2
    unhealthy_threshold = 2
    target = "HTTP:80/"
    interval = 5
    timeout = 2
  }

	depends_on = ["aws_internet_gateway.gw"]
}

resource "aws_launch_configuration" "foobar" {
  // need an AMI that listens on :80 at boot, this is:
  // bitnami-nginxstack-1.6.1-0-linux-ubuntu-14.04.1-x86_64-hvm-ebs-ami-99f5b1a9-3
  image_id = "ami-b5b3fc85"
  instance_type = "t2.micro"
	security_groups = ["${aws_security_group.foo.id}"]
}

resource "aws_autoscaling_group" "bar" {
  availability_zones = ["${aws_subnet.foo.availability_zone}"]
	vpc_zone_identifier = ["${aws_subnet.foo.id}"]
  name = "foobar3-terraform-test"
  max_size = 2
  min_size = 2
  health_check_grace_period = 300
  health_check_type = "ELB"
  min_elb_capacity = 2
  force_delete = true

  launch_configuration = "${aws_launch_configuration.foobar.name}"
  load_balancers = ["${aws_elb.bar.name}"]
}
`
<|endoftext|>


<commit_before>package cloudstack

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/xanzy/go-cloudstack/cloudstack"
)

func resourceCloudStackTemplate() *schema.Resource {
	return &schema.Resource{
		Create: resourceCloudStackTemplateCreate,
		Read:   resourceCloudStackTemplateRead,
		Update: resourceCloudStackTemplateUpdate,
		Delete: resourceCloudStackTemplateDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"display_text": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"format": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"hypervisor": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"os_type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"url": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"zone": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"is_dynamically_scalable": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"is_extractable": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"is_featured": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"is_public": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"password_enabled": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"is_ready": &schema.Schema{
				Type:     schema.TypeBool,
				Computed: true,
			},

			"is_ready_timeout": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  300,
			},
		},
	}
}

func resourceCloudStackTemplateCreate(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	if err := verifyTemplateParams(d); err != nil {
		return err
	}

	name := d.Get("name").(string)

	// Compute/set the display text
	displaytext := d.Get("display_text").(string)
	if displaytext == "" {
		displaytext = name
	}

	// Retrieve the os_type UUID
	ostypeid, e := retrieveUUID(cs, "os_type", d.Get("os_type").(string))
	if e != nil {
		return e.Error()
	}

	// Retrieve the zone UUID
	zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
	if e != nil {
		return e.Error()
	}

	// Create a new parameter struct
	p := cs.Template.NewRegisterTemplateParams(
		displaytext,
		d.Get("format").(string),
		d.Get("hypervisor").(string),
		name,
		ostypeid,
		d.Get("url").(string),
		zoneid)

	// Set optional parameters
	if v, ok := d.GetOk("is_dynamically_scalable"); ok {
		p.SetIsdynamicallyscalable(v.(bool))
	}

	if v, ok := d.GetOk("is_extractable"); ok {
		p.SetIsextractable(v.(bool))
	}

	if v, ok := d.GetOk("is_featured"); ok {
		p.SetIsfeatured(v.(bool))
	}

	if v, ok := d.GetOk("is_public"); ok {
		p.SetIspublic(v.(bool))
	}

	if v, ok := d.GetOk("password_enabled"); ok {
		p.SetPasswordenabled(v.(bool))
	}

	// If there is a project supplied, we retrieve and set the project id
	if project, ok := d.GetOk("project"); ok {
		// Retrieve the project UUID
		projectid, e := retrieveUUID(cs, "project", project.(string))
		if e != nil {
			return e.Error()
		}
		// Set the default project ID
		p.SetProjectid(projectid)
	}

	// Create the new template
	r, err := cs.Template.RegisterTemplate(p)
	if err != nil {
		return fmt.Errorf("Error creating template %s: %s", name, err)
	}

	d.SetId(r.RegisterTemplate[0].Id)

	// Wait until the template is ready to use, or timeout with an error...
	currentTime := time.Now().Unix()
	timeout := int64(d.Get("is_ready_timeout").(int))
	for {
		// Start with the sleep so the register action has a few seconds
		// to process the registration correctly. Without this wait
		time.Sleep(10 * time.Second)

		err := resourceCloudStackTemplateRead(d, meta)
		if err != nil {
			return err
		}

		if d.Get("is_ready").(bool) {
			return nil
		}

		if time.Now().Unix()-currentTime > timeout {
			return fmt.Errorf("Timeout while waiting for template to become ready")
		}
	}
}

func resourceCloudStackTemplateRead(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	// Get the template details
	t, count, err := cs.Template.GetTemplateByID(d.Id(), "executable")
	if err != nil {
		if count == 0 {
			log.Printf(
				"[DEBUG] Template %s no longer exists", d.Get("name").(string))
			d.SetId("")
			return nil
		}

		return err
	}

	d.Set("name", t.Name)
	d.Set("display_text", t.Displaytext)
	d.Set("format", t.Format)
	d.Set("hypervisor", t.Hypervisor)
	d.Set("is_dynamically_scalable", t.Isdynamicallyscalable)
	d.Set("is_extractable", t.Isextractable)
	d.Set("is_featured", t.Isfeatured)
	d.Set("is_public", t.Ispublic)
	d.Set("password_enabled", t.Passwordenabled)
	d.Set("is_ready", t.Isready)

	setValueOrUUID(d, "project", t.Project, t.Projectid)

	if t.Zoneid == "" {
		setValueOrUUID(d, "zone", t.Zonename, UnlimitedResourceID)
	} else {
		setValueOrUUID(d, "zone", t.Zonename, t.Zoneid)
	}

	setValueOrUUID(d, "os_type", t.Ostypename, t.Ostypeid)

	return nil
}

func resourceCloudStackTemplateUpdate(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)
	name := d.Get("name").(string)

	// Create a new parameter struct
	p := cs.Template.NewUpdateTemplateParams(d.Id())

	if d.HasChange("name") {
		p.SetName(name)
	}

	if d.HasChange("display_text") {
		p.SetDisplaytext(d.Get("display_text").(string))
	}

	if d.HasChange("format") {
		p.SetFormat(d.Get("format").(string))
	}

	if d.HasChange("is_dynamically_scalable") {
		p.SetIsdynamicallyscalable(d.Get("is_dynamically_scalable").(bool))
	}

	if d.HasChange("os_type") {
		ostypeid, e := retrieveUUID(cs, "os_type", d.Get("os_type").(string))
		if e != nil {
			return e.Error()
		}
		p.SetOstypeid(ostypeid)
	}

	if d.HasChange("password_enabled") {
		p.SetPasswordenabled(d.Get("password_enabled").(bool))
	}

	_, err := cs.Template.UpdateTemplate(p)
	if err != nil {
		return fmt.Errorf("Error updating template %s: %s", name, err)
	}

	return resourceCloudStackTemplateRead(d, meta)
}

func resourceCloudStackTemplateDelete(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	// Create a new parameter struct
	p := cs.Template.NewDeleteTemplateParams(d.Id())

	// Delete the template
	log.Printf("[INFO] Deleting template: %s", d.Get("name").(string))
	_, err := cs.Template.DeleteTemplate(p)
	if err != nil {
		// This is a very poor way to be told the UUID does no longer exist :(
		if strings.Contains(err.Error(), fmt.Sprintf(
			"Invalid parameter id value=%s due to incorrect long value format, "+
				"or entity does not exist", d.Id())) {
			return nil
		}

		return fmt.Errorf("Error deleting template %s: %s", d.Get("name").(string), err)
	}
	return nil
}

func verifyTemplateParams(d *schema.ResourceData) error {
	format := d.Get("format").(string)
	if format != "OVA" && format != "QCOW2" && format != "RAW" && format != "VHD" && format != "VMDK" {
		return fmt.Errorf(
			"%s is not a valid format. Valid options are 'OVA','QCOW2', 'RAW', 'VHD' and 'VMDK'", format)
	}

	return nil
}
<commit_msg>Update conditional to set UnlimitedResourceID for Zonename as well as Zoneid<commit_after>package cloudstack

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/xanzy/go-cloudstack/cloudstack"
)

func resourceCloudStackTemplate() *schema.Resource {
	return &schema.Resource{
		Create: resourceCloudStackTemplateCreate,
		Read:   resourceCloudStackTemplateRead,
		Update: resourceCloudStackTemplateUpdate,
		Delete: resourceCloudStackTemplateDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"display_text": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},

			"format": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"hypervisor": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"os_type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"url": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"project": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"zone": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"is_dynamically_scalable": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"is_extractable": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"is_featured": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"is_public": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"password_enabled": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},

			"is_ready": &schema.Schema{
				Type:     schema.TypeBool,
				Computed: true,
			},

			"is_ready_timeout": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  300,
			},
		},
	}
}

func resourceCloudStackTemplateCreate(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	if err := verifyTemplateParams(d); err != nil {
		return err
	}

	name := d.Get("name").(string)

	// Compute/set the display text
	displaytext := d.Get("display_text").(string)
	if displaytext == "" {
		displaytext = name
	}

	// Retrieve the os_type UUID
	ostypeid, e := retrieveUUID(cs, "os_type", d.Get("os_type").(string))
	if e != nil {
		return e.Error()
	}

	// Retrieve the zone UUID
	zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
	if e != nil {
		return e.Error()
	}

	// Create a new parameter struct
	p := cs.Template.NewRegisterTemplateParams(
		displaytext,
		d.Get("format").(string),
		d.Get("hypervisor").(string),
		name,
		ostypeid,
		d.Get("url").(string),
		zoneid)

	// Set optional parameters
	if v, ok := d.GetOk("is_dynamically_scalable"); ok {
		p.SetIsdynamicallyscalable(v.(bool))
	}

	if v, ok := d.GetOk("is_extractable"); ok {
		p.SetIsextractable(v.(bool))
	}

	if v, ok := d.GetOk("is_featured"); ok {
		p.SetIsfeatured(v.(bool))
	}

	if v, ok := d.GetOk("is_public"); ok {
		p.SetIspublic(v.(bool))
	}

	if v, ok := d.GetOk("password_enabled"); ok {
		p.SetPasswordenabled(v.(bool))
	}

	// If there is a project supplied, we retrieve and set the project id
	if project, ok := d.GetOk("project"); ok {
		// Retrieve the project UUID
		projectid, e := retrieveUUID(cs, "project", project.(string))
		if e != nil {
			return e.Error()
		}
		// Set the default project ID
		p.SetProjectid(projectid)
	}

	// Create the new template
	r, err := cs.Template.RegisterTemplate(p)
	if err != nil {
		return fmt.Errorf("Error creating template %s: %s", name, err)
	}

	d.SetId(r.RegisterTemplate[0].Id)

	// Wait until the template is ready to use, or timeout with an error...
	currentTime := time.Now().Unix()
	timeout := int64(d.Get("is_ready_timeout").(int))
	for {
		// Start with the sleep so the register action has a few seconds
		// to process the registration correctly. Without this wait
		time.Sleep(10 * time.Second)

		err := resourceCloudStackTemplateRead(d, meta)
		if err != nil {
			return err
		}

		if d.Get("is_ready").(bool) {
			return nil
		}

		if time.Now().Unix()-currentTime > timeout {
			return fmt.Errorf("Timeout while waiting for template to become ready")
		}
	}
}

func resourceCloudStackTemplateRead(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	// Get the template details
	t, count, err := cs.Template.GetTemplateByID(d.Id(), "executable")
	if err != nil {
		if count == 0 {
			log.Printf(
				"[DEBUG] Template %s no longer exists", d.Get("name").(string))
			d.SetId("")
			return nil
		}

		return err
	}

	d.Set("name", t.Name)
	d.Set("display_text", t.Displaytext)
	d.Set("format", t.Format)
	d.Set("hypervisor", t.Hypervisor)
	d.Set("is_dynamically_scalable", t.Isdynamicallyscalable)
	d.Set("is_extractable", t.Isextractable)
	d.Set("is_featured", t.Isfeatured)
	d.Set("is_public", t.Ispublic)
	d.Set("password_enabled", t.Passwordenabled)
	d.Set("is_ready", t.Isready)

	setValueOrUUID(d, "project", t.Project, t.Projectid)

	if t.Zoneid == "" {
		setValueOrUUID(d, "zone", UnlimitedResourceID, UnlimitedResourceID)
	} else {
		setValueOrUUID(d, "zone", t.Zonename, t.Zoneid)
	}

	setValueOrUUID(d, "os_type", t.Ostypename, t.Ostypeid)

	return nil
}

func resourceCloudStackTemplateUpdate(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)
	name := d.Get("name").(string)

	// Create a new parameter struct
	p := cs.Template.NewUpdateTemplateParams(d.Id())

	if d.HasChange("name") {
		p.SetName(name)
	}

	if d.HasChange("display_text") {
		p.SetDisplaytext(d.Get("display_text").(string))
	}

	if d.HasChange("format") {
		p.SetFormat(d.Get("format").(string))
	}

	if d.HasChange("is_dynamically_scalable") {
		p.SetIsdynamicallyscalable(d.Get("is_dynamically_scalable").(bool))
	}

	if d.HasChange("os_type") {
		ostypeid, e := retrieveUUID(cs, "os_type", d.Get("os_type").(string))
		if e != nil {
			return e.Error()
		}
		p.SetOstypeid(ostypeid)
	}

	if d.HasChange("password_enabled") {
		p.SetPasswordenabled(d.Get("password_enabled").(bool))
	}

	_, err := cs.Template.UpdateTemplate(p)
	if err != nil {
		return fmt.Errorf("Error updating template %s: %s", name, err)
	}

	return resourceCloudStackTemplateRead(d, meta)
}

func resourceCloudStackTemplateDelete(d *schema.ResourceData, meta interface{}) error {
	cs := meta.(*cloudstack.CloudStackClient)

	// Create a new parameter struct
	p := cs.Template.NewDeleteTemplateParams(d.Id())

	// Delete the template
	log.Printf("[INFO] Deleting template: %s", d.Get("name").(string))
	_, err := cs.Template.DeleteTemplate(p)
	if err != nil {
		// This is a very poor way to be told the UUID does no longer exist :(
		if strings.Contains(err.Error(), fmt.Sprintf(
			"Invalid parameter id value=%s due to incorrect long value format, "+
				"or entity does not exist", d.Id())) {
			return nil
		}

		return fmt.Errorf("Error deleting template %s: %s", d.Get("name").(string), err)
	}
	return nil
}

func verifyTemplateParams(d *schema.ResourceData) error {
	format := d.Get("format").(string)
	if format != "OVA" && format != "QCOW2" && format != "RAW" && format != "VHD" && format != "VMDK" {
		return fmt.Errorf(
			"%s is not a valid format. Valid options are 'OVA','QCOW2', 'RAW', 'VHD' and 'VMDK'", format)
	}

	return nil
}
<|endoftext|>