<commit_before>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     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 client

import (
	"bytes"
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/fleetspeak/fleetspeak/src/client/clienttestutils"
	"github.com/google/fleetspeak/fleetspeak/src/client/config"
	"github.com/google/fleetspeak/fleetspeak/src/client/service"
	"github.com/google/fleetspeak/fleetspeak/src/common"
	"github.com/google/fleetspeak/fleetspeak/src/comtesting"

	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
)

// A fakeService implements Service and passes all received messages into a channel.
type fakeService struct {
	c  chan *fspb.Message
	sc service.Context
}

func (s *fakeService) Start(sc service.Context) error {
	s.sc = sc
	return nil
}

func (s *fakeService) ProcessMessage(ctx context.Context, m *fspb.Message) error {
	s.c <- m
	return nil
}

func (s *fakeService) Stop() error {
	return nil
}

func TestMessageDelivery(t *testing.T) {
	msgs := make(chan *fspb.Message)
	fs := fakeService{c: msgs}

	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fs, nil
	}

	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}

	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}

	// Check that the service.Context provided to fs has a working LocalInfo.
	// TODO: move this to a separate test.
	li := fs.sc.GetLocalInfo()
	if li.ClientID != cl.config.ClientID() {
		t.Errorf("Expected LocalInfo to have ClientID: %v, got %v", cl.config.ClientID(), li.ClientID)
	}
	if !reflect.DeepEqual(li.Services, []string{"FakeService"}) {
		t.Errorf("Expected LocalInfo to be %v, got %v", []string{"FakeService"}, li.Services)
	}
}

func TestRekey(t *testing.T) {
	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP": service.NOOPFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	oid := cl.config.ClientID()

	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}

	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "system"},
				Destination: &fspb.Address{
					ClientId:    oid.Bytes(),
					ServiceName: "system"},
				MessageType: "RekeyRequest",
			},
		}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	tk := time.NewTicker(200 * time.Millisecond)
	start := time.Now()
	var nid common.ClientID
	defer tk.Stop()
	for range tk.C {
		nid = cl.config.ClientID()
		if nid != oid {
			break
		}
		if time.Since(start) > 20*time.Second {
			t.Errorf("Timed out waiting for id to change.")
			break
		}
	}
}

func TestMessageValidation(t *testing.T) {
	msgs := make(chan *fspb.Message)
	fs := fakeService{c: msgs}

	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fs, nil
	}

	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}

	for _, tc := range []struct {
		m    fspb.Message
		want string
	}{
		{m: fspb.Message{},
			want: "destination must have ServiceName",
		},
		{m: fspb.Message{Destination: &fspb.Address{ServiceName: ""}},
			want: "destination must have ServiceName",
		},
		{m: fspb.Message{Destination: &fspb.Address{ServiceName: "FakeService", ClientId: []byte("abcdef")}},
			want: "cannot send directly to client",
		},
	} {
		err := cl.ProcessMessage(context.Background(), service.AckMessage{M: &tc.m})
		if err == nil || !strings.HasPrefix(err.Error(), tc.want) {
			t.Errorf("ProcessMessage(%v) should give error starting with [%v] but got [%v]", tc.m, tc.want, err)
		}
	}

	cl.Stop()
}

func TestServiceValidation(t *testing.T) {
	tmpPath, fin := comtesting.GetTempDir("client_service_validation")
	defer fin()

	sp := filepath.Join(tmpPath, "services")
	if err := os.Mkdir(sp, 0777); err != nil {
		t.Fatalf("Unable to create services path [%s]: %v", sp, err)
	}

	msgs := make(chan *fspb.Message, 1)
	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fakeService{c: msgs}, nil
	}

	// This factory is used for service configs that shouldn't validate - if it does, it
	// causes the test to fail.
	failingServiceFactory := func(cfg *fspb.ClientServiceConfig) (service.Service, error) {
		t.Fatalf("failingServiceFactory called on %s", cfg.Name)
		return nil, fmt.Errorf("failingServiceFactory called")
	}

	// A service which should work.
	cfg := signServiceConfig(t, k, &fspb.ClientServiceConfig{
		Name:           "FakeService",
		Factory:        "FakeService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}},
	})
	if err := clienttestutils.WriteSignedServiceConfig(sp, "FakeService.signed", cfg); err != nil {
		t.Fatal(err)
	}

	// A service signed with the wrong key - should not be started.
	bk, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		t.Fatalf("Unable to generate bad deployment key: %v", err)
	}
	cfg = signServiceConfig(t, bk, &fspb.ClientServiceConfig{Name: "FailingServiceBadSig", Factory: "FailingService"})
	if err := clienttestutils.WriteSignedServiceConfig(sp, "FailingServiceBadSig.signed", cfg); err != nil {
		t.Fatal(err)
	}

	// A service signed with the right key, but requiring the wrong label.
	cfg = signServiceConfig(t, bk, &fspb.ClientServiceConfig{
		Name:           "FailingServiceBadLabel",
		Factory:        "FailingService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "windows"}},
	})
	if err := clienttestutils.WriteSignedServiceConfig(sp, "FailingServiceBadLabel.signed", cfg); err != nil {
		t.Fatal(err)
	}

	ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "")
	if err != nil {
		t.Fatal(err)
	}

	cl, err := New(
		config.Configuration{
			PersistenceHandler: ph,
			ClientLabels: []*fspb.Label{
				{ServiceName: "client", Label: "TestClient"},
				{ServiceName: "client", Label: "linux"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":           service.NOOPFactory,
				"FakeService":    fakeServiceFactory,
				"FailingService": failingServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	// Check that the good service started by passing a message through it.
	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}
	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}
}

func TestTextServiceConfig(t *testing.T) {
	tmpPath, fin := comtesting.GetTempDir("TestTextServiceConfig")
	defer fin()

	tsp := filepath.Join(tmpPath, "textservices")
	if err := os.Mkdir(tsp, 0777); err != nil {
		t.Fatalf("Unable to create services path [%s]: %v", tsp, err)
	}

	msgs := make(chan *fspb.Message, 1)
	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fakeService{c: msgs}, nil
	}

	// A text service.
	cfg := &fspb.ClientServiceConfig{
		Name:           "FakeService",
		Factory:        "FakeService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}},
	}
	if err := clienttestutils.WriteServiceConfig(tsp, "FakeService.txt", cfg); err != nil {
		t.Fatal(err)
	}

	ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "")
	if err != nil {
		t.Fatal(err)
	}

	cl, err := New(
		config.Configuration{
			PersistenceHandler: ph,
			ClientLabels: []*fspb.Label{
				{ServiceName: "client", Label: "TestClient"},
				{ServiceName: "client", Label: "linux"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	// Check that the service started by passing a message through it.
	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}
	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}
}

func signServiceConfig(t *testing.T, k *rsa.PrivateKey, cfg *fspb.ClientServiceConfig) *fspb.SignedClientServiceConfig {
	b, err := proto.Marshal(cfg)
	if err != nil {
		t.Fatalf("Unable to serialize service config: %v", err)
	}
	h := sha256.Sum256(b)
	sig, err := rsa.SignPSS(rand.Reader, k, crypto.SHA256, h[:], nil)
	if err != nil {
		t.Fatalf("Unable to sign service config: %v", err)
	}
	return &fspb.SignedClientServiceConfig{ServiceConfig: b, Signature: sig}
}
<commit_msg>Excise deployment key from a test.<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     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 client

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/google/fleetspeak/fleetspeak/src/client/clienttestutils"
	"github.com/google/fleetspeak/fleetspeak/src/client/config"
	"github.com/google/fleetspeak/fleetspeak/src/client/service"
	"github.com/google/fleetspeak/fleetspeak/src/common"
	"github.com/google/fleetspeak/fleetspeak/src/comtesting"

	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
)

// A fakeService implements Service and passes all received messages into a channel.
type fakeService struct {
	c  chan *fspb.Message
	sc service.Context
}

func (s *fakeService) Start(sc service.Context) error {
	s.sc = sc
	return nil
}

func (s *fakeService) ProcessMessage(ctx context.Context, m *fspb.Message) error {
	s.c <- m
	return nil
}

func (s *fakeService) Stop() error {
	return nil
}

func TestMessageDelivery(t *testing.T) {
	msgs := make(chan *fspb.Message)
	fs := fakeService{c: msgs}

	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fs, nil
	}

	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}

	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}

	// Check that the service.Context provided to fs has a working LocalInfo.
	// TODO: move this to a separate test.
	li := fs.sc.GetLocalInfo()
	if li.ClientID != cl.config.ClientID() {
		t.Errorf("Expected LocalInfo to have ClientID: %v, got %v", cl.config.ClientID(), li.ClientID)
	}
	if !reflect.DeepEqual(li.Services, []string{"FakeService"}) {
		t.Errorf("Expected LocalInfo to be %v, got %v", []string{"FakeService"}, li.Services)
	}
}

func TestRekey(t *testing.T) {
	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP": service.NOOPFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	oid := cl.config.ClientID()

	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}

	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "system"},
				Destination: &fspb.Address{
					ClientId:    oid.Bytes(),
					ServiceName: "system"},
				MessageType: "RekeyRequest",
			},
		}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	tk := time.NewTicker(200 * time.Millisecond)
	start := time.Now()
	var nid common.ClientID
	defer tk.Stop()
	for range tk.C {
		nid = cl.config.ClientID()
		if nid != oid {
			break
		}
		if time.Since(start) > 20*time.Second {
			t.Errorf("Timed out waiting for id to change.")
			break
		}
	}
}

func TestMessageValidation(t *testing.T) {
	msgs := make(chan *fspb.Message)
	fs := fakeService{c: msgs}

	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fs, nil
	}

	cl, err := New(
		config.Configuration{
			FixedServices: []*fspb.ClientServiceConfig{{Name: "FakeService", Factory: "FakeService"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}

	for _, tc := range []struct {
		m    fspb.Message
		want string
	}{
		{m: fspb.Message{},
			want: "destination must have ServiceName",
		},
		{m: fspb.Message{Destination: &fspb.Address{ServiceName: ""}},
			want: "destination must have ServiceName",
		},
		{m: fspb.Message{Destination: &fspb.Address{ServiceName: "FakeService", ClientId: []byte("abcdef")}},
			want: "cannot send directly to client",
		},
	} {
		err := cl.ProcessMessage(context.Background(), service.AckMessage{M: &tc.m})
		if err == nil || !strings.HasPrefix(err.Error(), tc.want) {
			t.Errorf("ProcessMessage(%v) should give error starting with [%v] but got [%v]", tc.m, tc.want, err)
		}
	}

	cl.Stop()
}

func TestServiceValidation(t *testing.T) {
	tmpPath, fin := comtesting.GetTempDir("client_service_validation")
	defer fin()

	sp := filepath.Join(tmpPath, "services")
	if err := os.Mkdir(sp, 0777); err != nil {
		t.Fatalf("Unable to create services path [%s]: %v", sp, err)
	}

	msgs := make(chan *fspb.Message, 1)
	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fakeService{c: msgs}, nil
	}

	// This factory is used for service configs that shouldn't validate - if it does, it
	// causes the test to fail.
	failingServiceFactory := func(cfg *fspb.ClientServiceConfig) (service.Service, error) {
		t.Fatalf("failingServiceFactory called on %s", cfg.Name)
		return nil, fmt.Errorf("failingServiceFactory called")
	}

	// A service which should work.
	cfg := signServiceConfig(t, &fspb.ClientServiceConfig{
		Name:           "FakeService",
		Factory:        "FakeService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}},
	})
	if err := clienttestutils.WriteSignedServiceConfig(sp, "FakeService.signed", cfg); err != nil {
		t.Fatal(err)
	}

	// A service requiring the wrong label.
	cfg = signServiceConfig(t, &fspb.ClientServiceConfig{
		Name:           "FailingServiceBadLabel",
		Factory:        "FailingService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "windows"}},
	})
	if err := clienttestutils.WriteSignedServiceConfig(sp, "FailingServiceBadLabel.signed", cfg); err != nil {
		t.Fatal(err)
	}

	ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "")
	if err != nil {
		t.Fatal(err)
	}

	cl, err := New(
		config.Configuration{
			PersistenceHandler: ph,
			ClientLabels: []*fspb.Label{
				{ServiceName: "client", Label: "TestClient"},
				{ServiceName: "client", Label: "linux"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":           service.NOOPFactory,
				"FakeService":    fakeServiceFactory,
				"FailingService": failingServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	// Check that the good service started by passing a message through it.
	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}
	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}
}

func TestTextServiceConfig(t *testing.T) {
	tmpPath, fin := comtesting.GetTempDir("TestTextServiceConfig")
	defer fin()

	tsp := filepath.Join(tmpPath, "textservices")
	if err := os.Mkdir(tsp, 0777); err != nil {
		t.Fatalf("Unable to create services path [%s]: %v", tsp, err)
	}

	msgs := make(chan *fspb.Message, 1)
	fakeServiceFactory := func(*fspb.ClientServiceConfig) (service.Service, error) {
		return &fakeService{c: msgs}, nil
	}

	// A text service.
	cfg := &fspb.ClientServiceConfig{
		Name:           "FakeService",
		Factory:        "FakeService",
		RequiredLabels: []*fspb.Label{{ServiceName: "client", Label: "linux"}},
	}
	if err := clienttestutils.WriteServiceConfig(tsp, "FakeService.txt", cfg); err != nil {
		t.Fatal(err)
	}

	ph, err := config.NewFilesystemPersistenceHandler(tmpPath, "")
	if err != nil {
		t.Fatal(err)
	}

	cl, err := New(
		config.Configuration{
			PersistenceHandler: ph,
			ClientLabels: []*fspb.Label{
				{ServiceName: "client", Label: "TestClient"},
				{ServiceName: "client", Label: "linux"}},
		},
		Components{
			ServiceFactories: map[string]service.Factory{
				"NOOP":        service.NOOPFactory,
				"FakeService": fakeServiceFactory,
			},
		})
	if err != nil {
		t.Fatalf("unable to create client: %v", err)
	}
	defer cl.Stop()

	// Check that the service started by passing a message through it.
	mid, err := common.RandomMessageID()
	if err != nil {
		t.Fatalf("unable to create message id: %v", err)
	}
	if err := cl.ProcessMessage(context.Background(),
		service.AckMessage{
			M: &fspb.Message{
				MessageId: mid.Bytes(),
				Source:    &fspb.Address{ServiceName: "FakeService"},
				Destination: &fspb.Address{
					ClientId:    cl.config.ClientID().Bytes(),
					ServiceName: "FakeService"},
			}}); err != nil {
		t.Fatalf("unable to process message: %v", err)
	}

	m := <-msgs

	if !bytes.Equal(m.MessageId, mid.Bytes()) {
		t.Errorf("Expected message with id: %v, got: %v", mid, m)
	}
}

func signServiceConfig(t *testing.T, cfg *fspb.ClientServiceConfig) *fspb.SignedClientServiceConfig {
	b, err := proto.Marshal(cfg)
	if err != nil {
		t.Fatalf("Unable to serialize service config: %v", err)
	}
	return &fspb.SignedClientServiceConfig{ServiceConfig: b}
}
<|endoftext|>


<commit_before>package testsupport

import (
	"fmt"
	"net"
	"strings"

	"github.com/square/certstrap/depot"
	"github.com/square/certstrap/pkix"
	"time"
)

type CertWriter struct {
	CertPath  string
	fileDepot *depot.FileDepot
}

const RSABits = 1024

func formatName(name string) string {
	return strings.Replace(name, " ", "_", -1)
}

func NewCertWriter(certPath string) (*CertWriter, error) {
	var (
		d   *depot.FileDepot
		err error
	)

	if d, err = depot.NewFileDepot(certPath); err != nil {
		return nil, fmt.Errorf("initialize depot: %s", err)
	}

	return &CertWriter{
		CertPath:  certPath,
		fileDepot: d,
	}, nil
}

func (c *CertWriter) WriteCA(caName string) (string, error) {
	key, err := pkix.CreateRSAKey(RSABits)
	if err != nil {
		return "", fmt.Errorf("create rsa key: %s", err)
	}

	expiry := time.Now().AddDate(5, 0, 0).UTC()
	crt, err := pkix.CreateCertificateAuthority(key, "", expiry, "", "", "", "", caName)
	if err != nil {
		return "", fmt.Errorf("create certificate authority: %s", err)
	}

	formattedCAName := formatName(caName)
	if err = depot.PutCertificate(c.fileDepot, formattedCAName, crt); err != nil {
		return "", fmt.Errorf("save certificate: %s", err)
	}

	if err = depot.PutPrivateKey(c.fileDepot, formattedCAName, key); err != nil {
		return "", fmt.Errorf("save private key: %s", err)
	}

	return fmt.Sprintf("%s/%s.crt", c.CertPath, formattedCAName), nil
}

func (c *CertWriter) WriteAndSign(commonName, caName string) (string, string, error) {
	key, err := pkix.CreateRSAKey(RSABits)
	if err != nil {
		return "", "", fmt.Errorf("create rsa key: %s", err)
	}

	csr, err := pkix.CreateCertificateSigningRequest(
		key, "", []net.IP{net.ParseIP("127.0.0.1")},
		[]string{commonName}, "", "", "", "", commonName,
	)
	if err != nil {
		return "", "", fmt.Errorf("create certificate request: %s", err)
	}

	formattedCommonName := formatName(commonName)
	if err = depot.PutPrivateKey(c.fileDepot, formattedCommonName, key); err != nil {
		return "", "", fmt.Errorf("save private key: %s", err)
	}

	formattedCAName := formatName(caName)
	crt, err := depot.GetCertificate(c.fileDepot, formattedCAName)
	if err != nil {
		return "", "", fmt.Errorf("get certificate: %s", err)
	}

	caKey, err := depot.GetPrivateKey(c.fileDepot, formattedCAName)
	if err != nil {
		return "", "", fmt.Errorf("get CA key: %s", err)
	}

	expiry := time.Now().AddDate(1, 0, 0).UTC()
	crtOut, err := pkix.CreateCertificateHost(crt, caKey, csr, expiry)
	if err != nil {
		return "", "", fmt.Errorf("create certificate host: %s", err)
	}

	if err = depot.PutCertificate(c.fileDepot, formattedCommonName, crtOut); err != nil {
		return "", "", fmt.Errorf("save certificate error: %s", err)
	}

	return fmt.Sprintf("%s/%s.crt", c.CertPath, formattedCommonName), fmt.Sprintf("%s/%s.key", c.CertPath, formattedCommonName), err
}
<commit_msg>Updated tls-helpers with new signature<commit_after>package testsupport

import (
	"fmt"
	"net"
	"strings"

	"time"

	"github.com/square/certstrap/depot"
	"github.com/square/certstrap/pkix"
)

type CertWriter struct {
	CertPath  string
	fileDepot *depot.FileDepot
}

const RSABits = 1024

func formatName(name string) string {
	return strings.Replace(name, " ", "_", -1)
}

func NewCertWriter(certPath string) (*CertWriter, error) {
	var (
		d   *depot.FileDepot
		err error
	)

	if d, err = depot.NewFileDepot(certPath); err != nil {
		return nil, fmt.Errorf("initialize depot: %s", err)
	}

	return &CertWriter{
		CertPath:  certPath,
		fileDepot: d,
	}, nil
}

func (c *CertWriter) WriteCA(caName string) (string, error) {
	key, err := pkix.CreateRSAKey(RSABits)
	if err != nil {
		return "", fmt.Errorf("create rsa key: %s", err)
	}

	expiry := time.Now().AddDate(5, 0, 0).UTC()
	crt, err := pkix.CreateCertificateAuthority(key, "", expiry, "", "", "", "", caName)
	if err != nil {
		return "", fmt.Errorf("create certificate authority: %s", err)
	}

	formattedCAName := formatName(caName)
	if err = depot.PutCertificate(c.fileDepot, formattedCAName, crt); err != nil {
		return "", fmt.Errorf("save certificate: %s", err)
	}

	if err = depot.PutPrivateKey(c.fileDepot, formattedCAName, key); err != nil {
		return "", fmt.Errorf("save private key: %s", err)
	}

	return fmt.Sprintf("%s/%s.crt", c.CertPath, formattedCAName), nil
}

func (c *CertWriter) WriteAndSign(commonName, caName string) (string, string, error) {
	key, err := pkix.CreateRSAKey(RSABits)
	if err != nil {
		return "", "", fmt.Errorf("create rsa key: %s", err)
	}

	csr, err := pkix.CreateCertificateSigningRequest(
		key, "", []net.IP{net.ParseIP("127.0.0.1")},
		[]string{commonName}, nil, "", "", "", "", commonName,
	)
	if err != nil {
		return "", "", fmt.Errorf("create certificate request: %s", err)
	}

	formattedCommonName := formatName(commonName)
	if err = depot.PutPrivateKey(c.fileDepot, formattedCommonName, key); err != nil {
		return "", "", fmt.Errorf("save private key: %s", err)
	}

	formattedCAName := formatName(caName)
	crt, err := depot.GetCertificate(c.fileDepot, formattedCAName)
	if err != nil {
		return "", "", fmt.Errorf("get certificate: %s", err)
	}

	caKey, err := depot.GetPrivateKey(c.fileDepot, formattedCAName)
	if err != nil {
		return "", "", fmt.Errorf("get CA key: %s", err)
	}

	expiry := time.Now().AddDate(1, 0, 0).UTC()
	crtOut, err := pkix.CreateCertificateHost(crt, caKey, csr, expiry)
	if err != nil {
		return "", "", fmt.Errorf("create certificate host: %s", err)
	}

	if err = depot.PutCertificate(c.fileDepot, formattedCommonName, crtOut); err != nil {
		return "", "", fmt.Errorf("save certificate error: %s", err)
	}

	return fmt.Sprintf("%s/%s.crt", c.CertPath, formattedCommonName), fmt.Sprintf("%s/%s.key", c.CertPath, formattedCommonName), err
}
<|endoftext|>


<commit_before>// Copyright 2015 The rkt 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.

// +build ignore

package main

import (
	"fmt"
	"os"

	"github.com/coreos/rkt/api/v1alpha"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

func main() {
	conn, err := grpc.Dial("localhost:15441", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	c := v1alpha.NewPublicAPIClient(conn)
	defer conn.Close()

	// List pods.
	podResp, err := c.ListPods(context.Background(), &v1alpha.ListPodsRequest{
		// Specify the request: Fetch and print only running pods and their details.
		Detail: true,
		Filters: []*v1alpha.PodFilter{
			{
				States: []v1alpha.PodState{v1alpha.PodState_POD_STATE_RUNNING},
			},
		},
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, p := range podResp.Pods {
		fmt.Printf("Pod %q is running\n", p.Id)

		logsResp, err := c.GetLogs(context.Background(), &v1alpha.GetLogsRequest{
			PodId: p.Id,
		})
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		logsRecvResp, err := logsResp.Recv()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		for _, l := range logsRecvResp.Lines {
			fmt.Println(l)
		}
	}

	// List images.
	imgResp, err := c.ListImages(context.Background(), &v1alpha.ListImagesRequest{
		// In this request, we fetch the details of images whose names are prefixed with "coreos.com".
		Detail: true,
		Filters: []*v1alpha.ImageFilter{
			{
				Prefixes: []string{"coreos.com"},
			},
		},
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, im := range imgResp.Images {
		fmt.Printf("Found image %q\n", im.Name)
	}
}
<commit_msg>api: client_example: GetLogs with follow<commit_after>// Copyright 2015 The rkt 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.

// +build ignore

package main

import (
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/coreos/rkt/api/v1alpha"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

func getLogsWithoutFollow(c v1alpha.PublicAPIClient, p *v1alpha.Pod) {
	logsResp, err := c.GetLogs(context.Background(), &v1alpha.GetLogsRequest{
		PodId:  p.Id,
		Follow: false,
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	logsRecvResp, err := logsResp.Recv()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, l := range logsRecvResp.Lines {
		fmt.Println(l)
	}
}

func getLogsWithFollow(c v1alpha.PublicAPIClient, p *v1alpha.Pod) {
	logsResp, err := c.GetLogs(context.Background(), &v1alpha.GetLogsRequest{
		PodId:  p.Id,
		Follow: true,
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for {
		logsRecvResp, err := logsResp.Recv()
		if err == io.EOF {
			return
		}

		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		for _, l := range logsRecvResp.Lines {
			fmt.Println(l)
		}
	}
}

func main() {
	followFlag := flag.Bool("follow", false, "enable 'follow' option on GetLogs")
	flag.Parse()

	conn, err := grpc.Dial("localhost:15441", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	c := v1alpha.NewPublicAPIClient(conn)
	defer conn.Close()

	// List pods.
	podResp, err := c.ListPods(context.Background(), &v1alpha.ListPodsRequest{
		// Specify the request: Fetch and print only running pods and their details.
		Detail: true,
		Filters: []*v1alpha.PodFilter{
			{
				States: []v1alpha.PodState{v1alpha.PodState_POD_STATE_RUNNING},
			},
		},
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, p := range podResp.Pods {
		if *followFlag {
			fmt.Printf("Pod %q is running. Following logs:\n", p.Id)
			getLogsWithFollow(c, p)
		} else {
			fmt.Printf("Pod %q is running.\n", p.Id)
			getLogsWithoutFollow(c, p)
		}
	}

	// List images.
	imgResp, err := c.ListImages(context.Background(), &v1alpha.ListImagesRequest{
		// In this request, we fetch the details of images whose names are prefixed with "coreos.com".
		Detail: true,
		Filters: []*v1alpha.ImageFilter{
			{
				Prefixes: []string{"coreos.com"},
			},
		},
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for _, im := range imgResp.Images {
		fmt.Printf("Found image %q\n", im.Name)
	}
}
<|endoftext|>


<commit_before>// mounttabled is a mount table daemon.
package main

import (
	"flag"
	"fmt"
	"os"
	"time"

	"veyron2"
	"veyron2/naming"
	"veyron2/rt"
	"veyron2/vlog"

	"veyron/lib/signals"

	"veyron/services/mounttable/lib"
)

var (
	mountName = flag.String("name", "", "Name to mount this mountable as.  Empty means don't mount.")
	// TODO(rthellend): Remove the address flag when the config manager is working.
	address = flag.String("address", ":0", "Address to listen on.  Default is to use a randomly assigned port")
	prefix  = flag.String("prefix", "mt", "The prefix to register the mounttable at.")
	aclFile = flag.String("acls", "", "ACL file. Default is to allow all access.")
	nhName  = flag.String("neighborhood_name", "", "If non-empty, publish in the local neighborhood under this name.")
)

const usage = `%s is a mount table daemon.

Usage:

  %s [--address=<local address>] [--name=<name>] [--neighborhood_name=<nh name>]

  <local address> is the the local address to listen on. By default, it will
  use a random port.

  <name>, if provided, causes the mount table to mount itself under that name.
  The name may be absolute for a remote mount table service (e.g., "/<remote mt
  address>//some/suffix") or could be relative to this process' default mount
  table (e.g., "some/suffix").

  <nh name>, if provided, will enable sharing with the local neighborhood with
  the provided name. The address of this mounttable will be published to the
  neighboorhood and everything in the neighborhood will be visible on this
  mounttable with the "nh" prefix.
`

func Usage() {
	fmt.Fprintf(os.Stderr, usage, os.Args[0], os.Args[0])
}

func main() {
	// TODO(cnicolaou): fix Usage so that it includes the flags defined by
	// the runtime
	flag.Usage = Usage
	r := rt.Init()
	defer r.Shutdown()

	server, err := r.NewServer(veyron2.ServesMountTableOpt(true))
	if err != nil {
		vlog.Errorf("r.NewServer failed: %v", err)
		return
	}
	defer server.Stop()
	mt, err := mounttable.NewMountTable(*aclFile)
	if err != nil {
		vlog.Errorf("r.NewMountTable failed: %v", err)
		return
	}
	if err := server.Register(*prefix, mt); err != nil {
		vlog.Errorf("server.Register failed to register mount table: %v", err)
		return
	}
	endpoint, err := server.Listen("tcp", *address)
	if err != nil {
		vlog.Errorf("server.Listen failed: %v", err)
		return
	}

	if len(*nhName) > 0 {
		mtAddr := naming.JoinAddressName(endpoint.String(), *prefix)

		nh, err := mounttable.NewNeighborhoodServer("", *nhName, mtAddr)
		if err != nil {
			vlog.Errorf("NewNeighborhoodServer failed: %v", err)
			return
		}
		nhPrefix := "nh"
		if err := server.Register(nhPrefix, nh); err != nil {
			vlog.Errorf("server.Register failed to register neighborhood: %v", err)
			return
		}
		nhAddr := naming.JoinAddressName(endpoint.String(), nhPrefix)
		nhMount := naming.Join(mtAddr, nhPrefix)

		ns := rt.R().Namespace()
		forever := time.Duration(0)
		if err = ns.Mount(rt.R().NewContext(), nhMount, nhAddr, forever); err != nil {
			vlog.Errorf("ns.Mount failed to mount neighborhood: %v", err)
			return
		}
	}

	if name := *mountName; len(name) > 0 {
		if err := server.Publish(name); err != nil {
			vlog.Errorf("Publish(%v) failed: %v", name, err)
			return
		}
		vlog.Infof("Mount table service at: %s (%s)",
			naming.Join(name, *prefix),
			naming.JoinAddressName(endpoint.String(), *prefix))

	} else {
		vlog.Infof("Mount table at: %s",
			naming.JoinAddressName(endpoint.String(), *prefix))
	}

	// Wait until signal is received.
	vlog.Info("Received signal ", <-signals.ShutdownOnSignals())
}
<commit_msg>veyron/services/mounttable/mounttabled: use two servers<commit_after>// mounttabled is a mount table daemon.
package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"time"

	"veyron2"
	"veyron2/naming"
	"veyron2/rt"
	"veyron2/vlog"

	"veyron/lib/signals"

	"veyron/services/mounttable/lib"
)

var (
	mountName = flag.String("name", "", "Name to mount this mountable as.  Empty means don't mount.")
	// TODO(rthellend): Remove the address flag when the config manager is working.
	address = flag.String("address", ":0", "Address to listen on.  Default is to use a randomly assigned port")
	prefix  = flag.String("prefix", "mt", "The prefix to register the mounttable at.")
	aclFile = flag.String("acls", "", "ACL file. Default is to allow all access.")
	nhName  = flag.String("neighborhood_name", "", "If non-empty, publish in the local neighborhood under this name.")
)

const usage = `%s is a mount table daemon.

Usage:

  %s [--address=<local address>] [--name=<name>] [--neighborhood_name=<nh name>]

  <local address> is the the local address to listen on. By default, it will
  use a random port.

  <name>, if provided, causes the mount table to mount itself under that name.
  The name may be absolute for a remote mount table service (e.g., "/<remote mt
  address>//some/suffix") or could be relative to this process' default mount
  table (e.g., "some/suffix").

  <nh name>, if provided, will enable sharing with the local neighborhood with
  the provided name. The address of this mounttable will be published to the
  neighboorhood and everything in the neighborhood will be visible on this
  mounttable with the "nh" prefix.
`

func Usage() {
	fmt.Fprintf(os.Stderr, usage, os.Args[0], os.Args[0])
}

func main() {
	// TODO(cnicolaou): fix Usage so that it includes the flags defined by
	// the runtime
	flag.Usage = Usage
	r := rt.Init()
	defer r.Shutdown()

	mtServer, err := r.NewServer(veyron2.ServesMountTableOpt(true))
	if err != nil {
		vlog.Errorf("r.NewServer failed: %v", err)
		return
	}
	defer mtServer.Stop()
	mt, err := mounttable.NewMountTable(*aclFile)
	if err != nil {
		vlog.Errorf("r.NewMountTable failed: %v", err)
		return
	}
	if err := mtServer.Register(*prefix, mt); err != nil {
		vlog.Errorf("mtServer.Register failed to register mount table: %v", err)
		return
	}
	mtEndpoint, err := mtServer.Listen("tcp", *address)
	if err != nil {
		vlog.Errorf("mtServer.Listen failed: %v", err)
		return
	}

	if len(*nhName) > 0 {
		nhServer, err := r.NewServer(veyron2.ServesMountTableOpt(true))
		if err != nil {
			vlog.Errorf("r.NewServer failed: %v", err)
			return
		}
		defer nhServer.Stop()
		host, _, err := net.SplitHostPort(*address)
		if err != nil {
			vlog.Errorf("parsing of address(%q) failed: %v", *address, err)
			return
		}
		nhEndpoint, err := nhServer.Listen("tcp", net.JoinHostPort(host, "0"))
		if err != nil {
			vlog.Errorf("nhServer.Listen failed: %v", err)
			return
		}
		mtAddr := naming.JoinAddressName(mtEndpoint.String(), *prefix)

		nh, err := mounttable.NewNeighborhoodServer("", *nhName, mtAddr)
		if err != nil {
			vlog.Errorf("NewNeighborhoodServer failed: %v", err)
			return
		}
		if err := nhServer.Register("", nh); err != nil {
			vlog.Errorf("nhServer.Register failed to register neighborhood: %v", err)
			return
		}
		nhAddr := naming.JoinAddressName(nhEndpoint.String(), "")
		nhMount := naming.Join(mtAddr, "nh")

		ns := rt.R().Namespace()
		forever := time.Duration(0)
		if err = ns.Mount(rt.R().NewContext(), nhMount, nhAddr, forever); err != nil {
			vlog.Errorf("ns.Mount failed to mount neighborhood: %v", err)
			return
		}
	}

	if name := *mountName; len(name) > 0 {
		if err := mtServer.Publish(name); err != nil {
			vlog.Errorf("Publish(%v) failed: %v", name, err)
			return
		}
		vlog.Infof("Mount table service at: %s (%s)",
			naming.Join(name, *prefix),
			naming.JoinAddressName(mtEndpoint.String(), *prefix))

	} else {
		vlog.Infof("Mount table at: %s",
			naming.JoinAddressName(mtEndpoint.String(), *prefix))
	}

	// Wait until signal is received.
	vlog.Info("Received signal ", <-signals.ShutdownOnSignals())
}
<|endoftext|>


<commit_before>// Clock counts down to or up from a target time.
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	target := time.Date(2018, 7, 1, 0, 0, 0, 0, time.Local)
	motto := "Just Go"
	printTargetTime(target, motto)
	exitOnEnterKey()

	var previous time.Time
	for {
		now := time.Now().Truncate(time.Second)
		if now != previous {
			previous = now
			countdown := now.Sub(target) // Negative times are before the target
			printCountdown(now, countdown)
		}
		time.Sleep(100 * time.Millisecond)
	}
}

func exitOnEnterKey() {
	go func() {
		buf := make([]byte, 1)
		_, _ = os.Stdin.Read(buf)
		os.Exit(0)
	}()
}

const (
	indent         = "\t"
	highlightStart = "\x1b[1;35m"
	highlightEnd   = "\x1b[0m"
)

func printTargetTime(target time.Time, motto string) {
	fmt.Print(indent, highlightStart, motto, highlightEnd, "\n")
	fmt.Print(indent, target.Format(time.UnixDate), "\n")
}

func printCountdown(now time.Time, countdown time.Duration) {
	var sign string
	if countdown >= 0 {
		sign = "+"
	} else {
		sign = "-"
		countdown = -countdown
	}

	days := int(countdown / (24 * time.Hour))
	countdown = countdown % (24 * time.Hour)

	fmt.Print(indent, now.Format(time.UnixDate), "  ", sign)
	if days > 0 {
		fmt.Print(days, "d")
	}
	fmt.Print(countdown, "          \r")
	os.Stdout.Sync()
}
<commit_msg>restart<commit_after>// Clock counts down to or up from a target time.
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	target := time.Date(2018, 7, 28, 0, 0, 0, 0, time.UTC)
	motto := "Just Go"
	printTargetTime(target, motto)
	exitOnEnterKey()

	var previous time.Time
	for {
		now := time.Now().Truncate(time.Second)
		if now != previous {
			previous = now
			countdown := now.Sub(target) // Negative times are before the target
			printCountdown(now, countdown)
		}
		time.Sleep(100 * time.Millisecond)
	}
}

func exitOnEnterKey() {
	go func() {
		buf := make([]byte, 1)
		_, _ = os.Stdin.Read(buf)
		os.Exit(0)
	}()
}

const (
	indent         = "\t"
	highlightStart = "\x1b[1;35m"
	highlightEnd   = "\x1b[0m"
)

func printTargetTime(target time.Time, motto string) {
	fmt.Print(indent, highlightStart, motto, highlightEnd, "\n")
	fmt.Print(indent, target.Format(time.UnixDate), "\n")
}

func printCountdown(now time.Time, countdown time.Duration) {
	var sign string
	if countdown >= 0 {
		sign = "+"
	} else {
		sign = "-"
		countdown = -countdown
	}

	days := int(countdown / (24 * time.Hour))
	countdown = countdown % (24 * time.Hour)

	fmt.Print(indent, now.Format(time.UnixDate), "  ", sign)
	if days > 0 {
		fmt.Print(days, "d")
	}
	fmt.Print(countdown, "          \r")
	os.Stdout.Sync()
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"crypto/md5"
	"encoding/hex"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"
	"gopkg.in/guregu/null.v3"
)

const groupSeparator = "::"

var ErrNameContainsGroupSeparator = errors.Errorf("group and check names may not contain '%s'", groupSeparator)

type SourceData struct {
	Data     []byte
	Filename string
}

type Stage struct {
	Duration NullDuration `json:"duration"`
	Target   null.Int     `json:"target"`
}

func (s *Stage) UnmarshalText(b []byte) error {
	var stage Stage
	parts := strings.SplitN(string(b), ":", 2)
	if len(parts) > 0 && parts[0] != "" {
		d, err := time.ParseDuration(parts[0])
		if err != nil {
			return err
		}
		stage.Duration = NullDurationFrom(d)
	}
	if len(parts) > 1 && parts[1] != "" {
		t, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return err
		}
		stage.Target = null.IntFrom(t)
	}
	*s = stage
	return nil
}

type Group struct {
	ID     string            `json:"id"`
	Path   string            `json:"path"`
	Name   string            `json:"name"`
	Parent *Group            `json:"parent"`
	Groups map[string]*Group `json:"groups"`
	Checks map[string]*Check `json:"checks"`

	groupMutex sync.Mutex
	checkMutex sync.Mutex
}

func NewGroup(name string, parent *Group) (*Group, error) {
	if strings.Contains(name, groupSeparator) {
		return nil, ErrNameContainsGroupSeparator
	}

	path := name
	if parent != nil {
		path = parent.Path + groupSeparator + path
	}

	hash := md5.Sum([]byte(path))
	id := hex.EncodeToString(hash[:])

	return &Group{
		ID:     id,
		Path:   path,
		Name:   name,
		Parent: parent,
		Groups: make(map[string]*Group),
		Checks: make(map[string]*Check),
	}, nil
}

func (g *Group) Group(name string) (*Group, error) {
	snapshot := g.Groups
	group, ok := snapshot[name]
	if !ok {
		g.groupMutex.Lock()
		defer g.groupMutex.Unlock()

		group, ok := g.Groups[name]
		if !ok {
			group, err := NewGroup(name, g)
			if err != nil {
				return nil, err
			}
			g.Groups[name] = group
			return group, nil
		}
		return group, nil
	}
	return group, nil
}

func (g *Group) Check(name string) (*Check, error) {
	snapshot := g.Checks
	check, ok := snapshot[name]
	if !ok {
		g.checkMutex.Lock()
		defer g.checkMutex.Unlock()
		check, ok := g.Checks[name]
		if !ok {
			check, err := NewCheck(name, g)
			if err != nil {
				return nil, err
			}
			g.Checks[name] = check
			return check, nil
		}
		return check, nil
	}
	return check, nil
}

type Check struct {
	ID    string `json:"id"`
	Path  string `json:"path"`
	Group *Group `json:"group"`
	Name  string `json:"name"`

	Passes int64 `json:"passes"`
	Fails  int64 `json:"fails"`
}

func NewCheck(name string, group *Group) (*Check, error) {
	if strings.Contains(name, groupSeparator) {
		return nil, ErrNameContainsGroupSeparator
	}

	path := group.Path + groupSeparator + name
	hash := md5.Sum([]byte(path))
	id := hex.EncodeToString(hash[:])

	return &Check{
		ID:    id,
		Path:  path,
		Group: group,
		Name:  name,
	}, nil
}
<commit_msg>Fixed JSON unmarshalling of stages<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"
	"gopkg.in/guregu/null.v3"
)

const groupSeparator = "::"

var ErrNameContainsGroupSeparator = errors.Errorf("group and check names may not contain '%s'", groupSeparator)

type SourceData struct {
	Data     []byte
	Filename string
}

type StageFields struct {
	Duration NullDuration `json:"duration"`
	Target   null.Int     `json:"target"`
}

type Stage StageFields

// For some reason, implementing UnmarshalText makes encoding/json treat the type as a string.
func (s *Stage) UnmarshalJSON(b []byte) error {
	var fields StageFields
	if err := json.Unmarshal(b, &fields); err != nil {
		return err
	}
	*s = Stage(fields)
	return nil
}

func (s *Stage) UnmarshalText(b []byte) error {
	var stage Stage
	parts := strings.SplitN(string(b), ":", 2)
	if len(parts) > 0 && parts[0] != "" {
		d, err := time.ParseDuration(parts[0])
		if err != nil {
			return err
		}
		stage.Duration = NullDurationFrom(d)
	}
	if len(parts) > 1 && parts[1] != "" {
		t, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return err
		}
		stage.Target = null.IntFrom(t)
	}
	*s = stage
	return nil
}

type Group struct {
	ID     string            `json:"id"`
	Path   string            `json:"path"`
	Name   string            `json:"name"`
	Parent *Group            `json:"parent"`
	Groups map[string]*Group `json:"groups"`
	Checks map[string]*Check `json:"checks"`

	groupMutex sync.Mutex
	checkMutex sync.Mutex
}

func NewGroup(name string, parent *Group) (*Group, error) {
	if strings.Contains(name, groupSeparator) {
		return nil, ErrNameContainsGroupSeparator
	}

	path := name
	if parent != nil {
		path = parent.Path + groupSeparator + path
	}

	hash := md5.Sum([]byte(path))
	id := hex.EncodeToString(hash[:])

	return &Group{
		ID:     id,
		Path:   path,
		Name:   name,
		Parent: parent,
		Groups: make(map[string]*Group),
		Checks: make(map[string]*Check),
	}, nil
}

func (g *Group) Group(name string) (*Group, error) {
	snapshot := g.Groups
	group, ok := snapshot[name]
	if !ok {
		g.groupMutex.Lock()
		defer g.groupMutex.Unlock()

		group, ok := g.Groups[name]
		if !ok {
			group, err := NewGroup(name, g)
			if err != nil {
				return nil, err
			}
			g.Groups[name] = group
			return group, nil
		}
		return group, nil
	}
	return group, nil
}

func (g *Group) Check(name string) (*Check, error) {
	snapshot := g.Checks
	check, ok := snapshot[name]
	if !ok {
		g.checkMutex.Lock()
		defer g.checkMutex.Unlock()
		check, ok := g.Checks[name]
		if !ok {
			check, err := NewCheck(name, g)
			if err != nil {
				return nil, err
			}
			g.Checks[name] = check
			return check, nil
		}
		return check, nil
	}
	return check, nil
}

type Check struct {
	ID    string `json:"id"`
	Path  string `json:"path"`
	Group *Group `json:"group"`
	Name  string `json:"name"`

	Passes int64 `json:"passes"`
	Fails  int64 `json:"fails"`
}

func NewCheck(name string, group *Group) (*Check, error) {
	if strings.Contains(name, groupSeparator) {
		return nil, ErrNameContainsGroupSeparator
	}

	path := group.Path + groupSeparator + name
	hash := md5.Sum([]byte(path))
	id := hex.EncodeToString(hash[:])

	return &Check{
		ID:    id,
		Path:  path,
		Group: group,
		Name:  name,
	}, nil
}
<|endoftext|>


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

package minio

import (
	"bufio"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"time"

	"github.com/minio/minio-go/pkg/s3utils"
)

// GetBucketNotification - get bucket notification at a given path.
func (c Client) GetBucketNotification(bucketName string) (bucketNotification BucketNotification, err error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return BucketNotification{}, err
	}
	notification, err := c.getBucketNotification(bucketName)
	if err != nil {
		return BucketNotification{}, err
	}
	return notification, nil
}

// Request server for notification rules.
func (c Client) getBucketNotification(bucketName string) (BucketNotification, error) {
	urlValues := make(url.Values)
	urlValues.Set("notification", "")

	// Execute GET on bucket to list objects.
	resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
		bucketName:       bucketName,
		queryValues:      urlValues,
		contentSHA256Hex: emptySHA256Hex,
	})

	defer closeResponse(resp)
	if err != nil {
		return BucketNotification{}, err
	}
	return processBucketNotificationResponse(bucketName, resp)

}

// processes the GetNotification http response from the server.
func processBucketNotificationResponse(bucketName string, resp *http.Response) (BucketNotification, error) {
	if resp.StatusCode != http.StatusOK {
		errResponse := httpRespToErrorResponse(resp, bucketName, "")
		return BucketNotification{}, errResponse
	}
	var bucketNotification BucketNotification
	err := xmlDecoder(resp.Body, &bucketNotification)
	if err != nil {
		return BucketNotification{}, err
	}
	return bucketNotification, nil
}

// Indentity represents the user id, this is a compliance field.
type identity struct {
	PrincipalID string `json:"principalId"`
}

// Notification event bucket metadata.
type bucketMeta struct {
	Name          string   `json:"name"`
	OwnerIdentity identity `json:"ownerIdentity"`
	ARN           string   `json:"arn"`
}

// Notification event object metadata.
type objectMeta struct {
	Key       string `json:"key"`
	Size      int64  `json:"size,omitempty"`
	ETag      string `json:"eTag,omitempty"`
	VersionID string `json:"versionId,omitempty"`
	Sequencer string `json:"sequencer"`
}

// Notification event server specific metadata.
type eventMeta struct {
	SchemaVersion   string     `json:"s3SchemaVersion"`
	ConfigurationID string     `json:"configurationId"`
	Bucket          bucketMeta `json:"bucket"`
	Object          objectMeta `json:"object"`
}

// sourceInfo represents information on the client that
// triggered the event notification.
type sourceInfo struct {
	Host      string `json:"host"`
	Port      string `json:"port"`
	UserAgent string `json:"userAgent"`
}

// NotificationEvent represents an Amazon an S3 bucket notification event.
type NotificationEvent struct {
	EventVersion      string            `json:"eventVersion"`
	EventSource       string            `json:"eventSource"`
	AwsRegion         string            `json:"awsRegion"`
	EventTime         string            `json:"eventTime"`
	EventName         string            `json:"eventName"`
	UserIdentity      identity          `json:"userIdentity"`
	RequestParameters map[string]string `json:"requestParameters"`
	ResponseElements  map[string]string `json:"responseElements"`
	S3                eventMeta         `json:"s3"`
	Source            sourceInfo        `json:"source"`
}

// NotificationInfo - represents the collection of notification events, additionally
// also reports errors if any while listening on bucket notifications.
type NotificationInfo struct {
	Records []NotificationEvent
	Err     error
}

// ListenBucketNotification - listen on bucket notifications.
func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, events []string, doneCh <-chan struct{}) <-chan NotificationInfo {
	notificationInfoCh := make(chan NotificationInfo, 1)
	// Only success, start a routine to start reading line by line.
	go func(notificationInfoCh chan<- NotificationInfo) {
		defer close(notificationInfoCh)

		// Validate the bucket name.
		if err := s3utils.CheckValidBucketName(bucketName); err != nil {
			notificationInfoCh <- NotificationInfo{
				Err: err,
			}
			return
		}

		// Check ARN partition to verify if listening bucket is supported
		if s3utils.IsAmazonEndpoint(*c.endpointURL) || s3utils.IsGoogleEndpoint(*c.endpointURL) {
			notificationInfoCh <- NotificationInfo{
				Err: ErrAPINotSupported("Listening for bucket notification is specific only to `minio` server endpoints"),
			}
			return
		}

		// Continuously run and listen on bucket notification.
		// Create a done channel to control 'ListObjects' go routine.
		retryDoneCh := make(chan struct{}, 1)

		// Indicate to our routine to exit cleanly upon return.
		defer close(retryDoneCh)

		// Wait on the jitter retry loop.
		for range c.newRetryTimerContinous(time.Second, time.Second*30, MaxJitter, retryDoneCh) {
			urlValues := make(url.Values)
			urlValues.Set("prefix", prefix)
			urlValues.Set("suffix", suffix)
			urlValues["events"] = events

			// Execute GET on bucket to list objects.
			resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
				bucketName:       bucketName,
				queryValues:      urlValues,
				contentSHA256Hex: emptySHA256Hex,
			})
			if err != nil {
				notificationInfoCh <- NotificationInfo{
					Err: err,
				}
				return
			}

			// Validate http response, upon error return quickly.
			if resp.StatusCode != http.StatusOK {
				errResponse := httpRespToErrorResponse(resp, bucketName, "")
				notificationInfoCh <- NotificationInfo{
					Err: errResponse,
				}
				return
			}

			// Initialize a new bufio scanner, to read line by line.
			bio := bufio.NewScanner(resp.Body)

			// Close the response body.
			defer resp.Body.Close()

			// Unmarshal each line, returns marshalled values.
			for bio.Scan() {
				var notificationInfo NotificationInfo
				if err = json.Unmarshal(bio.Bytes(), &notificationInfo); err != nil {
					continue
				}
				// Send notifications on channel only if there are events received.
				if len(notificationInfo.Records) > 0 {
					select {
					case notificationInfoCh <- notificationInfo:
					case <-doneCh:
						return
					}
				}
			}
			// Look for any underlying errors.
			if err = bio.Err(); err != nil {
				// For an unexpected connection drop from server, we close the body
				// and re-connect.
				if err == io.ErrUnexpectedEOF {
					resp.Body.Close()
				}
			}
		}
	}(notificationInfoCh)

	// Returns the notification info channel, for caller to start reading from.
	return notificationInfoCh
}
<commit_msg>Removing conditional check for notificationInfoCh in api-notication (#940)<commit_after>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2017 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"bufio"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"time"

	"github.com/minio/minio-go/pkg/s3utils"
)

// GetBucketNotification - get bucket notification at a given path.
func (c Client) GetBucketNotification(bucketName string) (bucketNotification BucketNotification, err error) {
	// Input validation.
	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
		return BucketNotification{}, err
	}
	notification, err := c.getBucketNotification(bucketName)
	if err != nil {
		return BucketNotification{}, err
	}
	return notification, nil
}

// Request server for notification rules.
func (c Client) getBucketNotification(bucketName string) (BucketNotification, error) {
	urlValues := make(url.Values)
	urlValues.Set("notification", "")

	// Execute GET on bucket to list objects.
	resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
		bucketName:       bucketName,
		queryValues:      urlValues,
		contentSHA256Hex: emptySHA256Hex,
	})

	defer closeResponse(resp)
	if err != nil {
		return BucketNotification{}, err
	}
	return processBucketNotificationResponse(bucketName, resp)

}

// processes the GetNotification http response from the server.
func processBucketNotificationResponse(bucketName string, resp *http.Response) (BucketNotification, error) {
	if resp.StatusCode != http.StatusOK {
		errResponse := httpRespToErrorResponse(resp, bucketName, "")
		return BucketNotification{}, errResponse
	}
	var bucketNotification BucketNotification
	err := xmlDecoder(resp.Body, &bucketNotification)
	if err != nil {
		return BucketNotification{}, err
	}
	return bucketNotification, nil
}

// Indentity represents the user id, this is a compliance field.
type identity struct {
	PrincipalID string `json:"principalId"`
}

// Notification event bucket metadata.
type bucketMeta struct {
	Name          string   `json:"name"`
	OwnerIdentity identity `json:"ownerIdentity"`
	ARN           string   `json:"arn"`
}

// Notification event object metadata.
type objectMeta struct {
	Key       string `json:"key"`
	Size      int64  `json:"size,omitempty"`
	ETag      string `json:"eTag,omitempty"`
	VersionID string `json:"versionId,omitempty"`
	Sequencer string `json:"sequencer"`
}

// Notification event server specific metadata.
type eventMeta struct {
	SchemaVersion   string     `json:"s3SchemaVersion"`
	ConfigurationID string     `json:"configurationId"`
	Bucket          bucketMeta `json:"bucket"`
	Object          objectMeta `json:"object"`
}

// sourceInfo represents information on the client that
// triggered the event notification.
type sourceInfo struct {
	Host      string `json:"host"`
	Port      string `json:"port"`
	UserAgent string `json:"userAgent"`
}

// NotificationEvent represents an Amazon an S3 bucket notification event.
type NotificationEvent struct {
	EventVersion      string            `json:"eventVersion"`
	EventSource       string            `json:"eventSource"`
	AwsRegion         string            `json:"awsRegion"`
	EventTime         string            `json:"eventTime"`
	EventName         string            `json:"eventName"`
	UserIdentity      identity          `json:"userIdentity"`
	RequestParameters map[string]string `json:"requestParameters"`
	ResponseElements  map[string]string `json:"responseElements"`
	S3                eventMeta         `json:"s3"`
	Source            sourceInfo        `json:"source"`
}

// NotificationInfo - represents the collection of notification events, additionally
// also reports errors if any while listening on bucket notifications.
type NotificationInfo struct {
	Records []NotificationEvent
	Err     error
}

// ListenBucketNotification - listen on bucket notifications.
func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, events []string, doneCh <-chan struct{}) <-chan NotificationInfo {
	notificationInfoCh := make(chan NotificationInfo, 1)
	// Only success, start a routine to start reading line by line.
	go func(notificationInfoCh chan<- NotificationInfo) {
		defer close(notificationInfoCh)

		// Validate the bucket name.
		if err := s3utils.CheckValidBucketName(bucketName); err != nil {
			notificationInfoCh <- NotificationInfo{
				Err: err,
			}
			return
		}

		// Check ARN partition to verify if listening bucket is supported
		if s3utils.IsAmazonEndpoint(*c.endpointURL) || s3utils.IsGoogleEndpoint(*c.endpointURL) {
			notificationInfoCh <- NotificationInfo{
				Err: ErrAPINotSupported("Listening for bucket notification is specific only to `minio` server endpoints"),
			}
			return
		}

		// Continuously run and listen on bucket notification.
		// Create a done channel to control 'ListObjects' go routine.
		retryDoneCh := make(chan struct{}, 1)

		// Indicate to our routine to exit cleanly upon return.
		defer close(retryDoneCh)

		// Wait on the jitter retry loop.
		for range c.newRetryTimerContinous(time.Second, time.Second*30, MaxJitter, retryDoneCh) {
			urlValues := make(url.Values)
			urlValues.Set("prefix", prefix)
			urlValues.Set("suffix", suffix)
			urlValues["events"] = events

			// Execute GET on bucket to list objects.
			resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
				bucketName:       bucketName,
				queryValues:      urlValues,
				contentSHA256Hex: emptySHA256Hex,
			})
			if err != nil {
				notificationInfoCh <- NotificationInfo{
					Err: err,
				}
				return
			}

			// Validate http response, upon error return quickly.
			if resp.StatusCode != http.StatusOK {
				errResponse := httpRespToErrorResponse(resp, bucketName, "")
				notificationInfoCh <- NotificationInfo{
					Err: errResponse,
				}
				return
			}

			// Initialize a new bufio scanner, to read line by line.
			bio := bufio.NewScanner(resp.Body)

			// Close the response body.
			defer resp.Body.Close()

			// Unmarshal each line, returns marshalled values.
			for bio.Scan() {
				var notificationInfo NotificationInfo
				if err = json.Unmarshal(bio.Bytes(), &notificationInfo); err != nil {
					continue
				}
				// Send notificationInfo
				select {
				case notificationInfoCh <- notificationInfo:
				case <-doneCh:
					return
				}
			}
			// Look for any underlying errors.
			if err = bio.Err(); err != nil {
				// For an unexpected connection drop from server, we close the body
				// and re-connect.
				if err == io.ErrUnexpectedEOF {
					resp.Body.Close()
				}
			}
		}
	}(notificationInfoCh)

	// Returns the notification info channel, for caller to start reading from.
	return notificationInfoCh
}
<|endoftext|>


<commit_before>package api

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

const testProxyLBName = "test_libsakuracloud_proxylb"

func TestProxyLBCreate(t *testing.T) {
	defer initProxyLB()()

	currentRegion := client.Zone
	defer func() { client.Zone = currentRegion }()
	client.Zone = "is1a"

	item := client.ProxyLB.New(testProxyLBName)
	assert.Equal(t, item.Name, testProxyLBName)

	item.SetSorryServer("133.242.0.3", 80)
	item.SetHTTPHealthCheck("libsacloud.com", "/", 0)
	item.AddBindPort("http", 80)
	item.AddServer("133.242.0.4", 80, true)

	item, err := client.ProxyLB.Create(item)

	assert.NoError(t, err)

	assert.Equal(t, item.Settings.ProxyLB.SorryServer.IPAddress, "133.242.0.3")
	assert.Equal(t, *item.Settings.ProxyLB.SorryServer.Port, 80)

	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Protocol, "http")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Host, "libsacloud.com")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Path, "/")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.DelayLoop, 10)
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Port, 0)

	assert.Len(t, item.Settings.ProxyLB.BindPorts, 1)
	assert.Len(t, item.Settings.ProxyLB.Servers, 1)

	assert.Equal(t, item.Settings.ProxyLB.BindPorts[0].ProxyMode, "http")
	assert.Equal(t, item.Settings.ProxyLB.BindPorts[0].Port, 80)

	assert.Equal(t, item.Settings.ProxyLB.Servers[0].IPAddress, "133.242.0.4")
	assert.Equal(t, item.Settings.ProxyLB.Servers[0].Port, 80)
	assert.Equal(t, item.Settings.ProxyLB.Servers[0].Enabled, true)

	client.ProxyLB.Delete(item.ID)
}

func initProxyLB() func() {
	cleanupProxyLB()
	return cleanupProxyLB
}

func cleanupProxyLB() {
	items, _ := client.ProxyLB.Reset().WithNameLike(testProxyLBName).Find()

	for _, item := range items.CommonServiceProxyLBItems {
		client.ProxyLB.Delete(item.ID)
	}
}
<commit_msg>Adjest testcases<commit_after>package api

import (
	"testing"

	"github.com/sacloud/libsacloud/sacloud"
	"github.com/stretchr/testify/assert"
)

const testProxyLBName = "test_libsakuracloud_proxylb"

func TestProxyLBCreate(t *testing.T) {
	defer initProxyLB()()

	currentRegion := client.Zone
	defer func() { client.Zone = currentRegion }()
	client.Zone = "is1a"

	item := client.ProxyLB.New(testProxyLBName)
	assert.Equal(t, item.Name, testProxyLBName)

	item.SetPlan(sacloud.ProxyLBPlan5000)
	item.SetSorryServer("133.242.0.3", 80)
	item.SetHTTPHealthCheck("libsacloud.com", "/", 0)
	item.AddBindPort("http", 80)
	item.AddServer("133.242.0.4", 80, true)

	item, err := client.ProxyLB.Create(item)

	assert.NoError(t, err)

	assert.Equal(t, item.GetPlan(), sacloud.ProxyLBPlan5000)

	assert.Equal(t, item.Settings.ProxyLB.SorryServer.IPAddress, "133.242.0.3")
	assert.Equal(t, *item.Settings.ProxyLB.SorryServer.Port, 80)

	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Protocol, "http")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Host, "libsacloud.com")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.Path, "/")
	assert.Equal(t, item.Settings.ProxyLB.HealthCheck.DelayLoop, 10)

	assert.Len(t, item.Settings.ProxyLB.BindPorts, 1)
	assert.Len(t, item.Settings.ProxyLB.Servers, 1)

	assert.Equal(t, item.Settings.ProxyLB.BindPorts[0].ProxyMode, "http")
	assert.Equal(t, item.Settings.ProxyLB.BindPorts[0].Port, 80)

	assert.Equal(t, item.Settings.ProxyLB.Servers[0].IPAddress, "133.242.0.4")
	assert.Equal(t, item.Settings.ProxyLB.Servers[0].Port, 80)
	assert.Equal(t, item.Settings.ProxyLB.Servers[0].Enabled, true)

	client.ProxyLB.Delete(item.ID)
}

func initProxyLB() func() {
	cleanupProxyLB()
	return cleanupProxyLB
}

func cleanupProxyLB() {
	items, _ := client.ProxyLB.Reset().WithNameLike(testProxyLBName).Find()

	for _, item := range items.CommonServiceProxyLBItems {
		client.ProxyLB.Delete(item.ID)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// +build windows

package libdokan

import (
	"sync"

	"github.com/keybase/kbfs/dokan"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

type fileOpener interface {
	open(ctx context.Context, oc *openContext, path []string) (f dokan.File, isDir bool, err error)
	dokan.File
}

// FolderList is a node that can list all of the logged-in user's
// favorite top-level folders, on either a public or private basis.
type FolderList struct {
	emptyFile
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]fileOpener
}

// GetFileInformation for dokan.
func (*FolderList) GetFileInformation(*dokan.FileInfo) (*dokan.Stat, error) {
	return defaultDirectoryInformation()
}

func (fl *FolderList) reportErr(ctx context.Context,
	mode libkbfs.ErrorModeType, tlfName libkbfs.CanonicalTlfName, err error, cancelFn func()) {
	if cancelFn != nil {
		defer cancelFn()
	}
	if err == nil {
		fl.fs.log.CDebugf(ctx, "Request complete")
		return
	}

	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.public, mode, err)
	// We just log the error as debug, rather than error, because it
	// might just indicate an expected error such as an ENOENT.
	//
	// TODO: Classify errors and escalate the logging level of the
	// important ones.
	fl.fs.log.CDebugf(ctx, err.Error())

}

// open tries to open the correct thing. Following aliases and deferring to
// Dir.open as necessary.
func (fl *FolderList) open(ctx context.Context, oc *openContext, path []string) (f dokan.File, isDir bool, err error) {
	fl.fs.log.CDebugf(ctx, "FL Lookup %#v", path)
	if len(path) == 0 {
		return oc.returnDirNoCleanup(fl)
	}

	defer func() {
		fl.reportErr(ctx, libkbfs.ReadMode, libkbfs.CanonicalTlfName(path[0]), err, nil)
	}()

	for oc.reduceRedirectionsLeft() {
		name := path[0]

		if name == "desktop.ini" {
			fl.fs.log.CDebugf(ctx, "FL Lookup ignoring desktop.ini")
			return nil, false, dokan.ErrObjectNameNotFound
		}

		fl.mu.Lock()
		child, ok := fl.folders[name]
		fl.mu.Unlock()

		if ok {
			fl.fs.log.CDebugf(ctx, "FL Lookup recursing to child %q", name)
			return child.open(ctx, oc, path[1:])
		}

		if len(path) == 1 && oc.isCreateDirectory() && isNewFolderName(name) {
			fl.fs.log.CDebugf(ctx, "FL Lookup creating EmptyFolder for Explorer")
			e := &EmptyFolder{}
			fl.lockedAddChild(name, e)
			return e, true, nil
		}

		h, err := libkbfs.ParseTlfHandle(
			ctx, fl.fs.config.KBPKI(), name, fl.public)
		fl.fs.log.CDebugf(ctx, "FL Lookup continuing -> %v,%v", h, err)
		switch err := err.(type) {
		case nil:
			// No error.
			break

		case libkbfs.TlfNameNotCanonical:
			// Only permit Aliases to targets that contain no errors.
			if !fl.isValidAliasTarget(ctx, err.NameToTry) {
				fl.fs.log.CDebugf(ctx, "FL Refusing alias to non-valid target %q", err.NameToTry)
				return nil, false, dokan.ErrObjectNameNotFound
			}

			if len(path) == 1 && oc.isOpenReparsePoint() {
				// Non-canonical name.
				n := &Alias{
					canon: err.NameToTry,
				}
				return n, true, nil
			}
			path[0] = err.NameToTry
			continue

		case libkbfs.NoSuchNameError, libkbfs.BadTLFNameError:
			return nil, false, dokan.ErrObjectNameNotFound

		default:
			// Some other error.
			return nil, false, err
		}

		fl.fs.log.CDebugf(ctx, "FL Lookup adding new child")
		child = newTLF(fl, h)
		fl.lockedAddChild(name, child)
		return child.open(ctx, oc, path[1:])
	}
	return nil, false, dokan.ErrObjectNameNotFound
}

func (fl *FolderList) forgetFolder(folderName string) {
	fl.mu.Lock()
	defer fl.mu.Unlock()
	delete(fl.folders, folderName)
}

// FindFiles for dokan.
func (fl *FolderList) FindFiles(fi *dokan.FileInfo, callback func(*dokan.NamedStat) error) (err error) {
	ctx, cancel := NewContextWithOpID(fl.fs, "FL FindFiles")
	defer func() { fl.fs.reportErr(ctx, libkbfs.ReadMode, err, cancel) }()

	_, _, err = fl.fs.config.KBPKI().GetCurrentUserInfo(ctx)
	isLoggedIn := err == nil

	var favs []libkbfs.Favorite
	if isLoggedIn {
		favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx)
		if err != nil {
			return err
		}
	}
	var ns dokan.NamedStat
	ns.FileAttributes = fileAttributeDirectory
	ns.NumberOfLinks = 1
	empty := true
	for _, fav := range favs {
		if fav.Public != fl.public {
			continue
		}
		empty = false
		ns.Name = fav.Name
		err = callback(&ns)
		if err != nil {
			return err
		}
	}
	if empty {
		return dokan.ErrObjectNameNotFound
	}
	return nil
}

func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool {
	return libkbfs.CheckTlfHandleOffline(ctx, nameToTry, fl.public) == nil
}

func (fl *FolderList) lockedAddChild(name string, val fileOpener) {
	fl.mu.Lock()
	fl.folders[name] = val
	fl.mu.Unlock()
}

func (fl *FolderList) updateTlfName(ctx context.Context, oldName string,
	newName string) {
	fl.mu.Lock()
	defer fl.mu.Unlock()
	tlf, ok := fl.folders[oldName]
	if !ok {
		return
	}

	fl.fs.log.CDebugf(ctx, "Folder name updated: %s -> %s", oldName, newName)
	delete(fl.folders, oldName)
	fl.folders[newName] = tlf
	// TODO: invalidate kernel cache for this name? (Make sure to
	// do so outside of the lock!)
}
<commit_msg>libdokan: Better logging in folderlist open<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// +build windows

package libdokan

import (
	"sync"

	"github.com/keybase/kbfs/dokan"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

type fileOpener interface {
	open(ctx context.Context, oc *openContext, path []string) (f dokan.File, isDir bool, err error)
	dokan.File
}

// FolderList is a node that can list all of the logged-in user's
// favorite top-level folders, on either a public or private basis.
type FolderList struct {
	emptyFile
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]fileOpener
}

// GetFileInformation for dokan.
func (*FolderList) GetFileInformation(*dokan.FileInfo) (*dokan.Stat, error) {
	return defaultDirectoryInformation()
}

func (fl *FolderList) reportErr(ctx context.Context,
	mode libkbfs.ErrorModeType, tlfName libkbfs.CanonicalTlfName, err error, cancelFn func()) {
	if cancelFn != nil {
		defer cancelFn()
	}
	if err == nil {
		fl.fs.log.CDebugf(ctx, "Request complete")
		return
	}

	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.public, mode, err)
	// We just log the error as debug, rather than error, because it
	// might just indicate an expected error such as an ENOENT.
	//
	// TODO: Classify errors and escalate the logging level of the
	// important ones.
	fl.fs.log.CDebugf(ctx, err.Error())

}

// open tries to open the correct thing. Following aliases and deferring to
// Dir.open as necessary.
func (fl *FolderList) open(ctx context.Context, oc *openContext, path []string) (f dokan.File, isDir bool, err error) {
	fl.fs.log.CDebugf(ctx, "FL Lookup %#v public=%v", path, fl.public)
	if len(path) == 0 {
		return oc.returnDirNoCleanup(fl)
	}

	defer func() {
		fl.reportErr(ctx, libkbfs.ReadMode, libkbfs.CanonicalTlfName(path[0]), err, nil)
	}()

	for oc.reduceRedirectionsLeft() {
		name := path[0]

		if name == "desktop.ini" {
			fl.fs.log.CDebugf(ctx, "FL Lookup ignoring desktop.ini")
			return nil, false, dokan.ErrObjectNameNotFound
		}

		fl.mu.Lock()
		child, ok := fl.folders[name]
		fl.mu.Unlock()

		if ok {
			fl.fs.log.CDebugf(ctx, "FL Lookup recursing to child %q", name)
			return child.open(ctx, oc, path[1:])
		}

		if len(path) == 1 && oc.isCreateDirectory() && isNewFolderName(name) {
			fl.fs.log.CDebugf(ctx, "FL Lookup creating EmptyFolder for Explorer")
			e := &EmptyFolder{}
			fl.lockedAddChild(name, e)
			return e, true, nil
		}

		h, err := libkbfs.ParseTlfHandle(
			ctx, fl.fs.config.KBPKI(), name, fl.public)
		fl.fs.log.CDebugf(ctx, "FL Lookup continuing -> %v,%v", h, err)
		switch err := err.(type) {
		case nil:
			// No error.
			break

		case libkbfs.TlfNameNotCanonical:
			// Only permit Aliases to targets that contain no errors.
			if !fl.isValidAliasTarget(ctx, err.NameToTry) {
				fl.fs.log.CDebugf(ctx, "FL Refusing alias to non-valid target %q", err.NameToTry)
				return nil, false, dokan.ErrObjectNameNotFound
			}

			if len(path) == 1 && oc.isOpenReparsePoint() {
				// Non-canonical name.
				n := &Alias{
					canon: err.NameToTry,
				}
				return n, true, nil
			}
			path[0] = err.NameToTry
			continue

		case libkbfs.NoSuchNameError, libkbfs.BadTLFNameError:
			return nil, false, dokan.ErrObjectNameNotFound

		default:
			// Some other error.
			return nil, false, err
		}

		fl.fs.log.CDebugf(ctx, "FL Lookup adding new child")
		child = newTLF(fl, h)
		fl.lockedAddChild(name, child)
		return child.open(ctx, oc, path[1:])
	}
	return nil, false, dokan.ErrObjectNameNotFound
}

func (fl *FolderList) forgetFolder(folderName string) {
	fl.mu.Lock()
	defer fl.mu.Unlock()
	delete(fl.folders, folderName)
}

// FindFiles for dokan.
func (fl *FolderList) FindFiles(fi *dokan.FileInfo, callback func(*dokan.NamedStat) error) (err error) {
	ctx, cancel := NewContextWithOpID(fl.fs, "FL FindFiles")
	defer func() { fl.fs.reportErr(ctx, libkbfs.ReadMode, err, cancel) }()

	_, _, err = fl.fs.config.KBPKI().GetCurrentUserInfo(ctx)
	isLoggedIn := err == nil

	var favs []libkbfs.Favorite
	if isLoggedIn {
		favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx)
		if err != nil {
			return err
		}
	}
	var ns dokan.NamedStat
	ns.FileAttributes = fileAttributeDirectory
	ns.NumberOfLinks = 1
	empty := true
	for _, fav := range favs {
		if fav.Public != fl.public {
			continue
		}
		empty = false
		ns.Name = fav.Name
		err = callback(&ns)
		if err != nil {
			return err
		}
	}
	if empty {
		return dokan.ErrObjectNameNotFound
	}
	return nil
}

func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool {
	return libkbfs.CheckTlfHandleOffline(ctx, nameToTry, fl.public) == nil
}

func (fl *FolderList) lockedAddChild(name string, val fileOpener) {
	fl.mu.Lock()
	fl.folders[name] = val
	fl.mu.Unlock()
}

func (fl *FolderList) updateTlfName(ctx context.Context, oldName string,
	newName string) {
	fl.mu.Lock()
	defer fl.mu.Unlock()
	tlf, ok := fl.folders[oldName]
	if !ok {
		return
	}

	fl.fs.log.CDebugf(ctx, "Folder name updated: %s -> %s", oldName, newName)
	delete(fl.folders, oldName)
	fl.folders[newName] = tlf
	// TODO: invalidate kernel cache for this name? (Make sure to
	// do so outside of the lock!)
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"encoding/xml"
	"github.com/lunny/html2md"
	"io/ioutil"
	"net/http"
	"time"
)

type disqusThread struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
	URL     string   `xml:"link"`
	Name    string   `xml:"name"`
}

type disqusAuthor struct {
	XMLName     xml.Name `xml:"author"`
	IsAnonymous bool     `xml:"isAnonymous"`
	Name        string   `xml:"name"`
	Email       string   `xml:"email"`
}

type disqusThreadId struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusParentId struct {
	XMLName xml.Name `xml:"parent"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPostId struct {
	XMLName xml.Name `xml:"post"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPost struct {
	XMLName      xml.Name       `xml:"post"`
	Id           string         `xml:"http://disqus.com/disqus-internals id,attr"`
	ThreadId     disqusThreadId `xml:"thread"`
	ParentId     disqusParentId `xml:"parent"`
	PostId       disqusPostId   `xml:"post"`
	Message      string         `xml:"message"`
	CreationDate time.Time      `xml:"createdAt"`
	IsDeleted    bool           `xml:"isDeleted"`
	IsSpam       bool           `xml:"isSpam"`
	Author       disqusAuthor   `xml:"author"`
}

type disqusXML struct {
	XMLName xml.Name       `xml:"disqus"`
	Threads []disqusThread `xml:"thread"`
	Posts   []disqusPost   `xml:"post"`
}

func domainImportDisqus(domain string, url string) (int, error) {
	if domain == "" || url == "" {
		return 0, errorMissingField
	}

	// TODO: make sure this is from disqus.com
	resp, err := http.Get(url)
	if err != nil {
		logger.Errorf("cannot get url: %v", err)
		return 0, errorCannotDownloadDisqus
	}

	defer resp.Body.Close()

	zr, err := gzip.NewReader(resp.Body)
	if err != nil {
		logger.Errorf("cannot create gzip reader: %v", err)
		return 0, errorInternal
	}

	contents, err := ioutil.ReadAll(zr)
	if err != nil {
		logger.Errorf("cannot read gzip contents uncompressed: %v", err)
		return 0, errorInternal
	}

	x := disqusXML{}
	err = xml.Unmarshal(contents, &x)
	if err != nil {
		logger.Errorf("cannot unmarshal XML: %v", err)
		return 0, errorInternal
	}

	// Map Disqus thread IDs to threads.
	threads := make(map[string]disqusThread)
	for _, thread := range x.Threads {
		threads[thread.Id] = thread
	}

	// Map Disqus emails to commenterHex (if not available, create a new one
	// with a random password that can be reset later).
	commenterHex := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		if _, ok := commenterHex[post.Author.Email]; ok {
			continue
		}

		c, err := commenterGetByEmail("commento", post.Author.Email)
		if err != nil && err != errorNoSuchCommenter {
			logger.Errorf("cannot get commenter by email: %v", err)
			return 0, errorInternal
		}

		if err == nil {
			commenterHex[post.Author.Email] = c.CommenterHex
			continue
		}

		randomPassword, err := randomHex(32)
		if err != nil {
			logger.Errorf("cannot generate random password for new commenter: %v", err)
			return 0, errorInternal
		}

		commenterHex[post.Author.Email], err = commenterNew(post.Author.Email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
		if err != nil {
			return 0, err
		}
	}

	// For each Disqus post, create a Commento comment. Attempt to convert the
	// HTML to markdown.
	numImported := 0
	disqusIdMap := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		parentHex := "root"
		if val, ok := disqusIdMap[post.ParentId.Id]; ok {
			parentHex = val
		}

		// TODO: restrict the list of tags to just the basics: , , ,
		// Especially remove  (convert it to ).
		commentHex, err := commentNew(
			commenterHex[post.Author.Email],
			domain,
			pathStrip(threads[post.ThreadId.Id].URL),
			parentHex,
			html2md.Convert(post.Message),
			"approved",
			post.CreationDate)
		if err != nil {
			return numImported, err
		}

		disqusIdMap[post.PostId.Id] = commentHex
		numImported += 1
	}

	return numImported, nil
}

func domainImportDisqusHandler(w http.ResponseWriter, r *http.Request) {
	type request struct {
		OwnerToken *string `json:"ownerToken"`
		Domain     *string `json:"domain"`
		URL        *string `json:"url"`
	}

	var x request
	if err := bodyUnmarshal(r, &x); err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	o, err := ownerGetByOwnerToken(*x.OwnerToken)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	domain := domainStrip(*x.Domain)
	isOwner, err := domainOwnershipVerify(o.OwnerHex, domain)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	if !isOwner {
		bodyMarshal(w, response{"success": false, "message": errorNotAuthorised.Error()})
		return
	}

	numImported, err := domainImportDisqus(domain, *x.URL)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	bodyMarshal(w, response{"success": true, "numImported": numImported})
}
<commit_msg>domain_import_disqus.go: update disqus export spec<commit_after>package main

import (
	"compress/gzip"
	"encoding/xml"
	"github.com/lunny/html2md"
	"io/ioutil"
	"net/http"
	"time"
)

type disqusThread struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
	URL     string   `xml:"link"`
	Name    string   `xml:"name"`
}

type disqusAuthor struct {
	XMLName     xml.Name `xml:"author"`
	Name        string   `xml:"name"`
	IsAnonymous bool     `xml:"isAnonymous"`
	Username    string   `xml:"username"`
}

type disqusThreadId struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusParentId struct {
	XMLName xml.Name `xml:"parent"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPostId struct {
	XMLName xml.Name `xml:"post"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPost struct {
	XMLName      xml.Name       `xml:"post"`
	Id           string         `xml:"http://disqus.com/disqus-internals id,attr"`
	ThreadId     disqusThreadId `xml:"thread"`
	ParentId     disqusParentId `xml:"parent"`
	Message      string         `xml:"message"`
	CreationDate time.Time      `xml:"createdAt"`
	IsDeleted    bool           `xml:"isDeleted"`
	IsSpam       bool           `xml:"isSpam"`
	Author       disqusAuthor   `xml:"author"`
}

type disqusXML struct {
	XMLName xml.Name       `xml:"disqus"`
	Threads []disqusThread `xml:"thread"`
	Posts   []disqusPost   `xml:"post"`
}

func domainImportDisqus(domain string, url string) (int, error) {
	if domain == "" || url == "" {
		return 0, errorMissingField
	}

	// TODO: make sure this is from disqus.com
	resp, err := http.Get(url)
	if err != nil {
		logger.Errorf("cannot get url: %v", err)
		return 0, errorCannotDownloadDisqus
	}

	defer resp.Body.Close()

	zr, err := gzip.NewReader(resp.Body)
	if err != nil {
		logger.Errorf("cannot create gzip reader: %v", err)
		return 0, errorInternal
	}

	contents, err := ioutil.ReadAll(zr)
	if err != nil {
		logger.Errorf("cannot read gzip contents uncompressed: %v", err)
		return 0, errorInternal
	}

	x := disqusXML{}
	err = xml.Unmarshal(contents, &x)
	if err != nil {
		logger.Errorf("cannot unmarshal XML: %v", err)
		return 0, errorInternal
	}

	// Map Disqus thread IDs to threads.
	threads := make(map[string]disqusThread)
	for _, thread := range x.Threads {
		threads[thread.Id] = thread
	}

	// Map Disqus emails to commenterHex (if not available, create a new one
	// with a random password that can be reset later).
	commenterHex := map[string]string{}
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		email := post.Author.Username + "@disqus.com"

		if _, ok := commenterHex[email]; ok {
			continue
		}

		c, err := commenterGetByEmail("commento", email)
		if err != nil && err != errorNoSuchCommenter {
			logger.Errorf("cannot get commenter by email: %v", err)
			return 0, errorInternal
		}

		if err == nil {
			commenterHex[email] = c.CommenterHex
			continue
		}

		randomPassword, err := randomHex(32)
		if err != nil {
			logger.Errorf("cannot generate random password for new commenter: %v", err)
			return 0, errorInternal
		}

		commenterHex[email], err = commenterNew(email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
		if err != nil {
			return 0, err
		}
	}

	// For each Disqus post, create a Commento comment. Attempt to convert the
	// HTML to markdown.
	numImported := 0
	disqusIdMap := map[string]string{}
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		cHex := "anonymous"
		if !post.Author.IsAnonymous {
			cHex = commenterHex[post.Author.Username+"@disqus.com"]
		}

		parentHex := "root"
		if val, ok := disqusIdMap[post.ParentId.Id]; ok {
			parentHex = val
		}

		// TODO: restrict the list of tags to just the basics: , , ,
		// Especially remove  (convert it to ).
		commentHex, err := commentNew(
			cHex,
			domain,
			pathStrip(threads[post.ThreadId.Id].URL),
			parentHex,
			html2md.Convert(post.Message),
			"approved",
			post.CreationDate)
		if err != nil {
			return numImported, err
		}

		disqusIdMap[post.Id] = commentHex
		numImported += 1
	}

	return numImported, nil
}

func domainImportDisqusHandler(w http.ResponseWriter, r *http.Request) {
	type request struct {
		OwnerToken *string `json:"ownerToken"`
		Domain     *string `json:"domain"`
		URL        *string `json:"url"`
	}

	var x request
	if err := bodyUnmarshal(r, &x); err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	o, err := ownerGetByOwnerToken(*x.OwnerToken)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	domain := domainStrip(*x.Domain)
	isOwner, err := domainOwnershipVerify(o.OwnerHex, domain)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	if !isOwner {
		bodyMarshal(w, response{"success": false, "message": errorNotAuthorised.Error()})
		return
	}

	numImported, err := domainImportDisqus(domain, *x.URL)
	if err != nil {
		bodyMarshal(w, response{"success": false, "message": err.Error()})
		return
	}

	bodyMarshal(w, response{"success": true, "numImported": numImported})
}
<|endoftext|>