<commit_before>/*
Copyright 2020 The cert-manager 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 trigger

import (
	"context"
	"fmt"
	"testing"
	"time"

	logtest "github.com/go-logr/logr/testing"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	coretesting "k8s.io/client-go/testing"
	fakeclock "k8s.io/utils/clock/testing"

	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificates/trigger/policies"
	testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
	"github.com/jetstack/cert-manager/test/unit/gen"
)

func Test_controller_ProcessItem(t *testing.T) {
	fixedNow := metav1.NewTime(time.Now())

	tests := map[string]struct {
		// key that should be passed to ProcessItem. If not set, the
		// 'namespace/name' of the 'Certificate' field will be used. If neither
		// is set, the key will be "".
		key string

		// Certificate to be synced for the test. if not set, the 'key' will be
		// passed to ProcessItem instead.
		existingCertificate *cmapi.Certificate

		mockDataForCertificateReturn    policies.Input
		mockDataForCertificateReturnErr error
		wantDataForCertificateCalled    bool

		mockShouldReissue       func(t *testing.T) policies.Func
		wantShouldReissueCalled bool

		// wantEvent, if set, is an 'event string' that is expected to be fired.
		// For example, "Normal Issuing Re-issuance forced by unit test case"
		// where 'Normal' is the event severity, 'Issuing' is the reason and the
		// remainder is the message.
		wantEvent string

		// wantConditions is the expected set of conditions on the Certificate
		// resource if an Update is made.
		// If nil, no update is expected.
		// If empty, an update to the empty set/nil is expected.
		wantConditions []cmapi.CertificateCondition

		// wantErr is the expected error text returned by the controller, if any.
		wantErr string
	}{
		"do nothing if an empty 'key' is used": {},
		"do nothing if an invalid 'key' is used": {
			key: "abc/def/ghi",
		},
		"do nothing if a key references a Certificate that does not exist": {
			key: "namespace/name",
		},
		"should do nothing if Certificate already has 'Issuing' condition": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
					Type:               "Issuing",
					Status:             "True",
					ObservedGeneration: 3,
				}),
			),
		},
		"should call shouldReissue with the correct cert, secret and current CR": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateSecretName("secret-1"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(2),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{
				Secret: gen.Secret("secret-1", gen.SetSecretNamespace("testns")),
				CurrentRevisionRequest: gen.CertificateRequest("cr-1", gen.SetCertificateRequestNamespace("testns"),
					gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("cert-1", "cert-1-uid")),
					gen.SetCertificateRequestAnnotations(map[string]string{"cert-manager.io/certificate-revision": "2"}),
				),
			},
			wantShouldReissueCalled: true,
			mockShouldReissue: func(t *testing.T) policies.Func {
				return func(gotInput policies.Input) (string, string, bool) {
					expectInput := policies.Input{
						Certificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
							gen.SetCertificateSecretName("secret-1"),
							gen.SetCertificateGeneration(42),
							gen.SetCertificateUID("cert-1-uid"),
							gen.SetCertificateRevision(2),
						),
						CurrentRevisionRequest: gen.CertificateRequest("cr-1", gen.SetCertificateRequestNamespace("testns"),
							gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("cert-1", "cert-1-uid")),
							gen.SetCertificateRequestAnnotations(map[string]string{"cert-manager.io/certificate-revision": "2"}),
						),
						Secret: gen.Secret("secret-1", gen.SetSecretNamespace("testns")),
					}
					assert.Equal(t, expectInput, gotInput)
					return "", "", false
				}
			},
		},
		"should log error when dataForCertificate errors": {
			existingCertificate:             gen.Certificate("cert-1", gen.SetCertificateNamespace("testns")),
			wantDataForCertificateCalled:    true,
			mockDataForCertificateReturnErr: fmt.Errorf("dataForCertificate failed"),
			wantErr:                         "dataForCertificate failed",
		},
		"should set Issuing=True if shouldReissue tells us to reissue": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      true,
			mockShouldReissue: func(*testing.T) policies.Func {
				return func(policies.Input) (string, string, bool) {
					return "ForceTriggered", "Re-issuance forced by unit test case", true
				}
			},
			wantEvent: "Normal Issuing Re-issuance forced by unit test case",
			wantConditions: []cmapi.CertificateCondition{{
				Type:               "Issuing",
				Status:             "True",
				Reason:             "ForceTriggered",
				Message:            "Re-issuance forced by unit test case",
				LastTransitionTime: &fixedNow,
				ObservedGeneration: 42,
			}},
		},
		"should not set Issuing=True when cert has been failing for 59 minutes": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-59*time.Minute))),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      false,
		},
		"should set Issuing=True when cert has been failing for 61 minutes and shouldReissue returns true": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-61*time.Minute))),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      true,
			mockShouldReissue: func(*testing.T) policies.Func {
				return func(policies.Input) (string, string, bool) {
					return "ForceTriggered", "Re-issuance forced by unit test case", true
				}
			},
			wantEvent: "Normal Issuing Re-issuance forced by unit test case",
			wantConditions: []cmapi.CertificateCondition{{
				Type:               "Issuing",
				Status:             "True",
				Reason:             "ForceTriggered",
				Message:            "Re-issuance forced by unit test case",
				LastTransitionTime: &fixedNow,
				ObservedGeneration: 42,
			}},
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			builder := &testpkg.Builder{
				T:     t,
				Clock: fakeclock.NewFakeClock(fixedNow.Time),
			}
			if test.existingCertificate != nil {
				builder.CertManagerObjects = append(builder.CertManagerObjects, test.existingCertificate)
			}
			builder.Init()

			w := &controllerWrapper{}
			_, _, err := w.Register(builder.Context)
			if err != nil {
				t.Fatal(err)
			}

			gotShouldReissueCalled := false
			w.shouldReissue = func(i policies.Input) (string, string, bool) {
				gotShouldReissueCalled = true
				if test.mockShouldReissue == nil {
					t.Fatal("no mock set for shouldReissue, but shouldReissue has been called")
					return "", "", false
				} else {
					return test.mockShouldReissue(t)(i)
				}
			}

			// TODO(mael): we should really remove the Certificate field from
			// DataForCertificate since the input certificate is always expected
			// to be the same as the output certiticate.
			test.mockDataForCertificateReturn.Certificate = test.existingCertificate

			gotDataForCertificateCalled := false
			w.dataForCertificate = func(context.Context, *cmapi.Certificate) (policies.Input, error) {
				gotDataForCertificateCalled = true
				return test.mockDataForCertificateReturn, test.mockDataForCertificateReturnErr
			}

			if test.wantConditions != nil {
				if test.existingCertificate == nil {
					t.Fatal("cannot expect an Update operation if test.certificate is nil")
				}
				expectedCert := test.existingCertificate.DeepCopy()
				expectedCert.Status.Conditions = test.wantConditions
				builder.ExpectedActions = append(builder.ExpectedActions,
					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
						cmapi.SchemeGroupVersion.WithResource("certificates"),
						"status",
						test.existingCertificate.Namespace,
						expectedCert,
					)),
				)
			}
			if test.wantEvent != "" {
				builder.ExpectedEvents = []string{test.wantEvent}
			}

			builder.Start()
			defer builder.Stop()

			key := test.key
			if key == "" && test.existingCertificate != nil {
				key, err = controllerpkg.KeyFunc(test.existingCertificate)
				if err != nil {
					t.Fatal(err)
				}
			}

			gotErr := w.controller.ProcessItem(context.Background(), key)
			switch {
			case gotErr != nil:
				if test.wantErr != gotErr.Error() {
					t.Errorf("error text did not match, got=%s, exp=%s", gotErr.Error(), test.wantErr)
				}
			default:
				if test.wantErr != "" {
					t.Errorf("got no error but expected: %s", test.wantErr)
				}
			}

			assert.Equal(t, test.wantDataForCertificateCalled, gotDataForCertificateCalled, "dataForCertificate func call")
			assert.Equal(t, test.wantShouldReissueCalled, gotShouldReissueCalled, "shouldReissue func call")

			builder.CheckAndFinish()
		})
	}
}

func Test_shouldBackoffReissuingOnFailure(t *testing.T) {
	clock := fakeclock.NewFakeClock(time.Date(2020, 11, 20, 16, 05, 00, 0000, time.Local))

	tests := map[string]struct {
		givenCert   *cmapi.Certificate
		wantBackoff bool
		wantDelay   time.Duration
	}{
		"should not back off from reissuing when the input request is nil": {
			givenCert:   gen.Certificate("cert-1", gen.SetCertificateNamespace("testns")),
			wantBackoff: false,
		},
		"should not back off from reissuing when cert is not failing": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				// LastFailureTime is not set here.
			),
			wantBackoff: false,
		},
		"should not back off from reissuing when the failure happened 61 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-61*time.Minute))),
			),
			wantBackoff: false,
		},
		"should not back off from reissuing when the failure happened 60 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-60*time.Minute))),
			),
			wantBackoff: false,
		},
		"should back off from reissuing when the failure happened 59 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-59*time.Minute))),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
			),
			wantBackoff: true,
			wantDelay:   1 * time.Minute,
		},
		"should back off from reissuing when the failure happened 0 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())),
				gen.SetCertificateDNSNames("example2.com"),
			),
			wantBackoff: true,
			wantDelay:   1 * time.Hour,
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			gotBackoff, gotDelay := shouldBackoffReissuingOnFailure(logtest.TestLogger{T: t}, clock, test.givenCert)
			assert.Equal(t, test.wantBackoff, gotBackoff)
			assert.Equal(t, test.wantDelay, gotDelay)
		})
	}
}
<commit_msg>PR comment: a sentence starts with a capital letter and ends with a dot<commit_after>/*
Copyright 2020 The cert-manager 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 trigger

import (
	"context"
	"fmt"
	"testing"
	"time"

	logtest "github.com/go-logr/logr/testing"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	coretesting "k8s.io/client-go/testing"
	fakeclock "k8s.io/utils/clock/testing"

	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificates/trigger/policies"
	testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
	"github.com/jetstack/cert-manager/test/unit/gen"
)

func Test_controller_ProcessItem(t *testing.T) {
	fixedNow := metav1.NewTime(time.Now())

	tests := map[string]struct {
		// key that should be passed to ProcessItem. If not set, the
		// 'namespace/name' of the 'Certificate' field will be used. If neither
		// is set, the key will be "".
		key string

		// Certificate to be synced for the test. If not set, the 'key' will be
		// passed to ProcessItem instead.
		existingCertificate *cmapi.Certificate

		mockDataForCertificateReturn    policies.Input
		mockDataForCertificateReturnErr error
		wantDataForCertificateCalled    bool

		mockShouldReissue       func(t *testing.T) policies.Func
		wantShouldReissueCalled bool

		// wantEvent, if set, is an 'event string' that is expected to be fired.
		// For example, "Normal Issuing Re-issuance forced by unit test case"
		// where 'Normal' is the event severity, 'Issuing' is the reason and the
		// remainder is the message.
		wantEvent string

		// wantConditions is the expected set of conditions on the Certificate
		// resource if an Update is made.
		// If nil, no update is expected.
		// If empty, an update to the empty set/nil is expected.
		wantConditions []cmapi.CertificateCondition

		// wantErr is the expected error text returned by the controller, if any.
		wantErr string
	}{
		"do nothing if an empty 'key' is used": {},
		"do nothing if an invalid 'key' is used": {
			key: "abc/def/ghi",
		},
		"do nothing if a key references a Certificate that does not exist": {
			key: "namespace/name",
		},
		"should do nothing if Certificate already has 'Issuing' condition": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateStatusCondition(cmapi.CertificateCondition{
					Type:               "Issuing",
					Status:             "True",
					ObservedGeneration: 3,
				}),
			),
		},
		"should call shouldReissue with the correct cert, secret and current CR": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateSecretName("secret-1"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(2),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{
				Secret: gen.Secret("secret-1", gen.SetSecretNamespace("testns")),
				CurrentRevisionRequest: gen.CertificateRequest("cr-1", gen.SetCertificateRequestNamespace("testns"),
					gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("cert-1", "cert-1-uid")),
					gen.SetCertificateRequestAnnotations(map[string]string{"cert-manager.io/certificate-revision": "2"}),
				),
			},
			wantShouldReissueCalled: true,
			mockShouldReissue: func(t *testing.T) policies.Func {
				return func(gotInput policies.Input) (string, string, bool) {
					expectInput := policies.Input{
						Certificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
							gen.SetCertificateSecretName("secret-1"),
							gen.SetCertificateGeneration(42),
							gen.SetCertificateUID("cert-1-uid"),
							gen.SetCertificateRevision(2),
						),
						CurrentRevisionRequest: gen.CertificateRequest("cr-1", gen.SetCertificateRequestNamespace("testns"),
							gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("cert-1", "cert-1-uid")),
							gen.SetCertificateRequestAnnotations(map[string]string{"cert-manager.io/certificate-revision": "2"}),
						),
						Secret: gen.Secret("secret-1", gen.SetSecretNamespace("testns")),
					}
					assert.Equal(t, expectInput, gotInput)
					return "", "", false
				}
			},
		},
		"should log error when dataForCertificate errors": {
			existingCertificate:             gen.Certificate("cert-1", gen.SetCertificateNamespace("testns")),
			wantDataForCertificateCalled:    true,
			mockDataForCertificateReturnErr: fmt.Errorf("dataForCertificate failed"),
			wantErr:                         "dataForCertificate failed",
		},
		"should set Issuing=True if shouldReissue tells us to reissue": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      true,
			mockShouldReissue: func(*testing.T) policies.Func {
				return func(policies.Input) (string, string, bool) {
					return "ForceTriggered", "Re-issuance forced by unit test case", true
				}
			},
			wantEvent: "Normal Issuing Re-issuance forced by unit test case",
			wantConditions: []cmapi.CertificateCondition{{
				Type:               "Issuing",
				Status:             "True",
				Reason:             "ForceTriggered",
				Message:            "Re-issuance forced by unit test case",
				LastTransitionTime: &fixedNow,
				ObservedGeneration: 42,
			}},
		},
		"should not set Issuing=True when cert has been failing for 59 minutes": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-59*time.Minute))),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      false,
		},
		"should set Issuing=True when cert has been failing for 61 minutes and shouldReissue returns true": {
			existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateGeneration(42),
				gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-61*time.Minute))),
			),
			wantDataForCertificateCalled: true,
			mockDataForCertificateReturn: policies.Input{},
			wantShouldReissueCalled:      true,
			mockShouldReissue: func(*testing.T) policies.Func {
				return func(policies.Input) (string, string, bool) {
					return "ForceTriggered", "Re-issuance forced by unit test case", true
				}
			},
			wantEvent: "Normal Issuing Re-issuance forced by unit test case",
			wantConditions: []cmapi.CertificateCondition{{
				Type:               "Issuing",
				Status:             "True",
				Reason:             "ForceTriggered",
				Message:            "Re-issuance forced by unit test case",
				LastTransitionTime: &fixedNow,
				ObservedGeneration: 42,
			}},
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			builder := &testpkg.Builder{
				T:     t,
				Clock: fakeclock.NewFakeClock(fixedNow.Time),
			}
			if test.existingCertificate != nil {
				builder.CertManagerObjects = append(builder.CertManagerObjects, test.existingCertificate)
			}
			builder.Init()

			w := &controllerWrapper{}
			_, _, err := w.Register(builder.Context)
			if err != nil {
				t.Fatal(err)
			}

			gotShouldReissueCalled := false
			w.shouldReissue = func(i policies.Input) (string, string, bool) {
				gotShouldReissueCalled = true
				if test.mockShouldReissue == nil {
					t.Fatal("no mock set for shouldReissue, but shouldReissue has been called")
					return "", "", false
				} else {
					return test.mockShouldReissue(t)(i)
				}
			}

			// TODO(mael): we should really remove the Certificate field from
			// DataForCertificate since the input certificate is always expected
			// to be the same as the output certiticate.
			test.mockDataForCertificateReturn.Certificate = test.existingCertificate

			gotDataForCertificateCalled := false
			w.dataForCertificate = func(context.Context, *cmapi.Certificate) (policies.Input, error) {
				gotDataForCertificateCalled = true
				return test.mockDataForCertificateReturn, test.mockDataForCertificateReturnErr
			}

			if test.wantConditions != nil {
				if test.existingCertificate == nil {
					t.Fatal("cannot expect an Update operation if test.certificate is nil")
				}
				expectedCert := test.existingCertificate.DeepCopy()
				expectedCert.Status.Conditions = test.wantConditions
				builder.ExpectedActions = append(builder.ExpectedActions,
					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
						cmapi.SchemeGroupVersion.WithResource("certificates"),
						"status",
						test.existingCertificate.Namespace,
						expectedCert,
					)),
				)
			}
			if test.wantEvent != "" {
				builder.ExpectedEvents = []string{test.wantEvent}
			}

			builder.Start()
			defer builder.Stop()

			key := test.key
			if key == "" && test.existingCertificate != nil {
				key, err = controllerpkg.KeyFunc(test.existingCertificate)
				if err != nil {
					t.Fatal(err)
				}
			}

			gotErr := w.controller.ProcessItem(context.Background(), key)
			switch {
			case gotErr != nil:
				if test.wantErr != gotErr.Error() {
					t.Errorf("error text did not match, got=%s, exp=%s", gotErr.Error(), test.wantErr)
				}
			default:
				if test.wantErr != "" {
					t.Errorf("got no error but expected: %s", test.wantErr)
				}
			}

			assert.Equal(t, test.wantDataForCertificateCalled, gotDataForCertificateCalled, "dataForCertificate func call")
			assert.Equal(t, test.wantShouldReissueCalled, gotShouldReissueCalled, "shouldReissue func call")

			builder.CheckAndFinish()
		})
	}
}

func Test_shouldBackoffReissuingOnFailure(t *testing.T) {
	clock := fakeclock.NewFakeClock(time.Date(2020, 11, 20, 16, 05, 00, 0000, time.Local))

	tests := map[string]struct {
		givenCert   *cmapi.Certificate
		wantBackoff bool
		wantDelay   time.Duration
	}{
		"should not back off from reissuing when the input request is nil": {
			givenCert:   gen.Certificate("cert-1", gen.SetCertificateNamespace("testns")),
			wantBackoff: false,
		},
		"should not back off from reissuing when cert is not failing": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				// LastFailureTime is not set here.
			),
			wantBackoff: false,
		},
		"should not back off from reissuing when the failure happened 61 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-61*time.Minute))),
			),
			wantBackoff: false,
		},
		"should not back off from reissuing when the failure happened 60 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-60*time.Minute))),
			),
			wantBackoff: false,
		},
		"should back off from reissuing when the failure happened 59 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-59*time.Minute))),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateDNSNames("example2.com"),
			),
			wantBackoff: true,
			wantDelay:   1 * time.Minute,
		},
		"should back off from reissuing when the failure happened 0 minutes ago": {
			givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"),
				gen.SetCertificateUID("cert-1-uid"),
				gen.SetCertificateRevision(1),
				gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())),
				gen.SetCertificateDNSNames("example2.com"),
			),
			wantBackoff: true,
			wantDelay:   1 * time.Hour,
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			gotBackoff, gotDelay := shouldBackoffReissuingOnFailure(logtest.TestLogger{T: t}, clock, test.givenCert)
			assert.Equal(t, test.wantBackoff, gotBackoff)
			assert.Equal(t, test.wantDelay, gotDelay)
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The cert-manager 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 acmeorders

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/utils/clock"

	"github.com/jetstack/cert-manager/pkg/acme/accounts"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmacmelisters "github.com/jetstack/cert-manager/pkg/client/listers/acme/v1"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

type controller struct {
	// issuer helper is used to obtain references to issuers, used by Sync()
	helper issuer.Helper

	// used to fetch ACME clients used in the controller
	accountRegistry accounts.Getter

	// all the listers used by this controller
	orderLister         cmacmelisters.OrderLister
	challengeLister     cmacmelisters.ChallengeLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister
	secretLister        corelisters.SecretLister

	// used for testing
	clock.Clock
	// used to record Events about resources to the API
	recorder record.EventRecorder
	// clientset used to update cert-manager API resources
	cmClient cmclient.Interface

	// maintain a reference to the workqueue for this controller
	// so the handleOwnedResource method can enqueue resources
	queue workqueue.RateLimitingInterface

	// logger to be used by this controller
	log logr.Logger
}

// Register registers and constructs the controller using the provided context.
// It returns the workqueue to be used to enqueue items, a list of
// InformerSynced functions that must be synced, or an error.
func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	c.log = logf.FromContext(ctx.RootContext, ControllerName)

	// create a queue used to queue up items to be processed
	c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName)

	// obtain references to all the informers used by this controller
	orderInformer := ctx.SharedInformerFactory.Acme().V1().Orders()
	issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers()
	challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges()
	secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets()
	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		orderInformer.Informer().HasSynced,
		issuerInformer.Informer().HasSynced,
		challengeInformer.Informer().HasSynced,
		secretInformer.Informer().HasSynced,
	}

	// set all the references to the listers for used by the Sync function
	c.orderLister = orderInformer.Lister()
	c.issuerLister = issuerInformer.Lister()
	c.challengeLister = challengeInformer.Lister()
	c.secretLister = secretInformer.Lister()

	// if we are running in non-namespaced mode (i.e. --namespace=""), we also
	// register event handlers and obtain a lister for clusterissuers.
	if ctx.Namespace == "" {
		clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers()
		mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced)
		c.clusterIssuerLister = clusterIssuerInformer.Lister()
		// register handler function for clusterissuer resources
		clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer})
	}

	// register handler functions
	orderInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})
	issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer})
	challengeInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc(c.log, c.queue, orderGvk, c.orderGetter),
	})

	// instantiate additional helpers used by this controller
	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.recorder = ctx.Recorder
	c.cmClient = ctx.CMClient
	// clock is used when setting the failureTime on an Order's status
	c.clock = ctx.Clock
	c.accountRegistry = ctx.ACMEOptions.AccountRegistry

	return c.queue, mustSync, nil
}

func (c *controller) orderGetter(namespace, name string) (interface{}, error) {
	return c.orderLister.Orders(namespace).Get(name)
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key")
		return nil
	}

	order, err := c.orderLister.Orders(namespace).Get(name)
	if err != nil {
		if k8sErrors.IsNotFound(err) {
			log.Error(err, "order in work queue no longer exists")
			return nil
		}

		return err
	}

	ctx = logf.NewContext(ctx, logf.WithResource(log, order))
	return c.Sync(ctx, order)
}

var keyFunc = controllerpkg.KeyFunc

const (
	ControllerName = "orders"
)

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controller{}).
			Complete()
	})
}
<commit_msg>Update pkg/controller/acmeorders/controller.go<commit_after>/*
Copyright 2020 The cert-manager 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 acmeorders

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/utils/clock"

	"github.com/jetstack/cert-manager/pkg/acme/accounts"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmacmelisters "github.com/jetstack/cert-manager/pkg/client/listers/acme/v1"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

type controller struct {
	// issuer helper is used to obtain references to issuers, used by Sync()
	helper issuer.Helper

	// used to fetch ACME clients used in the controller
	accountRegistry accounts.Getter

	// all the listers used by this controller
	orderLister         cmacmelisters.OrderLister
	challengeLister     cmacmelisters.ChallengeLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister
	secretLister        corelisters.SecretLister

	// used for testing
	clock clock.Clock
	// used to record Events about resources to the API
	recorder record.EventRecorder
	// clientset used to update cert-manager API resources
	cmClient cmclient.Interface

	// maintain a reference to the workqueue for this controller
	// so the handleOwnedResource method can enqueue resources
	queue workqueue.RateLimitingInterface

	// logger to be used by this controller
	log logr.Logger
}

// Register registers and constructs the controller using the provided context.
// It returns the workqueue to be used to enqueue items, a list of
// InformerSynced functions that must be synced, or an error.
func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	c.log = logf.FromContext(ctx.RootContext, ControllerName)

	// create a queue used to queue up items to be processed
	c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName)

	// obtain references to all the informers used by this controller
	orderInformer := ctx.SharedInformerFactory.Acme().V1().Orders()
	issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers()
	challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges()
	secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets()
	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		orderInformer.Informer().HasSynced,
		issuerInformer.Informer().HasSynced,
		challengeInformer.Informer().HasSynced,
		secretInformer.Informer().HasSynced,
	}

	// set all the references to the listers for used by the Sync function
	c.orderLister = orderInformer.Lister()
	c.issuerLister = issuerInformer.Lister()
	c.challengeLister = challengeInformer.Lister()
	c.secretLister = secretInformer.Lister()

	// if we are running in non-namespaced mode (i.e. --namespace=""), we also
	// register event handlers and obtain a lister for clusterissuers.
	if ctx.Namespace == "" {
		clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers()
		mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced)
		c.clusterIssuerLister = clusterIssuerInformer.Lister()
		// register handler function for clusterissuer resources
		clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer})
	}

	// register handler functions
	orderInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})
	issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer})
	challengeInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc(c.log, c.queue, orderGvk, c.orderGetter),
	})

	// instantiate additional helpers used by this controller
	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.recorder = ctx.Recorder
	c.cmClient = ctx.CMClient
	// clock is used when setting the failureTime on an Order's status
	c.clock = ctx.Clock
	c.accountRegistry = ctx.ACMEOptions.AccountRegistry

	return c.queue, mustSync, nil
}

func (c *controller) orderGetter(namespace, name string) (interface{}, error) {
	return c.orderLister.Orders(namespace).Get(name)
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key")
		return nil
	}

	order, err := c.orderLister.Orders(namespace).Get(name)
	if err != nil {
		if k8sErrors.IsNotFound(err) {
			log.Error(err, "order in work queue no longer exists")
			return nil
		}

		return err
	}

	ctx = logf.NewContext(ctx, logf.WithResource(log, order))
	return c.Sync(ctx, order)
}

var keyFunc = controllerpkg.KeyFunc

const (
	ControllerName = "orders"
)

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controller{}).
			Complete()
	})
}
<|endoftext|>


<commit_before>package nslabels

import (
	"fmt"
	"strings"

	"github.com/rancher/types/apis/core/v1"
	typescorev1 "github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	ProjectIDFieldLabel           = "field.cattle.io/projectId"
	ProjectScopedSecretAnnotation = "secret.user.cattle.io/secret"
)

type namespaceHandler struct {
	secrets  v1.SecretInterface
	nsClient typescorev1.NamespaceInterface
}

func Register(cluster *config.UserContext) {
	logrus.Infof("Registering namespaceHandler for adding labels ")
	nsh := &namespaceHandler{
		secrets:  cluster.Core.Secrets(""),
		nsClient: cluster.Core.Namespaces(""),
	}
	cluster.Core.Namespaces("").AddHandler("namespaceHandler", nsh.Sync)
}

func (nsh *namespaceHandler) Sync(key string, ns *corev1.Namespace) error {
	if ns == nil {
		return nil
	}
	logrus.Debugf("namespaceHandler: Sync: key=%v, ns=%+v", key, *ns)

	field, ok := ns.Annotations[ProjectIDFieldLabel]
	if !ok {
		return nil
	}

	projectID := ""
	clusterID := ""
	if field != "" {
		splits := strings.Split(field, ":")
		if len(splits) != 2 {
			return nil
		}
		projectID = splits[1]
		clusterID = splits[0]
	}

	logrus.Debugf("namespaceHandler: Sync: projectID=%v", projectID)

	if err := nsh.addProjectIDLabelToNamespace(ns, projectID, clusterID); err != nil {
		logrus.Errorf("namespaceHandler: Sync: error adding project id label to namespace err=%v", err)
		return nil
	}

	return nil
}

func (nsh *namespaceHandler) addProjectIDLabelToNamespace(ns *corev1.Namespace, projectID string, clusterID string) error {
	if ns == nil {
		return fmt.Errorf("cannot add label to nil namespace")
	}
	if ns.Labels[ProjectIDFieldLabel] != projectID {
		nsh.updateProjectIDLabelForSecrets(projectID, ns.Name, clusterID)
		logrus.Infof("namespaceHandler: addProjectIDLabelToNamespace: adding label %v=%v to namespace=%v", ProjectIDFieldLabel, projectID, ns.Name)
		nscopy := ns.DeepCopy()
		if nscopy.Labels == nil {
			nscopy.Labels = map[string]string{}
		}
		nscopy.Labels[ProjectIDFieldLabel] = projectID
		if _, err := nsh.nsClient.Update(nscopy); err != nil {
			return err
		}
	}

	return nil
}

func (nsh *namespaceHandler) updateProjectIDLabelForSecrets(projectID string, namespace string, clusterID string) error {
	secrets, err := nsh.secrets.List(metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.namespace=%s", namespace)})
	if err != nil {
		return err
	}
	for _, secret := range secrets.Items {
		if secret.Annotations[ProjectScopedSecretAnnotation] == "true" {
			if err := nsh.secrets.DeleteNamespaced(namespace, secret.Name, &metav1.DeleteOptions{}); err != nil {
				return err
			}
		} else {
			secretCopy := secret.DeepCopy()
			if projectID == "" {
				secretCopy.Annotations[ProjectIDFieldLabel] = projectID
			} else {
				secretCopy.Annotations[ProjectIDFieldLabel] = fmt.Sprintf("%s:%s", clusterID, projectID)
			}
			if _, err := nsh.secrets.Update(secretCopy); err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>fix crash cause by lack of annotation<commit_after>package nslabels

import (
	"fmt"
	"strings"

	"github.com/rancher/types/apis/core/v1"
	typescorev1 "github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	ProjectIDFieldLabel           = "field.cattle.io/projectId"
	ProjectScopedSecretAnnotation = "secret.user.cattle.io/secret"
)

type namespaceHandler struct {
	secrets  v1.SecretInterface
	nsClient typescorev1.NamespaceInterface
}

func Register(cluster *config.UserContext) {
	logrus.Infof("Registering namespaceHandler for adding labels ")
	nsh := &namespaceHandler{
		secrets:  cluster.Core.Secrets(""),
		nsClient: cluster.Core.Namespaces(""),
	}
	cluster.Core.Namespaces("").AddHandler("namespaceHandler", nsh.Sync)
}

func (nsh *namespaceHandler) Sync(key string, ns *corev1.Namespace) error {
	if ns == nil {
		return nil
	}
	logrus.Debugf("namespaceHandler: Sync: key=%v, ns=%+v", key, *ns)

	field, ok := ns.Annotations[ProjectIDFieldLabel]
	if !ok {
		return nil
	}

	projectID := ""
	clusterID := ""
	if field != "" {
		splits := strings.Split(field, ":")
		if len(splits) != 2 {
			return nil
		}
		projectID = splits[1]
		clusterID = splits[0]
	}

	logrus.Debugf("namespaceHandler: Sync: projectID=%v", projectID)

	if err := nsh.addProjectIDLabelToNamespace(ns, projectID, clusterID); err != nil {
		logrus.Errorf("namespaceHandler: Sync: error adding project id label to namespace err=%v", err)
		return nil
	}

	return nil
}

func (nsh *namespaceHandler) addProjectIDLabelToNamespace(ns *corev1.Namespace, projectID string, clusterID string) error {
	if ns == nil {
		return fmt.Errorf("cannot add label to nil namespace")
	}
	if ns.Labels[ProjectIDFieldLabel] != projectID {
		nsh.updateProjectIDLabelForSecrets(projectID, ns.Name, clusterID)
		logrus.Infof("namespaceHandler: addProjectIDLabelToNamespace: adding label %v=%v to namespace=%v", ProjectIDFieldLabel, projectID, ns.Name)
		nscopy := ns.DeepCopy()
		if nscopy.Labels == nil {
			nscopy.Labels = map[string]string{}
		}
		nscopy.Labels[ProjectIDFieldLabel] = projectID
		if _, err := nsh.nsClient.Update(nscopy); err != nil {
			return err
		}
	}

	return nil
}

func (nsh *namespaceHandler) updateProjectIDLabelForSecrets(projectID string, namespace string, clusterID string) error {
	secrets, err := nsh.secrets.List(metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.namespace=%s", namespace)})
	if err != nil {
		return err
	}
	for _, secret := range secrets.Items {
		if secret.Annotations[ProjectScopedSecretAnnotation] == "true" {
			if err := nsh.secrets.DeleteNamespaced(namespace, secret.Name, &metav1.DeleteOptions{}); err != nil {
				return err
			}
		} else {
			secretCopy := secret.DeepCopy()
			if secretCopy.Annotations == nil {
				secretCopy.Annotations = make(map[string]string)
			}
			if projectID == "" {
				secretCopy.Annotations[ProjectIDFieldLabel] = projectID
			} else {
				secretCopy.Annotations[ProjectIDFieldLabel] = fmt.Sprintf("%s:%s", clusterID, projectID)
			}
			if _, err := nsh.secrets.Update(secretCopy); err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 configmap

import (
	"fmt"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/rest"
	"k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/registry/generic"
	"k8s.io/kubernetes/pkg/runtime"
	apistorage "k8s.io/kubernetes/pkg/storage"
	"k8s.io/kubernetes/pkg/util/validation/field"
)

// strategy implements behavior for ConfigMap objects
type strategy struct {
	runtime.ObjectTyper
	api.NameGenerator
}

// Strategy is the default logic that applies when creating and updating ConfigMap
// objects via the REST API.
var Strategy = strategy{api.Scheme, api.SimpleNameGenerator}

// Strategy should implement rest.RESTCreateStrategy
var _ rest.RESTCreateStrategy = Strategy

// Strategy should implement rest.RESTUpdateStrategy
var _ rest.RESTUpdateStrategy = Strategy

func (strategy) NamespaceScoped() bool {
	return true
}

func (strategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
	_ = obj.(*api.ConfigMap)
}

func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
	cfg := obj.(*api.ConfigMap)

	return validation.ValidateConfigMap(cfg)
}

// Canonicalize normalizes the object after validation.
func (strategy) Canonicalize(obj runtime.Object) {
}

func (strategy) AllowCreateOnUpdate() bool {
	return false
}

func (strategy) PrepareForUpdate(ctx api.Context, newObj, oldObj runtime.Object) {
	_ = oldObj.(*api.ConfigMap)
	_ = newObj.(*api.ConfigMap)
}

func (strategy) AllowUnconditionalUpdate() bool {
	return true
}

func (strategy) ValidateUpdate(ctx api.Context, newObj, oldObj runtime.Object) field.ErrorList {
	oldCfg, newCfg := oldObj.(*api.ConfigMap), newObj.(*api.ConfigMap)

	return validation.ValidateConfigMapUpdate(newCfg, oldCfg)
}

// ConfigMapToSelectableFields returns a field set that represents the object for matching purposes.
func ConfigMapToSelectableFields(cfg *api.ConfigMap) fields.Set {
	return generic.ObjectMetaFieldsSet(&cfg.ObjectMeta, true)
}

// MatchConfigMap returns a generic matcher for a given label and field selector.
func MatchConfigMap(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate {
	return apistorage.SelectionPredicate{
		Label: label,
		Field: field,
		GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
			cfg, ok := obj.(*api.ConfigMap)
			if !ok {
				return nil, nil, fmt.Errorf("given object is not of type ConfigMap")
			}

			return labels.Set(cfg.ObjectMeta.Labels), ConfigMapToSelectableFields(cfg), nil
		},
	}
}
<commit_msg>Update strategy.go<commit_after>/*
Copyright 2015 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 configmap

import (
	"fmt"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/rest"
	"k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/registry/generic"
	"k8s.io/kubernetes/pkg/runtime"
	apistorage "k8s.io/kubernetes/pkg/storage"
	"k8s.io/kubernetes/pkg/util/validation/field"
)

// strategy implements behavior for ConfigMap objects
type strategy struct {
	runtime.ObjectTyper
	api.NameGenerator
}

// Strategy is the default logic that applies when creating and updating ConfigMap
// objects via the REST API.
var Strategy = strategy{api.Scheme, api.SimpleNameGenerator}

// Strategy should implement rest.RESTCreateStrategy
var _ rest.RESTCreateStrategy = Strategy

// Strategy should implement rest.RESTUpdateStrategy
var _ rest.RESTUpdateStrategy = Strategy

func (strategy) NamespaceScoped() bool {
	return true
}

func (strategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
	_ = obj.(*api.ConfigMap)
}

func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
	cfg := obj.(*api.ConfigMap)

	return validation.ValidateConfigMap(cfg)
}

// Canonicalize normalizes the object after validation.
func (strategy) Canonicalize(obj runtime.Object) {
}

func (strategy) AllowCreateOnUpdate() bool {
	return false
}

func (strategy) PrepareForUpdate(ctx api.Context, newObj, oldObj runtime.Object) {
	_ = oldObj.(*api.ConfigMap)
	_ = newObj.(*api.ConfigMap)
}

func (strategy) AllowUnconditionalUpdate() bool {
	return true
}

func (strategy) ValidateUpdate(ctx api.Context, newObj, oldObj runtime.Object) field.ErrorList {
	oldCfg, newCfg := oldObj.(*api.ConfigMap), newObj.(*api.ConfigMap)

	return validation.ValidateConfigMapUpdate(newCfg, oldCfg)
}

// ConfigMapToSelectableFields returns a field set that represents the object for matching purposes.
func ConfigMapToSelectableFields(cfg *api.ConfigMap) fields.Set {
	return generic.ObjectMetaFieldsSet(&cfg.ObjectMeta, true)
}

// MatchConfigMap returns a generic matcher for a given label and field selector.
func MatchConfigMap(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate {
	return apistorage.SelectionPredicate{
		Label: label,
		Field: field,
		GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
			cfg, ok := obj.(*api.ConfigMap)
			if !ok {
				return nil, nil, fmt.Errorf("given object is not a ConfigMap")
			}

			return labels.Set(cfg.ObjectMeta.Labels), ConfigMapToSelectableFields(cfg), nil
		},
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// 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
//
//     https://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 runtimeconfigurator

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"testing"
	"time"

	tspb "github.com/golang/protobuf/ptypes/timestamp"
	"github.com/google/go-cloud/runtimevar"
	"github.com/google/go-cloud/runtimevar/driver"
	"github.com/google/go-cmp/cmp"
	pb "google.golang.org/genproto/googleapis/cloud/runtimeconfig/v1beta1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Ensure that watcher implements driver.Watcher.
var _ driver.Watcher = &watcher{}

// fakeServer partially implements runtimevarManagerServer for Client to connect to.  Prefill
// responses field with the ordered list of responses to GetVariable calls.
type fakeServer struct {
	pb.RuntimeConfigManagerServer
	responses []response
	index     int
}

type response struct {
	vrbl *pb.Variable
	err  error
}

func (s *fakeServer) GetVariable(context.Context, *pb.GetVariableRequest) (*pb.Variable, error) {
	if len(s.responses) == 0 {
		return nil, fmt.Errorf("fakeClient missing responses")
	}
	resp := s.responses[s.index]
	// Adjust index to next response for next call till it gets to last one, then keep using the
	// last one.
	if s.index < len(s.responses)-1 {
		s.index++
	}
	return resp.vrbl, resp.err
}

func setUp(t *testing.T, fs *fakeServer) (*Client, func()) {
	t.Helper()
	// Set up gRPC server.
	lis, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatalf("tcp listen failed: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterRuntimeConfigManagerServer(s, fs)
	// Run gRPC server on a background goroutine.
	go s.Serve(lis)

	// Set up client.
	conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("did not connect: %v", err)
	}
	client := &Client{
		conn:   conn,
		client: pb.NewRuntimeConfigManagerClient(conn),
	}
	return client, func() {
		client.Close()
		s.Stop()
	}
}

type jsonData struct {
	Hello string `json:"hello"`
}

var (
	// Set wait timeout used for tests.
	watchOpt = &WatchOptions{
		WaitTime: 100 * time.Millisecond,
	}
	resourceName = ResourceName{
		ProjectID: "ID42",
		Config:    "config",
		Variable:  "greetings",
	}
	startTime = time.Now().Unix()
	jsonVar1  = &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Text{Text: `{"hello": "hello"}`},
		UpdateTime: &tspb.Timestamp{Seconds: startTime},
	}
	jsonVar2 = &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Value{Value: []byte(`{"hello": "hola"}`)},
		UpdateTime: &tspb.Timestamp{Seconds: startTime + 100},
	}
	jsonDataPtr *jsonData
)

func TestWatch(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
			{vrbl: jsonVar2},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	cfg, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("NewConfig returned error: %v", err)
	}

	got1, err := cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got1.Value.(*jsonData), &jsonData{"hello"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}

	got2, err := cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got2.Value.(*jsonData), &jsonData{"hola"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
}

func TestCustomDecode(t *testing.T) {
	value := "hello world"
	strVar := &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Value{Value: []byte(value)},
		UpdateTime: &tspb.Timestamp{Seconds: startTime},
	}

	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: strVar},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	watchOpt := &WatchOptions{
		WaitTime: 500 * time.Millisecond,
	}
	cfg, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder("", stringDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	got, err := cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got.Value.(string), value); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
}

func stringDecode(b []byte, obj interface{}) error {
	// obj is a pointer to a string.
	v := reflect.ValueOf(obj).Elem()
	v.SetString(string(b))
	return nil
}

func TestWatchCancelledBeforeFirstWatch(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	cfg, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	ctx, cancel := context.WithCancel(ctx)
	cancel()

	_, err = cfg.Watch(ctx)
	if err == nil {
		t.Fatal("Config.Watch returned nil error, expecting an error from cancelling")
	}
}

func TestContextCancelledInBetweenWatchCalls(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	cfg, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	_, err = cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}

	ctx, cancel := context.WithCancel(ctx)
	cancel()

	_, err = cfg.Watch(ctx)
	if err == nil {
		t.Fatal("Config.Watch returned nil error, expecting an error from cancelling")
	}
}

func TestWatchDeletedAndReset(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
			{err: status.Error(codes.NotFound, "deleted")},
			{vrbl: jsonVar2},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	cfg, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig() returned error: %v", err)
	}

	prev, err := cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}

	// Expect deleted error.
	if _, err := cfg.Watch(ctx); err == nil {
		t.Fatalf("Config.Watch returned nil, want error")
	}

	// Calling Watch again will poll for jsonVar2.
	got, err := cfg.Watch(ctx)
	if err != nil {
		t.Fatalf("Config.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got.Value.(*jsonData), &jsonData{"hola"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
	if !got.UpdateTime.After(prev.UpdateTime) {
		t.Errorf("Snapshot.UpdateTime is less than or equal to previous value")
	}
}
<commit_msg>runtimevar: Another config->variable cleanup<commit_after>// Copyright 2018 Google LLC
//
// 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
//
//     https://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 runtimeconfigurator

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"testing"
	"time"

	tspb "github.com/golang/protobuf/ptypes/timestamp"
	"github.com/google/go-cloud/runtimevar"
	"github.com/google/go-cloud/runtimevar/driver"
	"github.com/google/go-cmp/cmp"
	pb "google.golang.org/genproto/googleapis/cloud/runtimeconfig/v1beta1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Ensure that watcher implements driver.Watcher.
var _ driver.Watcher = &watcher{}

// fakeServer partially implements runtimevarManagerServer for Client to connect to.  Prefill
// responses field with the ordered list of responses to GetVariable calls.
type fakeServer struct {
	pb.RuntimeConfigManagerServer
	responses []response
	index     int
}

type response struct {
	vrbl *pb.Variable
	err  error
}

func (s *fakeServer) GetVariable(context.Context, *pb.GetVariableRequest) (*pb.Variable, error) {
	if len(s.responses) == 0 {
		return nil, fmt.Errorf("fakeClient missing responses")
	}
	resp := s.responses[s.index]
	// Adjust index to next response for next call till it gets to last one, then keep using the
	// last one.
	if s.index < len(s.responses)-1 {
		s.index++
	}
	return resp.vrbl, resp.err
}

func setUp(t *testing.T, fs *fakeServer) (*Client, func()) {
	t.Helper()
	// Set up gRPC server.
	lis, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatalf("tcp listen failed: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterRuntimeConfigManagerServer(s, fs)
	// Run gRPC server on a background goroutine.
	go s.Serve(lis)

	// Set up client.
	conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
	if err != nil {
		t.Fatalf("did not connect: %v", err)
	}
	client := &Client{
		conn:   conn,
		client: pb.NewRuntimeConfigManagerClient(conn),
	}
	return client, func() {
		client.Close()
		s.Stop()
	}
}

type jsonData struct {
	Hello string `json:"hello"`
}

var (
	// Set wait timeout used for tests.
	watchOpt = &WatchOptions{
		WaitTime: 100 * time.Millisecond,
	}
	resourceName = ResourceName{
		ProjectID: "ID42",
		Config:    "config",
		Variable:  "greetings",
	}
	startTime = time.Now().Unix()
	jsonVar1  = &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Text{Text: `{"hello": "hello"}`},
		UpdateTime: &tspb.Timestamp{Seconds: startTime},
	}
	jsonVar2 = &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Value{Value: []byte(`{"hello": "hola"}`)},
		UpdateTime: &tspb.Timestamp{Seconds: startTime + 100},
	}
	jsonDataPtr *jsonData
)

func TestWatch(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
			{vrbl: jsonVar2},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	variable, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("NewConfig returned error: %v", err)
	}

	got1, err := variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got1.Value.(*jsonData), &jsonData{"hello"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}

	got2, err := variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got2.Value.(*jsonData), &jsonData{"hola"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
}

func TestCustomDecode(t *testing.T) {
	value := "hello world"
	strVar := &pb.Variable{
		Name:       "greetings",
		Contents:   &pb.Variable_Value{Value: []byte(value)},
		UpdateTime: &tspb.Timestamp{Seconds: startTime},
	}

	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: strVar},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	watchOpt := &WatchOptions{
		WaitTime: 500 * time.Millisecond,
	}
	variable, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder("", stringDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	got, err := variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got.Value.(string), value); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
}

func stringDecode(b []byte, obj interface{}) error {
	// obj is a pointer to a string.
	v := reflect.ValueOf(obj).Elem()
	v.SetString(string(b))
	return nil
}

func TestWatchCancelledBeforeFirstWatch(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	variable, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	ctx, cancel := context.WithCancel(ctx)
	cancel()

	_, err = variable.Watch(ctx)
	if err == nil {
		t.Fatal("Variable.Watch returned nil error, expecting an error from cancelling")
	}
}

func TestContextCancelledInBetweenWatchCalls(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	variable, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig returned error: %v", err)
	}

	_, err = variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}

	ctx, cancel := context.WithCancel(ctx)
	cancel()

	_, err = variable.Watch(ctx)
	if err == nil {
		t.Fatal("Variable.Watch returned nil error, expecting an error from cancelling")
	}
}

func TestWatchDeletedAndReset(t *testing.T) {
	client, cleanUp := setUp(t, &fakeServer{
		responses: []response{
			{vrbl: jsonVar1},
			{err: status.Error(codes.NotFound, "deleted")},
			{vrbl: jsonVar2},
		},
	})
	defer cleanUp()

	ctx := context.Background()
	variable, err := client.NewVariable(ctx, resourceName, runtimevar.NewDecoder(jsonDataPtr, runtimevar.JSONDecode), watchOpt)
	if err != nil {
		t.Fatalf("Client.NewConfig() returned error: %v", err)
	}

	prev, err := variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}

	// Expect deleted error.
	if _, err := variable.Watch(ctx); err == nil {
		t.Fatalf("Variable.Watch returned nil, want error")
	}

	// Calling Watch again will poll for jsonVar2.
	got, err := variable.Watch(ctx)
	if err != nil {
		t.Fatalf("Variable.Watch returned error: %v", err)
	}
	if diff := cmp.Diff(got.Value.(*jsonData), &jsonData{"hola"}); diff != "" {
		t.Errorf("Snapshot.Value: %s", diff)
	}
	if !got.UpdateTime.After(prev.UpdateTime) {
		t.Errorf("Snapshot.UpdateTime is less than or equal to previous value")
	}
}
<|endoftext|>


<commit_before>package lclip

import (
	"os"
	"path/filepath"

	_ "github.com/mattn/go-sqlite3"
	"github.com/mitchellh/go-homedir"
	"github.com/naoina/genmai"
)

func exists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func DefaultPath() (string, error) {
	h, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(h, ".lclip.json"), nil
}

type Variable struct {
	Label string `db:"pk"`
	Data  []byte
}

type Clipboard struct {
	path string
	db   *genmai.DB
}

func NewClipboard(path string) (*Clipboard, error) {
	db, err := genmai.New(&genmai.SQLite3Dialect{}, path)
	if err != nil {
		return nil, err
	}
	if err = db.CreateTableIfNotExists(&Variable{}); err != nil {
		return nil, err
	}
	return &Clipboard{
		path: path,
		db:   db,
	}, nil
}

func NewClipboardWithDefaultPath() (*Clipboard, error) {
	path, err := DefaultPath()
	if err != nil {
		return nil, err
	}
	return NewClipboard(path)
}

func (c *Clipboard) Path() string {
	return c.path
}

func (c *Clipboard) searchVariable(label string) (*Variable, error) {
	res := make([]Variable, 0, 1)
	err := c.db.Select(&res, c.db.Where("label", "=", label))
	if err != nil {
		return nil, err
	}
	if len(res) < 1 {
		return nil, nil
	}
	return &res[0], nil
}

func (c *Clipboard) Get(label string) []byte {
	v, err := c.searchVariable(label)
	if err != nil {
		return nil
	}
	if v == nil {
		return []byte(``)
	}
	return v.Data
}

func (c *Clipboard) Set(label string, data []byte) {
	v, err := c.searchVariable(label)
	if err != nil {
		return
	}
	if v == nil {
		_, err = c.db.Insert(&Variable{Label: label, Data: data})
		return
	}
	_, err = c.db.Update(&Variable{Label: v.Label, Data: data})
	return
}

func (c *Clipboard) Labels() []string {
	res := make([]Variable, 0)
	if err := c.db.Select(&res); err != nil {
		return nil
	}
	labels := make([]string, len(res))
	for i := 0; i < len(res); i++ {
		labels[i] = res[i].Label
	}
	return labels
}

func (c *Clipboard) Delete(label string) error {
	v, err := c.searchVariable(label)
	if err != nil {
		return err
	}
	if v == nil {
		return nil
	}
	_, err = c.db.Delete(v)
	return nil
}

func (c *Clipboard) Close() error {
	return c.db.Close()
}
<commit_msg>Back to sqlite to json<commit_after>package lclip

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/mitchellh/go-homedir"
)

func exists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func DefaultPath() (string, error) {
	h, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(h, ".lclip.json"), nil
}

type Clipboard struct {
	path    string
	storage map[string][]byte
}

func NewClipboard(path string) (*Clipboard, error) {
	if !exists(path) {
		err := ioutil.WriteFile(path, []byte(`{}`), 0644)
		if err != nil {
			return nil, err
		}
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	c := &Clipboard{path: path}
	if err := json.NewDecoder(f).Decode(&c.storage); err != nil {
		return nil, err
	}
	return c, nil
}

func NewClipboardWithDefaultPath() (*Clipboard, error) {
	path, err := DefaultPath()
	if err != nil {
		return nil, err
	}
	return NewClipboard(path)
}

func (c *Clipboard) Get(label string) []byte {
	return c.storage[label]
}

func (c *Clipboard) Set(label string, data []byte) {
	c.storage[label] = data
}

func (c *Clipboard) Labels() []string {
	labels := make([]string, 0, len(c.storage))
	for label, _ := range c.storage {
		labels = append(labels, label)
	}
	return labels
}

func (c *Clipboard) Delete(label string) error {
	delete(c.storage, label)
	return nil
}

func (c *Clipboard) Close() error {
	f, err := os.Create(c.path)
	if err != nil {
		return err
	}
	defer f.Close()
	return json.NewEncoder(f).Encode(&c.storage)
}
<|endoftext|>


<commit_before>// +build linux lxc

package lxcadapter

import (
	"github.com/dropbox/changes-client/client"
	"github.com/dropbox/changes-client/client/adapter"
	"gopkg.in/lxc/go-lxc.v2"
	"log"
	"sync"
	"testing"

	. "gopkg.in/check.v1"
)

var (
	containerName string
)

func TestAdapter(t *testing.T) { TestingT(t) }

type AdapterSuite struct{}

var _ = Suite(&AdapterSuite{})

// we want to output the log from running the container
func (s *AdapterSuite) reportLogChunks(clientLog *client.Log) {
	for chunk := range clientLog.Chan {
		log.Print(string(chunk))
	}
}

func (s *AdapterSuite) ensureContainerRemoved(c *C) {
	container, err := lxc.NewContainer(containerName, lxc.DefaultConfigPath())
	c.Assert(err, IsNil)
	defer lxc.Release(container)

	if container.Running() {
		err = container.Stop()
		c.Assert(err, IsNil)
	}
	c.Assert(container.Running(), Equals, false)

	if container.Defined() {
		err = container.Destroy()
		c.Assert(err, IsNil)
	}
	c.Assert(container.Defined(), Equals, false)
}

func (s *AdapterSuite) SetUpSuite(c *C) {
	s.ensureContainerRemoved(c)
}

func (s *AdapterSuite) TestCompleteFlow(c *C) {
	var cmd *client.Command
	var err error
	var result *client.CommandResult

	clientLog := client.NewLog()
	adapter, err := adapter.Get("lxc")
	c.Assert(err, IsNil)

	wg := sync.WaitGroup{}

	wg.Add(1)
	go func() {
		defer wg.Done()
		s.reportLogChunks(clientLog)
	}()

	config := &client.Config{}
	config.JobstepID = containerName

	err = adapter.Init(config)
	c.Assert(err, IsNil)

	err = adapter.Prepare(clientLog)
	c.Assert(err, IsNil)
	defer adapter.Shutdown(clientLog)

	cmd, err = client.NewCommand("test", "#!/bin/bash -e\necho hello > foo.txt\nexit 0")
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "")
	c.Assert(result.Success, Equals, true)

	cmd, err = client.NewCommand("test", "#!/bin/bash -e\necho $HOME\nexit 0")
	cmd.CaptureOutput = true
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "/home/ubuntu\n")
	c.Assert(result.Success, Equals, true)

	// test with a command that expects stdin
	cmd, err = client.NewCommand("test", "#!/bin/bash -e\nread foo\nexit 1")
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "")
	c.Assert(result.Success, Equals, false)

	artifacts, err := adapter.CollectArtifacts([]string{"foo.txt"}, clientLog)
	c.Assert(err, IsNil)
	c.Assert(len(artifacts), Equals, 1)
	c.Assert(artifacts[0], Matches, ".*/home/ubuntu/foo.txt")

	clientLog.Close()

	wg.Wait()
}

func init() {
	containerName = "84e6165919c04514a330fe789f367007"
}
<commit_msg>Log when we forcefully stop/cleanup containers<commit_after>// +build linux lxc

package lxcadapter

import (
	"github.com/dropbox/changes-client/client"
	"github.com/dropbox/changes-client/client/adapter"
	"gopkg.in/lxc/go-lxc.v2"
	"log"
	"sync"
	"testing"

	. "gopkg.in/check.v1"
)

var (
	containerName string
)

func TestAdapter(t *testing.T) { TestingT(t) }

type AdapterSuite struct{}

var _ = Suite(&AdapterSuite{})

// we want to output the log from running the container
func (s *AdapterSuite) reportLogChunks(clientLog *client.Log) {
	for chunk := range clientLog.Chan {
		log.Print(string(chunk))
	}
}

func (s *AdapterSuite) ensureContainerRemoved(c *C) {
	container, err := lxc.NewContainer(containerName, lxc.DefaultConfigPath())
	c.Assert(err, IsNil)
	defer lxc.Release(container)

	if container.Running() {
		log.Println("Existing test container running. Executing Stop()")
		err = container.Stop()
		c.Assert(err, IsNil)
	}
	c.Assert(container.Running(), Equals, false)

	if container.Defined() {
		log.Println("Existing test container present. Executing Destroy()")
		err = container.Destroy()
		c.Assert(err, IsNil)
	}
	c.Assert(container.Defined(), Equals, false)
}

func (s *AdapterSuite) SetUpSuite(c *C) {
	s.ensureContainerRemoved(c)
}

func (s *AdapterSuite) TestCompleteFlow(c *C) {
	var cmd *client.Command
	var err error
	var result *client.CommandResult

	clientLog := client.NewLog()
	adapter, err := adapter.Get("lxc")
	c.Assert(err, IsNil)

	wg := sync.WaitGroup{}

	wg.Add(1)
	go func() {
		defer wg.Done()
		s.reportLogChunks(clientLog)
	}()

	config := &client.Config{}
	config.JobstepID = containerName

	err = adapter.Init(config)
	c.Assert(err, IsNil)

	err = adapter.Prepare(clientLog)
	c.Assert(err, IsNil)
	defer adapter.Shutdown(clientLog)

	cmd, err = client.NewCommand("test", "#!/bin/bash -e\necho hello > foo.txt\nexit 0")
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "")
	c.Assert(result.Success, Equals, true)

	cmd, err = client.NewCommand("test", "#!/bin/bash -e\necho $HOME\nexit 0")
	cmd.CaptureOutput = true
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "/home/ubuntu\n")
	c.Assert(result.Success, Equals, true)

	// test with a command that expects stdin
	cmd, err = client.NewCommand("test", "#!/bin/bash -e\nread foo\nexit 1")
	c.Assert(err, IsNil)

	result, err = adapter.Run(cmd, clientLog)
	c.Assert(err, IsNil)
	c.Assert(string(result.Output), Equals, "")
	c.Assert(result.Success, Equals, false)

	artifacts, err := adapter.CollectArtifacts([]string{"foo.txt"}, clientLog)
	c.Assert(err, IsNil)
	c.Assert(len(artifacts), Equals, 1)
	c.Assert(artifacts[0], Matches, ".*/home/ubuntu/foo.txt")

	clientLog.Close()

	wg.Wait()
}

func init() {
	containerName = "84e6165919c04514a330fe789f367007"
}
<|endoftext|>


<commit_before>package pq_types

import (
	. "gopkg.in/check.v1"
)

func (s *TypesSuite) TestPoint(c *C) {
	a := new(Point)

	b := []byte{
		0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0x45, 0x36,
		0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0x36, 0x45, 0x38, 0x36,
		0x31, 0x42, 0x46, 0x30, 0x43, 0x44, 0x34, 0x32, 0x34, 0x30, 0x32, 0x31,
		0x39, 0x34, 0x46, 0x37, 0x37, 0x31, 0x33, 0x34, 0x45, 0x39, 0x34, 0x42,
		0x34, 0x30,
	}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, &Point{Lon: 37.6088900, Lat: 55.8219130})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`SRID=4326;POINT(37.6088900 55.8219130)`))
}

func (s *TypesSuite) TestPointDB(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	var (
		err error
		p1  Point
	)

	for _, p := range []Point{
		{Lon: 37.6088900, Lat: 55.8219130},
		{Lon: -37.6088900, Lat: -55.8219130},
		{Lon: 0, Lat: 0},
		{Lon: 0.00, Lat: 0.0},
	} {
		s.SetUpTest(c)

		_, err = s.db.Exec("INSERT INTO pq_types (point) VALUES($1)", p)
		c.Check(err, IsNil)

		err = s.db.QueryRow("SELECT point FROM pq_types").Scan(&p1)
		c.Check(err, IsNil)

		c.Check(p1, DeepEquals, p)
	}
}

func (s *TypesSuite) TestBox2D(c *C) {
	a := new(Box2D)

	b := []byte{
		0x42, 0x4f, 0x58, 0x28, 0x30, 0x2e, 0x31, 0x32, 0x35, 0x20, 0x30, 0x2e, 0x32,
		0x35, 0x2c, 0x30, 0x2e, 0x35, 0x20, 0x31, 0x29,
	}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, &Box2D{Min: Point{Lon: 0.125, Lat: 0.25}, Max: Point{Lon: 0.5, Lat: 1}})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`BOX(0.1250000 0.2500000,0.5000000 1.0000000)`))
}

func (s *TypesSuite) TestBoxDB(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	var (
		err  error
		box1 Box2D
	)

	for _, box := range []Box2D{
		{Min: Point{Lon: 0.125, Lat: 0.25}, Max: Point{Lon: 0.5, Lat: 1}},
		{Min: Point{Lon: -0.125, Lat: -0.25}, Max: Point{Lon: 0.5, Lat: 1}},
		{Min: Point{Lon: -0.55, Lat: -0.55}, Max: Point{Lon: 0.5, Lat: 1}},
	} {
		s.SetUpTest(c)

		_, err = s.db.Exec("INSERT INTO pq_types (box) VALUES($1)", box)
		c.Check(err, IsNil)

		err = s.db.QueryRow("SELECT box FROM pq_types").Scan(&box1)
		c.Check(err, IsNil)

		c.Check(box1, DeepEquals, box)
	}
}

func (s *TypesSuite) TestPolygon(c *C) {
	a := new(Polygon)

	b := []byte{
		0x30, 0x31, 0x30, 0x33, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0x45, 0x36, 0x31, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x43, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x44, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43, 0x30, 0x33, 0x46, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x46, 0x30, 0x33, 0x46,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x45, 0x30,
		0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x46, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x45, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x44, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x44, 0x30, 0x33, 0x46}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, &Polygon{
		Points: []Point{
			{Lon: 0.125, Lat: 0.25},
			{Lon: 0.125, Lat: 1},
			{Lon: 0.5, Lat: 1},
			{Lon: 0.5, Lat: 0.25},
			{Lon: 0.125, Lat: 0.25}}})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`SRID=4326;POLYGON((0.1250000 0.2500000,0.1250000 1.0000000,0.5000000 1.0000000,0.5000000 0.2500000,0.1250000 0.2500000))`))
}

func (s *TypesSuite) TestPolygonDB(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	var (
		err  error
		pol1 Polygon
	)

	for _, pol := range []Polygon{
		{
			Points: []Point{
				{Lon: 0.125, Lat: 0.25},
				{Lon: 0.125, Lat: 1},
				{Lon: 0.5, Lat: 1},
				{Lon: 0.5, Lat: 0.25},
				{Lon: 0.125, Lat: 0.25}}},
		{
			Points: []Point{
				{Lon: 0.0, Lat: 0.0},
				{Lon: -50.555, Lat: -50.555},
				{Lon: -50, Lat: 0},
				{Lon: 0, Lat: 0}}},
	} {
		s.SetUpTest(c)

		_, err = s.db.Exec("INSERT INTO pq_types (polygon) VALUES($1)", pol)
		c.Check(err, IsNil)

		err = s.db.QueryRow("SELECT polygon FROM pq_types").Scan(&pol1)
		c.Check(err, IsNil)

		c.Check(pol1, DeepEquals, pol)
	}
}
<commit_msg>Unify tests.<commit_after>package pq_types

import (
	. "gopkg.in/check.v1"
)

func (s *TypesSuite) TestPointScanValue(c *C) {
	var a Point
	b := []byte{
		0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0x45, 0x36,
		0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0x36, 0x45, 0x38, 0x36,
		0x31, 0x42, 0x46, 0x30, 0x43, 0x44, 0x34, 0x32, 0x34, 0x30, 0x32, 0x31,
		0x39, 0x34, 0x46, 0x37, 0x37, 0x31, 0x33, 0x34, 0x45, 0x39, 0x34, 0x42,
		0x34, 0x30,
	}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, Point{Lon: 37.6088900, Lat: 55.8219130})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`SRID=4326;POINT(37.6088900 55.8219130)`))
}

func (s *TypesSuite) TestPoint(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	for _, p := range []Point{
		{Lon: 37.6088900, Lat: 55.8219130},
		{Lon: -37.6088900, Lat: -55.8219130},
		{Lon: 0, Lat: 0},
		{Lon: 0.00, Lat: 0.0},
	} {
		s.SetUpTest(c)

		_, err := s.db.Exec("INSERT INTO pq_types (point) VALUES($1)", p)
		c.Assert(err, IsNil)

		p1 := Point{Lon: -1, Lat: -1}
		err = s.db.QueryRow("SELECT point FROM pq_types").Scan(&p1)
		c.Check(err, IsNil)

		c.Check(p1, DeepEquals, p)
	}
}

func (s *TypesSuite) TestBox2DScanValue(c *C) {
	var a Box2D
	b := []byte{
		0x42, 0x4f, 0x58, 0x28, 0x30, 0x2e, 0x31, 0x32, 0x35, 0x20, 0x30, 0x2e,
		0x32, 0x35, 0x2c, 0x30, 0x2e, 0x35, 0x20, 0x31, 0x29,
	}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, Box2D{Min: Point{Lon: 0.125, Lat: 0.25}, Max: Point{Lon: 0.5, Lat: 1}})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`BOX(0.1250000 0.2500000,0.5000000 1.0000000)`))
}

func (s *TypesSuite) TestBox2D(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	for _, b := range []Box2D{
		{Min: Point{Lon: 0.125, Lat: 0.25}, Max: Point{Lon: 0.5, Lat: 1}},
		{Min: Point{Lon: -0.125, Lat: -0.25}, Max: Point{Lon: 0.5, Lat: 1}},
		{Min: Point{Lon: -0.55, Lat: -0.55}, Max: Point{Lon: 0.5, Lat: 1}},
	} {
		s.SetUpTest(c)

		_, err := s.db.Exec("INSERT INTO pq_types (box) VALUES($1)", b)
		c.Assert(err, IsNil)

		var b1 Box2D
		err = s.db.QueryRow("SELECT box FROM pq_types").Scan(&b1)
		c.Check(err, IsNil)

		c.Check(b1, DeepEquals, b)
	}
}

func (s *TypesSuite) TestPolygonScanValue(c *C) {
	var a Polygon
	b := []byte{
		0x30, 0x31, 0x30, 0x33, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0x45, 0x36,
		0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43, 0x30,
		0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x44, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43, 0x30, 0x33, 0x46, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x46, 0x30,
		0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x45, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x46, 0x30, 0x33, 0x46, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x45, 0x30,
		0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x44, 0x30, 0x33, 0x46, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x43, 0x30, 0x33, 0x46, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x44, 0x30,
		0x33, 0x46,
	}
	c.Check(a.Scan(b), IsNil)
	c.Check(a, DeepEquals, Polygon{
		Points: []Point{
			{Lon: 0.125, Lat: 0.25},
			{Lon: 0.125, Lat: 1},
			{Lon: 0.5, Lat: 1},
			{Lon: 0.5, Lat: 0.25},
			{Lon: 0.125, Lat: 0.25},
		},
	})
	v, err := a.Value()
	c.Check(err, IsNil)
	c.Check(v, DeepEquals, []byte(`SRID=4326;POLYGON((0.1250000 0.2500000,0.1250000 1.0000000,0.5000000 1.0000000,0.5000000 0.2500000,0.1250000 0.2500000))`))
}

func (s *TypesSuite) TestPolygon(c *C) {
	if s.skipPostGIS {
		c.Skip("PostGIS not available")
	}

	for _, p := range []Polygon{
		{
			Points: []Point{
				{Lon: 0.125, Lat: 0.25},
				{Lon: 0.125, Lat: 1},
				{Lon: 0.5, Lat: 1},
				{Lon: 0.5, Lat: 0.25},
				{Lon: 0.125, Lat: 0.25}}},
		{
			Points: []Point{
				{Lon: 0.0, Lat: 0.0},
				{Lon: -50.555, Lat: -50.555},
				{Lon: -50, Lat: 0},
				{Lon: 0, Lat: 0}}},
	} {
		s.SetUpTest(c)

		_, err := s.db.Exec("INSERT INTO pq_types (polygon) VALUES($1)", p)
		c.Assert(err, IsNil)

		var p1 Polygon
		err = s.db.QueryRow("SELECT polygon FROM pq_types").Scan(&p1)
		c.Check(err, IsNil)

		c.Check(p1, DeepEquals, p)
	}
}
<|endoftext|>