<commit_before>package repository

import (
	"crypto/sha256"
	"io"
	"io/ioutil"
	"os"
	"restic"
	"restic/crypto"
	"restic/debug"
	"restic/hashing"
	"restic/pack"

	"restic/errors"
)

// Repack takes a list of packs together with a list of blobs contained in
// these packs. Each pack is loaded and the blobs listed in keepBlobs is saved
// into a new pack. Afterwards, the packs are removed. This operation requires
// an exclusive lock on the repo.
func Repack(repo restic.Repository, packs restic.IDSet, keepBlobs restic.BlobSet) (err error) {
	debug.Log("repacking %d packs while keeping %d blobs", len(packs), len(keepBlobs))

	for packID := range packs {
		// load the complete pack into a temp file
		h := restic.Handle{Type: restic.DataFile, Name: packID.String()}

		tempfile, err := ioutil.TempFile("", "restic-temp-repack-")
		if err != nil {
			return errors.Wrap(err, "TempFile")
		}

		beRd, err := repo.Backend().Load(h, 0, 0)
		if err != nil {
			return err
		}

		defer beRd.Close()

		hrd := hashing.NewReader(beRd, sha256.New())
		packLength, err := io.Copy(tempfile, hrd)
		if err != nil {
			return errors.Wrap(err, "Copy")
		}

		hash := restic.IDFromHash(hrd.Sum(nil))
		debug.Log("pack %v loaded (%d bytes), hash %v", packID.Str(), packLength, hash.Str())

		if !packID.Equal(hash) {
			return errors.Errorf("hash does not match id: want %v, got %v", packID, hash)
		}

		_, err = tempfile.Seek(0, 0)
		if err != nil {
			return errors.Wrap(err, "Seek")
		}

		blobs, err := pack.List(repo.Key(), tempfile, packLength)
		if err != nil {
			return err
		}

		debug.Log("processing pack %v, blobs: %v", packID.Str(), len(blobs))
		var buf []byte
		for _, entry := range blobs {
			h := restic.BlobHandle{ID: entry.ID, Type: entry.Type}
			if !keepBlobs.Has(h) {
				continue
			}

			debug.Log("  process blob %v", h)

			buf = buf[:len(buf)]
			if uint(len(buf)) < entry.Length {
				buf = make([]byte, entry.Length)
			}
			buf = buf[:entry.Length]

			n, err := tempfile.ReadAt(buf, int64(entry.Offset))
			if err != nil {
				return errors.Wrap(err, "ReadAt")
			}

			if n != len(buf) {
				return errors.Errorf("read blob %v from %v: not enough bytes read, want %v, got %v",
					h, tempfile.Name(), len(buf), n)
			}

			n, err = crypto.Decrypt(repo.Key(), buf, buf)
			if err != nil {
				return err
			}

			buf = buf[:n]

			id := restic.Hash(buf)
			if !id.Equal(entry.ID) {
				return errors.Errorf("read blob %v from %v: wrong data returned, hash is %v",
					h, tempfile.Name(), id)
			}

			_, err = repo.SaveBlob(entry.Type, buf, entry.ID)
			if err != nil {
				return err
			}

			debug.Log("  saved blob %v", entry.ID.Str())

			keepBlobs.Delete(h)
		}

		if err = tempfile.Close(); err != nil {
			return errors.Wrap(err, "Close")
		}

		if err = os.Remove(tempfile.Name()); err != nil {
			return errors.Wrap(err, "Remove")
		}
	}

	if err := repo.Flush(); err != nil {
		return err
	}

	for packID := range packs {
		h := restic.Handle{Type: restic.DataFile, Name: packID.String()}
		err := repo.Backend().Remove(h)
		if err != nil {
			debug.Log("error removing pack %v: %v", packID.Str(), err)
			return err
		}
		debug.Log("removed pack %v", packID.Str())
	}

	return nil
}
<commit_msg>prune: Close backend reader after download<commit_after>package repository

import (
	"crypto/sha256"
	"io"
	"io/ioutil"
	"os"
	"restic"
	"restic/crypto"
	"restic/debug"
	"restic/hashing"
	"restic/pack"

	"restic/errors"
)

// Repack takes a list of packs together with a list of blobs contained in
// these packs. Each pack is loaded and the blobs listed in keepBlobs is saved
// into a new pack. Afterwards, the packs are removed. This operation requires
// an exclusive lock on the repo.
func Repack(repo restic.Repository, packs restic.IDSet, keepBlobs restic.BlobSet) (err error) {
	debug.Log("repacking %d packs while keeping %d blobs", len(packs), len(keepBlobs))

	for packID := range packs {
		// load the complete pack into a temp file
		h := restic.Handle{Type: restic.DataFile, Name: packID.String()}

		tempfile, err := ioutil.TempFile("", "restic-temp-repack-")
		if err != nil {
			return errors.Wrap(err, "TempFile")
		}

		beRd, err := repo.Backend().Load(h, 0, 0)
		if err != nil {
			return err
		}

		hrd := hashing.NewReader(beRd, sha256.New())
		packLength, err := io.Copy(tempfile, hrd)
		if err != nil {
			return errors.Wrap(err, "Copy")
		}

		if err = beRd.Close(); err != nil {
			return errors.Wrap(err, "Close")
		}

		hash := restic.IDFromHash(hrd.Sum(nil))
		debug.Log("pack %v loaded (%d bytes), hash %v", packID.Str(), packLength, hash.Str())

		if !packID.Equal(hash) {
			return errors.Errorf("hash does not match id: want %v, got %v", packID, hash)
		}

		_, err = tempfile.Seek(0, 0)
		if err != nil {
			return errors.Wrap(err, "Seek")
		}

		blobs, err := pack.List(repo.Key(), tempfile, packLength)
		if err != nil {
			return err
		}

		debug.Log("processing pack %v, blobs: %v", packID.Str(), len(blobs))
		var buf []byte
		for _, entry := range blobs {
			h := restic.BlobHandle{ID: entry.ID, Type: entry.Type}
			if !keepBlobs.Has(h) {
				continue
			}

			debug.Log("  process blob %v", h)

			buf = buf[:len(buf)]
			if uint(len(buf)) < entry.Length {
				buf = make([]byte, entry.Length)
			}
			buf = buf[:entry.Length]

			n, err := tempfile.ReadAt(buf, int64(entry.Offset))
			if err != nil {
				return errors.Wrap(err, "ReadAt")
			}

			if n != len(buf) {
				return errors.Errorf("read blob %v from %v: not enough bytes read, want %v, got %v",
					h, tempfile.Name(), len(buf), n)
			}

			n, err = crypto.Decrypt(repo.Key(), buf, buf)
			if err != nil {
				return err
			}

			buf = buf[:n]

			id := restic.Hash(buf)
			if !id.Equal(entry.ID) {
				return errors.Errorf("read blob %v from %v: wrong data returned, hash is %v",
					h, tempfile.Name(), id)
			}

			_, err = repo.SaveBlob(entry.Type, buf, entry.ID)
			if err != nil {
				return err
			}

			debug.Log("  saved blob %v", entry.ID.Str())

			keepBlobs.Delete(h)
		}

		if err = tempfile.Close(); err != nil {
			return errors.Wrap(err, "Close")
		}

		if err = os.Remove(tempfile.Name()); err != nil {
			return errors.Wrap(err, "Remove")
		}
	}

	if err := repo.Flush(); err != nil {
		return err
	}

	for packID := range packs {
		h := restic.Handle{Type: restic.DataFile, Name: packID.String()}
		err := repo.Backend().Remove(h)
		if err != nil {
			debug.Log("error removing pack %v: %v", packID.Str(), err)
			return err
		}
		debug.Log("removed pack %v", packID.Str())
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package accesscontrol

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/common/crypto/tlsgen"
	"github.com/hyperledger/fabric/common/flogging/floggingtest"
	pb "github.com/hyperledger/fabric/protos/peer"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap/zapcore"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

type ccSrv struct {
	l              net.Listener
	grpcSrv        *grpc.Server
	t              *testing.T
	expectedCCname string
}

func (cs *ccSrv) Register(stream pb.ChaincodeSupport_RegisterServer) error {
	msg, err := stream.Recv()
	if err != nil {
		return err
	}

	// First message is a register message
	assert.Equal(cs.t, pb.ChaincodeMessage_REGISTER.String(), msg.Type.String())
	// And its chaincode name is the expected one
	chaincodeID := &pb.ChaincodeID{}
	err = proto.Unmarshal(msg.Payload, chaincodeID)
	if err != nil {
		return err
	}
	assert.Equal(cs.t, cs.expectedCCname, chaincodeID.Name)
	// Subsequent messages are just echoed back
	for {
		msg, _ = stream.Recv()
		if err != nil {
			return err
		}
		err = stream.Send(msg)
		if err != nil {
			return err
		}
	}
}

func (cs *ccSrv) stop() {
	cs.grpcSrv.Stop()
	cs.l.Close()
}

func createTLSService(t *testing.T, ca tlsgen.CA, host string) *grpc.Server {
	keyPair, err := ca.NewServerCertKeyPair(host)
	cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
	assert.NoError(t, err)
	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    x509.NewCertPool(),
	}
	tlsConf.ClientCAs.AppendCertsFromPEM(ca.CertBytes())
	return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConf)))
}

func newCCServer(t *testing.T, port int, expectedCCname string, withTLS bool, ca tlsgen.CA) *ccSrv {
	var s *grpc.Server
	if withTLS {
		s = createTLSService(t, ca, "localhost")
	} else {
		s = grpc.NewServer()
	}

	l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "", port))
	assert.NoError(t, err, "%v", err)
	return &ccSrv{
		t:              t,
		expectedCCname: expectedCCname,
		l:              l,
		grpcSrv:        s,
	}
}

type ccClient struct {
	conn   *grpc.ClientConn
	stream pb.ChaincodeSupport_RegisterClient
}

func newClient(t *testing.T, port int, cert *tls.Certificate, peerCACert []byte) (*ccClient, error) {
	tlsCfg := &tls.Config{
		RootCAs: x509.NewCertPool(),
	}

	tlsCfg.RootCAs.AppendCertsFromPEM(peerCACert)
	if cert != nil {
		tlsCfg.Certificates = []tls.Certificate{*cert}
	}
	tlsOpts := grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	conn, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), tlsOpts, grpc.WithBlock())
	if err != nil {
		return nil, err
	}
	chaincodeSupportClient := pb.NewChaincodeSupportClient(conn)
	stream, err := chaincodeSupportClient.Register(context.Background())
	assert.NoError(t, err)
	return &ccClient{
		conn:   conn,
		stream: stream,
	}, nil
}

func (c *ccClient) close() {
	c.conn.Close()
}

func (c *ccClient) sendMsg(msg *pb.ChaincodeMessage) {
	c.stream.Send(msg)
}

func (c *ccClient) recv() *pb.ChaincodeMessage {
	msgs := make(chan *pb.ChaincodeMessage, 1)
	go func() {
		msg, _ := c.stream.Recv()
		if msg != nil {
			msgs <- msg
		}
	}()
	select {
	case <-time.After(time.Second):
		return nil
	case msg := <-msgs:
		return msg
	}
}

func TestAccessControl(t *testing.T) {
	backupTTL := ttl
	defer func() {
		ttl = backupTTL
	}()
	ttl = time.Second * 3

	oldLogger := logger
	l, recorder := floggingtest.NewTestLogger(t, floggingtest.AtLevel(zapcore.InfoLevel))
	logger = l
	defer func() { logger = oldLogger }()

	chaincodeID := &pb.ChaincodeID{Name: "example02"}
	payload, err := proto.Marshal(chaincodeID)
	registerMsg := &pb.ChaincodeMessage{
		Type:    pb.ChaincodeMessage_REGISTER,
		Payload: payload,
	}
	putStateMsg := &pb.ChaincodeMessage{
		Type: pb.ChaincodeMessage_PUT_STATE,
	}

	ca, _ := tlsgen.NewCA()
	srv := newCCServer(t, 7052, "example02", true, ca)
	auth := NewAuthenticator(ca)
	pb.RegisterChaincodeSupportServer(srv.grpcSrv, auth.Wrap(srv))
	go srv.grpcSrv.Serve(srv.l)
	defer srv.stop()

	// Create an attacker without a TLS certificate
	_, err = newClient(t, 7052, nil, ca.CertBytes())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "context deadline exceeded")

	// Create an attacker with its own TLS certificate
	maliciousCA, _ := tlsgen.NewCA()
	keyPair, err := maliciousCA.NewClientCertKeyPair()
	cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
	assert.NoError(t, err)
	_, err = newClient(t, 7052, &cert, ca.CertBytes())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "context deadline exceeded")

	// Create a chaincode for example01 that tries to impersonate example02
	kp, err := auth.Generate("example01")
	assert.NoError(t, err)
	keyBytes, err := base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err := base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	mismatchedShim, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer mismatchedShim.close()
	mismatchedShim.sendMsg(registerMsg)
	mismatchedShim.sendMsg(putStateMsg)
	// Mismatched chaincode didn't get back anything
	assert.Nil(t, mismatchedShim.recv())
	assertLogContains(t, recorder, "with given certificate hash", "belongs to a different chaincode")

	// Create the real chaincode that its cert is generated by us that should pass the security checks
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	realCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer realCC.close()
	realCC.sendMsg(registerMsg)
	realCC.sendMsg(putStateMsg)
	echoMsg := realCC.recv()
	// The real chaincode should be echoed back its message
	assert.NotNil(t, echoMsg)
	assert.Equal(t, pb.ChaincodeMessage_PUT_STATE, echoMsg.Type)
	// Log should not complain about anything
	assert.Empty(t, recorder.Messages())

	// Create the real chaincode that its cert is generated by us
	// but one that the first message sent by it isn't a register message.
	// The second message that is sent is a register message but it's "too late"
	// and the stream is already denied.
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	confusedCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer confusedCC.close()
	confusedCC.sendMsg(putStateMsg)
	confusedCC.sendMsg(registerMsg)
	confusedCC.sendMsg(putStateMsg)
	assert.Nil(t, confusedCC.recv())
	assertLogContains(t, recorder, "expected a ChaincodeMessage_REGISTER message")

	// Create a real chaincode, that its cert was generated by us
	// but it sends a malformed first message
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	malformedMessageCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer malformedMessageCC.close()
	// Save old payload
	originalPayload := registerMsg.Payload
	registerMsg.Payload = append(registerMsg.Payload, 0)
	malformedMessageCC.sendMsg(registerMsg)
	malformedMessageCC.sendMsg(putStateMsg)
	assert.Nil(t, malformedMessageCC.recv())
	assertLogContains(t, recorder, "Failed unmarshaling message")
	// Recover old payload
	registerMsg.Payload = originalPayload

	// Create a real chaincode, that its cert was generated by us
	// but have it reconnect only after too much time.
	// This tests a use case where the CC's cert has been expired
	// and the CC has been compromised. We don't want it to be able
	// to reconnect to us.
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	lateCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer lateCC.close()
	time.Sleep(ttl + time.Second*2)
	lateCC.sendMsg(registerMsg)
	lateCC.sendMsg(putStateMsg)
	echoMsg = lateCC.recv()
	assert.Nil(t, echoMsg)
	assertLogContains(t, recorder, "with given certificate hash", "not found in registry")
}

func assertLogContains(t *testing.T, r *floggingtest.Recorder, ss ...string) {
	defer r.Reset()
	for _, s := range ss {
		assert.NotEmpty(t, r.MessagesContaining(s))
	}
}
<commit_msg>[FAB-15038] add no error assertions to tests<commit_after>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package accesscontrol

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/common/crypto/tlsgen"
	"github.com/hyperledger/fabric/common/flogging/floggingtest"
	pb "github.com/hyperledger/fabric/protos/peer"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap/zapcore"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

type ccSrv struct {
	l              net.Listener
	grpcSrv        *grpc.Server
	t              *testing.T
	expectedCCname string
}

func (cs *ccSrv) Register(stream pb.ChaincodeSupport_RegisterServer) error {
	msg, err := stream.Recv()
	if err != nil {
		return err
	}

	// First message is a register message
	assert.Equal(cs.t, pb.ChaincodeMessage_REGISTER.String(), msg.Type.String())
	// And its chaincode name is the expected one
	chaincodeID := &pb.ChaincodeID{}
	err = proto.Unmarshal(msg.Payload, chaincodeID)
	if err != nil {
		return err
	}
	assert.Equal(cs.t, cs.expectedCCname, chaincodeID.Name)
	// Subsequent messages are just echoed back
	for {
		msg, _ = stream.Recv()
		if err != nil {
			return err
		}
		err = stream.Send(msg)
		if err != nil {
			return err
		}
	}
}

func (cs *ccSrv) stop() {
	cs.grpcSrv.Stop()
	cs.l.Close()
}

func createTLSService(t *testing.T, ca tlsgen.CA, host string) *grpc.Server {
	keyPair, err := ca.NewServerCertKeyPair(host)
	assert.NoError(t, err)
	cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
	assert.NoError(t, err)
	tlsConf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    x509.NewCertPool(),
	}
	tlsConf.ClientCAs.AppendCertsFromPEM(ca.CertBytes())
	return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConf)))
}

func newCCServer(t *testing.T, port int, expectedCCname string, withTLS bool, ca tlsgen.CA) *ccSrv {
	var s *grpc.Server
	if withTLS {
		s = createTLSService(t, ca, "localhost")
	} else {
		s = grpc.NewServer()
	}

	l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "", port))
	assert.NoError(t, err, "%v", err)
	return &ccSrv{
		t:              t,
		expectedCCname: expectedCCname,
		l:              l,
		grpcSrv:        s,
	}
}

type ccClient struct {
	conn   *grpc.ClientConn
	stream pb.ChaincodeSupport_RegisterClient
}

func newClient(t *testing.T, port int, cert *tls.Certificate, peerCACert []byte) (*ccClient, error) {
	tlsCfg := &tls.Config{
		RootCAs: x509.NewCertPool(),
	}

	tlsCfg.RootCAs.AppendCertsFromPEM(peerCACert)
	if cert != nil {
		tlsCfg.Certificates = []tls.Certificate{*cert}
	}
	tlsOpts := grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	conn, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), tlsOpts, grpc.WithBlock())
	if err != nil {
		return nil, err
	}
	chaincodeSupportClient := pb.NewChaincodeSupportClient(conn)
	stream, err := chaincodeSupportClient.Register(context.Background())
	assert.NoError(t, err)
	return &ccClient{
		conn:   conn,
		stream: stream,
	}, nil
}

func (c *ccClient) close() {
	c.conn.Close()
}

func (c *ccClient) sendMsg(msg *pb.ChaincodeMessage) {
	c.stream.Send(msg)
}

func (c *ccClient) recv() *pb.ChaincodeMessage {
	msgs := make(chan *pb.ChaincodeMessage, 1)
	go func() {
		msg, _ := c.stream.Recv()
		if msg != nil {
			msgs <- msg
		}
	}()
	select {
	case <-time.After(time.Second):
		return nil
	case msg := <-msgs:
		return msg
	}
}

func TestAccessControl(t *testing.T) {
	backupTTL := ttl
	defer func() {
		ttl = backupTTL
	}()
	ttl = time.Second * 3

	oldLogger := logger
	l, recorder := floggingtest.NewTestLogger(t, floggingtest.AtLevel(zapcore.InfoLevel))
	logger = l
	defer func() { logger = oldLogger }()

	chaincodeID := &pb.ChaincodeID{Name: "example02"}
	payload, err := proto.Marshal(chaincodeID)
	assert.NoError(t, err)
	registerMsg := &pb.ChaincodeMessage{
		Type:    pb.ChaincodeMessage_REGISTER,
		Payload: payload,
	}
	putStateMsg := &pb.ChaincodeMessage{
		Type: pb.ChaincodeMessage_PUT_STATE,
	}

	ca, _ := tlsgen.NewCA()
	srv := newCCServer(t, 7052, "example02", true, ca)
	auth := NewAuthenticator(ca)
	pb.RegisterChaincodeSupportServer(srv.grpcSrv, auth.Wrap(srv))
	go srv.grpcSrv.Serve(srv.l)
	defer srv.stop()

	// Create an attacker without a TLS certificate
	_, err = newClient(t, 7052, nil, ca.CertBytes())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "context deadline exceeded")

	// Create an attacker with its own TLS certificate
	maliciousCA, _ := tlsgen.NewCA()
	keyPair, err := maliciousCA.NewClientCertKeyPair()
	assert.NoError(t, err)
	cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
	assert.NoError(t, err)
	_, err = newClient(t, 7052, &cert, ca.CertBytes())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "context deadline exceeded")

	// Create a chaincode for example01 that tries to impersonate example02
	kp, err := auth.Generate("example01")
	assert.NoError(t, err)
	keyBytes, err := base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err := base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	mismatchedShim, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer mismatchedShim.close()
	mismatchedShim.sendMsg(registerMsg)
	mismatchedShim.sendMsg(putStateMsg)
	// Mismatched chaincode didn't get back anything
	assert.Nil(t, mismatchedShim.recv())
	assertLogContains(t, recorder, "with given certificate hash", "belongs to a different chaincode")

	// Create the real chaincode that its cert is generated by us that should pass the security checks
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	realCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer realCC.close()
	realCC.sendMsg(registerMsg)
	realCC.sendMsg(putStateMsg)
	echoMsg := realCC.recv()
	// The real chaincode should be echoed back its message
	assert.NotNil(t, echoMsg)
	assert.Equal(t, pb.ChaincodeMessage_PUT_STATE, echoMsg.Type)
	// Log should not complain about anything
	assert.Empty(t, recorder.Messages())

	// Create the real chaincode that its cert is generated by us
	// but one that the first message sent by it isn't a register message.
	// The second message that is sent is a register message but it's "too late"
	// and the stream is already denied.
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	confusedCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer confusedCC.close()
	confusedCC.sendMsg(putStateMsg)
	confusedCC.sendMsg(registerMsg)
	confusedCC.sendMsg(putStateMsg)
	assert.Nil(t, confusedCC.recv())
	assertLogContains(t, recorder, "expected a ChaincodeMessage_REGISTER message")

	// Create a real chaincode, that its cert was generated by us
	// but it sends a malformed first message
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	malformedMessageCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer malformedMessageCC.close()
	// Save old payload
	originalPayload := registerMsg.Payload
	registerMsg.Payload = append(registerMsg.Payload, 0)
	malformedMessageCC.sendMsg(registerMsg)
	malformedMessageCC.sendMsg(putStateMsg)
	assert.Nil(t, malformedMessageCC.recv())
	assertLogContains(t, recorder, "Failed unmarshaling message")
	// Recover old payload
	registerMsg.Payload = originalPayload

	// Create a real chaincode, that its cert was generated by us
	// but have it reconnect only after too much time.
	// This tests a use case where the CC's cert has been expired
	// and the CC has been compromised. We don't want it to be able
	// to reconnect to us.
	kp, err = auth.Generate("example02")
	assert.NoError(t, err)
	keyBytes, err = base64.StdEncoding.DecodeString(kp.Key)
	assert.NoError(t, err)
	certBytes, err = base64.StdEncoding.DecodeString(kp.Cert)
	assert.NoError(t, err)
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	assert.NoError(t, err)
	lateCC, err := newClient(t, 7052, &cert, ca.CertBytes())
	assert.NoError(t, err)
	defer lateCC.close()
	time.Sleep(ttl + time.Second*2)
	lateCC.sendMsg(registerMsg)
	lateCC.sendMsg(putStateMsg)
	echoMsg = lateCC.recv()
	assert.Nil(t, echoMsg)
	assertLogContains(t, recorder, "with given certificate hash", "not found in registry")
}

func assertLogContains(t *testing.T, r *floggingtest.Recorder, ss ...string) {
	defer r.Reset()
	for _, s := range ss {
		assert.NotEmpty(t, r.MessagesContaining(s))
	}
}
<|endoftext|>


<commit_before>package acme

import (
	"time"

	"github.com/square/go-jose"
)

type directory struct {
	NewAuthzURL   string `json:"new-authz"`
	NewCertURL    string `json:"new-cert"`
	NewRegURL     string `json:"new-reg"`
	RevokeCertURL string `json:"revoke-cert"`
}

type recoveryKeyMessage struct {
	Length int             `json:"length,omitempty"`
	Client jose.JsonWebKey `json:"client,omitempty"`
	Server jose.JsonWebKey `json:"client,omitempty"`
}

type registrationMessage struct {
	Resource string   `json:"resource"`
	Contact  []string `json:"contact"`
	//	RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
}

// Registration is returned by the ACME server after the registration
// The client implementation should save this registration somewhere.
type Registration struct {
	Resource string `json:"resource,omitempty"`
	ID       int    `json:"id"`
	Key      struct {
		Kty string `json:"kty"`
		N   string `json:"n"`
		E   string `json:"e"`
	} `json:"key"`
	Contact        []string `json:"contact"`
	Agreement      string   `json:"agreement,omitempty"`
	Authorizations string   `json:"authorizations,omitempty"`
	Certificates   string   `json:"certificates,omitempty"`
	//	RecoveryKey    recoveryKeyMessage `json:"recoveryKey,omitempty"`
}

// RegistrationResource represents all important informations about a registration
// of which the client needs to keep track itself.
type RegistrationResource struct {
	Body        Registration
	URI         string
	NewAuthzURL string
	TosURL      string
}

type authorizationResource struct {
	Body       authorization
	Domain     string
	NewCertURL string
	AuthURL    string
}

type authorization struct {
	Resource     string      `json:"resource,omitempty"`
	Identifier   identifier  `json:"identifier"`
	Status       string      `json:"status,omitempty"`
	Expires      time.Time   `json:"expires,omitempty"`
	Challenges   []challenge `json:"challenges,omitempty"`
	Combinations [][]int     `json:"combinations,omitempty"`
}

type identifier struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type challenge struct {
	Resource         string `json:"resource,omitempty"`
	Type             string `json:"type,omitempty"`
	Status           string `json:"status,omitempty"`
	URI              string `json:"uri,omitempty"`
	Token            string `json:"token,omitempty"`
	KeyAuthorization string `json:"keyAuthorization,omitempty"`
	TLS              bool   `json:"tls,omitempty"`
}

type csrMessage struct {
	Resource       string   `json:"resource,omitempty"`
	Csr            string   `json:"csr"`
	Authorizations []string `json:"authorizations"`
}

type revokeCertMessage struct {
	Resource    string `json:"resource"`
	Certificate string `json:"certificate"`
}

// CertificateResource represents a CA issued certificate.
// PrivateKey and Certificate are both already PEM encoded
// and can be directly written to disk. Certificate may
// be a certificate bundle, depending on the options supplied
// to create it.
type CertificateResource struct {
	Domain        string `json:"domain"`
	CertURL       string `json:"certUrl"`
	CertStableURL string `json:"certStableUrl"`
	PrivateKey    []byte `json:"-"`
	Certificate   []byte `json:"-"`
}
<commit_msg>Add JSON tags to RegistrationResource as used in official client<commit_after>package acme

import (
	"time"

	"github.com/square/go-jose"
)

type directory struct {
	NewAuthzURL   string `json:"new-authz"`
	NewCertURL    string `json:"new-cert"`
	NewRegURL     string `json:"new-reg"`
	RevokeCertURL string `json:"revoke-cert"`
}

type recoveryKeyMessage struct {
	Length int             `json:"length,omitempty"`
	Client jose.JsonWebKey `json:"client,omitempty"`
	Server jose.JsonWebKey `json:"client,omitempty"`
}

type registrationMessage struct {
	Resource string   `json:"resource"`
	Contact  []string `json:"contact"`
	//	RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
}

// Registration is returned by the ACME server after the registration
// The client implementation should save this registration somewhere.
type Registration struct {
	Resource string `json:"resource,omitempty"`
	ID       int    `json:"id"`
	Key      struct {
		Kty string `json:"kty"`
		N   string `json:"n"`
		E   string `json:"e"`
	} `json:"key"`
	Contact        []string `json:"contact"`
	Agreement      string   `json:"agreement,omitempty"`
	Authorizations string   `json:"authorizations,omitempty"`
	Certificates   string   `json:"certificates,omitempty"`
	//	RecoveryKey    recoveryKeyMessage `json:"recoveryKey,omitempty"`
}

// RegistrationResource represents all important informations about a registration
// of which the client needs to keep track itself.
type RegistrationResource struct {
	Body        Registration `json:"body,omitempty"`
	URI         string       `json:"uri,omitempty"`
	NewAuthzURL string       `json:"new_authzr_uri,omitempty"`
	TosURL      string       `json:"terms_of_service,omitempty"`
}

type authorizationResource struct {
	Body       authorization
	Domain     string
	NewCertURL string
	AuthURL    string
}

type authorization struct {
	Resource     string      `json:"resource,omitempty"`
	Identifier   identifier  `json:"identifier"`
	Status       string      `json:"status,omitempty"`
	Expires      time.Time   `json:"expires,omitempty"`
	Challenges   []challenge `json:"challenges,omitempty"`
	Combinations [][]int     `json:"combinations,omitempty"`
}

type identifier struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type challenge struct {
	Resource         string `json:"resource,omitempty"`
	Type             string `json:"type,omitempty"`
	Status           string `json:"status,omitempty"`
	URI              string `json:"uri,omitempty"`
	Token            string `json:"token,omitempty"`
	KeyAuthorization string `json:"keyAuthorization,omitempty"`
	TLS              bool   `json:"tls,omitempty"`
}

type csrMessage struct {
	Resource       string   `json:"resource,omitempty"`
	Csr            string   `json:"csr"`
	Authorizations []string `json:"authorizations"`
}

type revokeCertMessage struct {
	Resource    string `json:"resource"`
	Certificate string `json:"certificate"`
}

// CertificateResource represents a CA issued certificate.
// PrivateKey and Certificate are both already PEM encoded
// and can be directly written to disk. Certificate may
// be a certificate bundle, depending on the options supplied
// to create it.
type CertificateResource struct {
	Domain        string `json:"domain"`
	CertURL       string `json:"certUrl"`
	CertStableURL string `json:"certStableUrl"`
	PrivateKey    []byte `json:"-"`
	Certificate   []byte `json:"-"`
}
<|endoftext|>


<commit_before>package filters

import (
	"encoding/json"
	"fmt"
	"github.com/abesto/easyssh/target"
	"github.com/abesto/easyssh/util"
	"regexp"
	"strings"
)

var ec2InstanceIdRegex = regexp.MustCompile("i-[0-9a-f]{8}")

type ec2InstanceIdParser interface {
	Parse(input string) string
}
type realEc2InstanceIdParser struct{}

func (p realEc2InstanceIdParser) Parse(input string) string {
	return ec2InstanceIdRegex.FindString(input)
}

type ec2InstanceIdLookup struct {
	region        string
	commandRunner util.CommandRunner
	idParser      ec2InstanceIdParser
}

func (f *ec2InstanceIdLookup) Filter(targets []target.Target) []target.Target {
	if f.region == "" {
		panic(fmt.Sprintf("%s requires exactly one argument, the region name to use for looking up instances", nameEc2InstanceId))
	}
	// TODO: build map by parsed id, do a single query using --instance-ids
	for idx, t := range targets {
		instanceId := f.idParser.Parse(t.Host)
		if len(instanceId) > 0 {
			util.Logger.Infof("EC2 Instance lookup: %s in %s", instanceId, f.region)

			output, err := f.commandRunner.RunGetOutput("aws", []string{"ec2", "describe-instances", "--instance-id", instanceId, "--region", f.region})
			util.Logger.Debugf("Response from AWS API: %s", output)
			if err != nil {
				util.Logger.Infof("EC2 Instance lookup failed for %s (%s) in region %s (aws command failed): %s", t.Host, instanceId, f.region, strings.TrimSpace(string(output)))
				continue
			}

			var data map[string]interface{}
			if err = json.Unmarshal(output, &data); err != nil {
				panic(fmt.Sprintf("Invalid JSON returned by AWS API.\nError: %s\nJSON follows this line\n%s", err.Error(), output))
			}

			var reservations = data["Reservations"]
			if reservations == nil || len(reservations.([]interface{})) == 0 {
				util.Logger.Infof("EC2 instance lookup failed for %s (%s) in region %s (Reservations is empty in the received JSON)", t.Host, instanceId, f.region)
				continue
			}
			targets[idx].Host = reservations.([]interface{})[0].(map[string]interface{})["Instances"].([]interface{})[0].(map[string]interface{})["PublicIpAddress"].(string)
		} else {
			util.Logger.Debugf("Target %s looks like it doesn't have EC2 instance ID, skipping lookup for region %s", t, f.region)
		}
	}
	return targets
}
func (f *ec2InstanceIdLookup) SetArgs(args []interface{}) {
	if len(args) != 1 {
		panic(fmt.Sprintf("%s requires exactly one argument, the region name to use for looking up instances", nameEc2InstanceId))
	}
	f.region = string(args[0].([]byte))
}
func (f *ec2InstanceIdLookup) String() string {
	return fmt.Sprintf("<%s %s>", nameEc2InstanceId, f.region)
}
<commit_msg>ec2-instance-id filter: proper JSON unmarshalling<commit_after>package filters

import (
	"encoding/json"
	"fmt"
	"github.com/abesto/easyssh/target"
	"github.com/abesto/easyssh/util"
	"regexp"
	"strings"
)

var ec2InstanceIdRegex = regexp.MustCompile("i-[0-9a-f]{8}")

type ec2InstanceIdParser interface {
	Parse(input string) string
}
type realEc2InstanceIdParser struct{}

func (p realEc2InstanceIdParser) Parse(input string) string {
	return ec2InstanceIdRegex.FindString(input)
}

type apiResponse struct {
	Reservations []struct {
		Instances []struct {
			PublicDnsName   string
			PublicIpAddress string
		}
	}
}

type ec2InstanceIdLookup struct {
	region        string
	commandRunner util.CommandRunner
	idParser      ec2InstanceIdParser
}

func (f *ec2InstanceIdLookup) Filter(targets []target.Target) []target.Target {
	if f.region == "" {
		panic(fmt.Sprintf("%s requires exactly one argument, the region name to use for looking up instances", nameEc2InstanceId))
	}
	// TODO: build map by parsed id, do a single query using --instance-ids
	for idx, t := range targets {
		instanceId := f.idParser.Parse(t.Host)
		if len(instanceId) > 0 {
			util.Logger.Infof("EC2 Instance lookup: %s in %s", instanceId, f.region)

			output, err := f.commandRunner.RunGetOutput("aws", []string{"ec2", "describe-instances", "--instance-id", instanceId, "--region", f.region})
			util.Logger.Debugf("Response from AWS API: %s", output)
			if err != nil {
				util.Logger.Infof("EC2 Instance lookup failed for %s (%s) in region %s (aws command failed): %s", t.Host, instanceId, f.region, strings.TrimSpace(string(output)))
				continue
			}

			var data apiResponse
			if err = json.Unmarshal(output, &data); err != nil {
				panic(fmt.Sprintf("Invalid JSON returned by AWS API.\nError: %s\nJSON follows this line\n%s", err.Error(), output))
			}

			if data.Reservations == nil || len(data.Reservations) == 0 {
				util.Logger.Infof("EC2 instance lookup failed for %s (%s) in region %s (Reservations is empty in the received JSON)", t.Host, instanceId, f.region)
				continue
			}
			targets[idx].Host = data.Reservations[0].Instances[0].PublicIpAddress
		} else {
			util.Logger.Debugf("Target %s looks like it doesn't have EC2 instance ID, skipping lookup for region %s", t, f.region)
		}
	}
	return targets
}
func (f *ec2InstanceIdLookup) SetArgs(args []interface{}) {
	if len(args) != 1 {
		panic(fmt.Sprintf("%s requires exactly one argument, the region name to use for looking up instances", nameEc2InstanceId))
	}
	f.region = string(args[0].([]byte))
}
func (f *ec2InstanceIdLookup) String() string {
	return fmt.Sprintf("<%s %s>", nameEc2InstanceId, f.region)
}
<|endoftext|>


<commit_before>package oldstatus

import (
	"encoding/json"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path"
	"strings"
	"testing"

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

func TestGetStatus(t *testing.T) {
	// prepare
	var status StatusData

	mux := getStatusReadMux(&status)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path, "json")

	// test1
	status.Lock()
	var p1 float32 = 1.2
	status.Progress = &p1
	w1 := "foobar_img"
	status.What = &w1
	status.Status = "whatever"
	status.Unlock()

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	var responseStatus1 StatusData
	decoder1 := json.NewDecoder(resp.Body)
	assert.Nil(t, decoder1.Decode(&responseStatus1))

	status.RLock()
	assert.Equal(t, p1, *responseStatus1.Progress)
	assert.Equal(t, w1, *responseStatus1.What)
	assert.Equal(t, "whatever", responseStatus1.Status)
	status.RUnlock()

	// test2
	status.Lock()
	status.Progress = nil
	status.What = nil
	status.Status = "other_status"
	status.Unlock()

	resp, err = http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	var responseStatus2 StatusData
	decoder2 := json.NewDecoder(resp.Body)
	assert.Nil(t, decoder2.Decode(&responseStatus2))

	status.RLock()
	assert.Nil(t, responseStatus2.Progress)
	assert.Nil(t, responseStatus2.What)
	assert.Equal(t, "other_status", responseStatus2.Status)
	status.RUnlock()
}

func TestGetFavicon(t *testing.T) {
	// prepare
	mux := getStatusReadMux(nil)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path, "favicon.ico")

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestGetHTML(t *testing.T) {
	// prepare
	mux := getStatusReadMux(nil)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path)

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	responseBody, err := ioutil.ReadAll(resp.Body)
	assert.Nil(t, err)
	assert.Equal(t, htmlBody, string(responseBody))
}

func TestPutOnUnixSocket(t *testing.T) {
	// prepare path for the socket
	f, err := ioutil.TempFile("", "platconf-unittest-")
	assert.Nil(t, err)
	socketPath := f.Name()
	f.Close()
	os.Remove(socketPath)

	// start
	var status StatusData
	err = listenOnUnixSocket(&status, socketPath)
	defer os.Remove(socketPath)
	assert.Nil(t, err)

	// make a UNIX socket-connected HTTP client
	fakeDial := func(proto, addr string) (conn net.Conn, err error) {
		return net.Dial("unix", socketPath)
	}

	client := http.Client{
		Transport: &http.Transport{
			Dial: fakeDial,
		},
	}

	// test1
	req1, err := http.NewRequest("PUT", "http://foobar/status", strings.NewReader(`{"status": "foobar1"}`))
	assert.Nil(t, err)
	resp1, err := client.Do(req1)
	assert.Nil(t, err)
	assert.Equal(t, http.StatusAccepted, resp1.StatusCode)

	status.RLock()
	assert.Equal(t, "foobar1", status.Status)
	assert.Nil(t, status.Progress)
	assert.Nil(t, status.What)
	status.RUnlock()

	// test2
	req2, err := http.NewRequest("PUT", "http://foobar/status", strings.NewReader(`{"status": "whatever", "what": "something", "progress": 12312.12412}`))
	assert.Nil(t, err)
	resp2, err := client.Do(req2)
	assert.Nil(t, err)
	assert.Equal(t, http.StatusAccepted, resp2.StatusCode)

	status.RLock()
	assert.Equal(t, "whatever", status.Status)
	assert.Equal(t, float32(12312.12412), *status.Progress)
	assert.Equal(t, "something", *status.What)
	status.RUnlock()
}

func TestUpdateByFile2(t *testing.T) {
	preservedStatusFilePath := statusFilePath
	defer func() { statusFilePath = preservedStatusFilePath }()

	f, err := ioutil.TempFile("", "platconf-unittest-")
	assert.Nil(t, err)
	statusFilePath = f.Name()
	f.Close()
	defer os.Remove(statusFilePath)
}
<commit_msg>add test for oldstatus file watcher<commit_after>package oldstatus

import (
	"encoding/json"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path"
	"strings"
	"testing"
	"time"

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

func TestGetStatus(t *testing.T) {
	// prepare
	var status StatusData

	mux := getStatusReadMux(&status)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path, "json")

	// test1
	status.Lock()
	var p1 float32 = 1.2
	status.Progress = &p1
	w1 := "foobar_img"
	status.What = &w1
	status.Status = "whatever"
	status.Unlock()

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	var responseStatus1 StatusData
	decoder1 := json.NewDecoder(resp.Body)
	assert.Nil(t, decoder1.Decode(&responseStatus1))

	status.RLock()
	assert.Equal(t, p1, *responseStatus1.Progress)
	assert.Equal(t, w1, *responseStatus1.What)
	assert.Equal(t, "whatever", responseStatus1.Status)
	status.RUnlock()

	// test2
	status.Lock()
	status.Progress = nil
	status.What = nil
	status.Status = "other_status"
	status.Unlock()

	resp, err = http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	var responseStatus2 StatusData
	decoder2 := json.NewDecoder(resp.Body)
	assert.Nil(t, decoder2.Decode(&responseStatus2))

	status.RLock()
	assert.Nil(t, responseStatus2.Progress)
	assert.Nil(t, responseStatus2.What)
	assert.Equal(t, "other_status", responseStatus2.Status)
	status.RUnlock()
}

func TestGetFavicon(t *testing.T) {
	// prepare
	mux := getStatusReadMux(nil)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path, "favicon.ico")

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestGetHTML(t *testing.T) {
	// prepare
	mux := getStatusReadMux(nil)
	srv := httptest.NewServer(mux)

	u, err := url.Parse(srv.URL)
	assert.Nil(t, err)
	u.Path = path.Join(u.Path)

	resp, err := http.Get(u.String())
	assert.Nil(t, err)
	responseBody, err := ioutil.ReadAll(resp.Body)
	assert.Nil(t, err)
	assert.Equal(t, htmlBody, string(responseBody))
}

func TestPutOnUnixSocket(t *testing.T) {
	// prepare path for the socket
	f, err := ioutil.TempFile("", "platconf-unittest-")
	assert.Nil(t, err)
	socketPath := f.Name()
	f.Close()
	os.Remove(socketPath)

	// start
	var status StatusData
	err = listenOnUnixSocket(&status, socketPath)
	defer os.Remove(socketPath)
	assert.Nil(t, err)

	// make a UNIX socket-connected HTTP client
	fakeDial := func(proto, addr string) (conn net.Conn, err error) {
		return net.Dial("unix", socketPath)
	}

	client := http.Client{
		Transport: &http.Transport{
			Dial: fakeDial,
		},
	}

	// test1
	req1, err := http.NewRequest("PUT", "http://foobar/status", strings.NewReader(`{"status": "foobar1"}`))
	assert.Nil(t, err)
	resp1, err := client.Do(req1)
	assert.Nil(t, err)
	assert.Equal(t, http.StatusAccepted, resp1.StatusCode)

	status.RLock()
	assert.Equal(t, "foobar1", status.Status)
	assert.Nil(t, status.Progress)
	assert.Nil(t, status.What)
	status.RUnlock()

	// test2
	req2, err := http.NewRequest("PUT", "http://foobar/status", strings.NewReader(`{"status": "whatever", "what": "something", "progress": 12312.12412}`))
	assert.Nil(t, err)
	resp2, err := client.Do(req2)
	assert.Nil(t, err)
	assert.Equal(t, http.StatusAccepted, resp2.StatusCode)

	status.RLock()
	assert.Equal(t, "whatever", status.Status)
	assert.Equal(t, float32(12312.12412), *status.Progress)
	assert.Equal(t, "something", *status.What)
	status.RUnlock()
}

func TestFileWatcher(t *testing.T) {
	// prepare path for the socket
	f, err := ioutil.TempFile("", "platconf-unittest-")
	assert.Nil(t, err)
	path := f.Name()
	f.Close()
	defer os.Remove(path)

	// start
	var status StatusData
	go func() {
		errWatcher := watchStatusFileForChange(&status, path)
		assert.Nil(t, errWatcher)
	}()
	time.Sleep(time.Second)

	// test1
	err = ioutil.WriteFile(path, []byte(`{"status": "foobar1"}`), 0644)
	assert.Nil(t, err)
	time.Sleep(time.Second)

	status.RLock()
	assert.Equal(t, "foobar1", status.Status)
	assert.Nil(t, status.Progress)
	assert.Nil(t, status.What)
	status.RUnlock()

	// test2
	err = ioutil.WriteFile(path, []byte(`{"status": "whatever", "what": "something", "progress": 12312.12412}`), 0644)
	assert.Nil(t, err)
	time.Sleep(time.Second)

	status.RLock()
	assert.Equal(t, "whatever", status.Status)
	assert.Equal(t, float32(12312.12412), *status.Progress)
	assert.Equal(t, "something", *status.What)
	status.RUnlock()
}

func TestUpdateByFile2(t *testing.T) {
	preservedStatusFilePath := statusFilePath
	defer func() { statusFilePath = preservedStatusFilePath }()

	f, err := ioutil.TempFile("", "platconf-unittest-")
	assert.Nil(t, err)
	statusFilePath = f.Name()
	f.Close()
	defer os.Remove(statusFilePath)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package client provides helper functions for OpenConfig CLI tools.
package client

import (
	"io"
	"strings"
	"sync"

	"github.com/aristanetworks/glog"
	"github.com/golang/protobuf/proto"
	"github.com/openconfig/reference/rpc/openconfig"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

const defaultPort = "6030"

// PublishFunc is the method to publish responses
type PublishFunc func(addr string, message proto.Message)

// Client is a connected gRPC client
type Client struct {
	client openconfig.OpenConfigClient
	ctx    context.Context
	device string
}

// New creates a new gRPC client and connects it
func New(username, password, addr string, opts []grpc.DialOption) *Client {
	device := addr
	if !strings.ContainsRune(addr, ':') {
		addr += ":" + defaultPort
	}
	// Make sure we don't move past the grpc.Dial() call until we actually
	// established an HTTP/2 connection successfully.
	opts = append(opts, grpc.WithBlock(), grpc.WithWaitForHandshake())
	conn, err := grpc.Dial(addr, opts...)
	if err != nil {
		glog.Fatalf("Failed to dial: %s", err)
	}
	glog.Infof("Connected to %s", addr)
	client := openconfig.NewOpenConfigClient(conn)

	ctx := context.Background()
	if username != "" {
		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
			"username", username,
			"password", password))
	}
	return &Client{
		client: client,
		device: device,
		ctx:    ctx,
	}
}

// Get sends a get request and returns the responses
func (c *Client) Get(path string) []*openconfig.Notification {
	req := &openconfig.GetRequest{
		Path: []*openconfig.Path{
			{
				Element: strings.Split(path, "/"),
			},
		},
	}
	response, err := c.client.Get(c.ctx, req)
	if err != nil {
		glog.Fatalf("Get failed: %s", err)
	}
	return response.Notification
}

// Subscribe sends subscriptions, and consumes responses.
// The given publish function is used to publish SubscribeResponses received
// for the given subscriptions, when connected to the given host, with the
// given user/pass pair, or the client-side cert specified in the gRPC opts.
// This function does not normally return so it should probably be run in its
// own goroutine.  When this function returns, the given WaitGroup is marked
// as done.
func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string,
	publish PublishFunc) {
	defer wg.Done()
	stream, err := c.client.Subscribe(c.ctx)
	if err != nil {
		glog.Fatalf("Subscribe failed: %s", err)
	}
	defer stream.CloseSend()

	for _, path := range subscriptions {
		sub := &openconfig.SubscribeRequest{
			Request: &openconfig.SubscribeRequest_Subscribe{
				Subscribe: &openconfig.SubscriptionList{
					Subscription: []*openconfig.Subscription{
						{
							Path: &openconfig.Path{Element: strings.Split(path, "/")},
						},
					},
				},
			},
		}

		glog.Infof("Sending subscribe request: %s", sub)
		err = stream.Send(sub)
		if err != nil {
			glog.Fatalf("Failed to subscribe: %s", err)
		}
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			if err != io.EOF {
				glog.Fatalf("Error received from the server: %s", err)
			}
			return
		}
		switch resp := resp.Response.(type) {
		case *openconfig.SubscribeResponse_SyncResponse:
			if !resp.SyncResponse {
				panic("initial sync failed," +
					" check that you're using a client compatible with the server")
			}
		}
		glog.V(3).Info(resp)
		publish(c.device, resp)
	}
}
<commit_msg>openconfig/client: Remove grpc.WithWaitForHandshake<commit_after>// Copyright (c) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package client provides helper functions for OpenConfig CLI tools.
package client

import (
	"io"
	"strings"
	"sync"

	"github.com/aristanetworks/glog"
	"github.com/golang/protobuf/proto"
	"github.com/openconfig/reference/rpc/openconfig"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

const defaultPort = "6030"

// PublishFunc is the method to publish responses
type PublishFunc func(addr string, message proto.Message)

// Client is a connected gRPC client
type Client struct {
	client openconfig.OpenConfigClient
	ctx    context.Context
	device string
}

// New creates a new gRPC client and connects it
func New(username, password, addr string, opts []grpc.DialOption) *Client {
	device := addr
	if !strings.ContainsRune(addr, ':') {
		addr += ":" + defaultPort
	}
	// Make sure we don't move past the grpc.Dial() call until we actually
	// established an HTTP/2 connection successfully.
	opts = append(opts, grpc.WithBlock())
	conn, err := grpc.Dial(addr, opts...)
	if err != nil {
		glog.Fatalf("Failed to dial: %s", err)
	}
	glog.Infof("Connected to %s", addr)
	client := openconfig.NewOpenConfigClient(conn)

	ctx := context.Background()
	if username != "" {
		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
			"username", username,
			"password", password))
	}
	return &Client{
		client: client,
		device: device,
		ctx:    ctx,
	}
}

// Get sends a get request and returns the responses
func (c *Client) Get(path string) []*openconfig.Notification {
	req := &openconfig.GetRequest{
		Path: []*openconfig.Path{
			{
				Element: strings.Split(path, "/"),
			},
		},
	}
	response, err := c.client.Get(c.ctx, req)
	if err != nil {
		glog.Fatalf("Get failed: %s", err)
	}
	return response.Notification
}

// Subscribe sends subscriptions, and consumes responses.
// The given publish function is used to publish SubscribeResponses received
// for the given subscriptions, when connected to the given host, with the
// given user/pass pair, or the client-side cert specified in the gRPC opts.
// This function does not normally return so it should probably be run in its
// own goroutine.  When this function returns, the given WaitGroup is marked
// as done.
func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string,
	publish PublishFunc) {
	defer wg.Done()
	stream, err := c.client.Subscribe(c.ctx)
	if err != nil {
		glog.Fatalf("Subscribe failed: %s", err)
	}
	defer stream.CloseSend()

	for _, path := range subscriptions {
		sub := &openconfig.SubscribeRequest{
			Request: &openconfig.SubscribeRequest_Subscribe{
				Subscribe: &openconfig.SubscriptionList{
					Subscription: []*openconfig.Subscription{
						{
							Path: &openconfig.Path{Element: strings.Split(path, "/")},
						},
					},
				},
			},
		}

		glog.Infof("Sending subscribe request: %s", sub)
		err = stream.Send(sub)
		if err != nil {
			glog.Fatalf("Failed to subscribe: %s", err)
		}
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			if err != io.EOF {
				glog.Fatalf("Error received from the server: %s", err)
			}
			return
		}
		switch resp := resp.Response.(type) {
		case *openconfig.SubscribeResponse_SyncResponse:
			if !resp.SyncResponse {
				panic("initial sync failed," +
					" check that you're using a client compatible with the server")
			}
		}
		glog.V(3).Info(resp)
		publish(c.device, resp)
	}
}
<|endoftext|>


<commit_before>//go:build !nautilus && !octopus
// +build !nautilus,!octopus

package admin

import (
	"errors"
	"os"
	pth "path"
	"testing"
	"time"

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

	"github.com/ceph/go-ceph/cephfs"
)

func mirrorConfig() string {
	return os.Getenv("MIRROR_CONF")
}

const (
	noForce      = false
	mirrorClient = "client.mirror_remote"
)

func TestMirroring(t *testing.T) {
	if mirrorConfig() == "" {
		t.Skip("no mirror config available")
	}

	fsa1 := getFSAdmin(t)
	fsname := "cephfs"

	require.NotNil(t, fsa1.conn)
	err := fsa1.EnableMirroringModule(noForce)
	assert.NoError(t, err)
	defer func() {
		err := fsa1.DisableMirroringModule()
		assert.NoError(t, err)
	}()
	require.NoError(t, err)
	time.Sleep(500 * time.Millisecond) // TODO: improve this

	smadmin1 := fsa1.SnapshotMirror()
	err = smadmin1.Enable(fsname)
	require.NoError(t, err)
	defer func() {
		err := smadmin1.Disable(fsname)
		require.NoError(t, err)
	}()
	time.Sleep(500 * time.Millisecond) // TODO: improve this

	fsa2 := newFSAdmin(t, mirrorConfig())
	err = fsa2.EnableMirroringModule(noForce)
	require.NoError(t, err)
	defer func() {
		err := fsa2.DisableMirroringModule()
		assert.NoError(t, err)
	}()

	smadmin2 := fsa2.SnapshotMirror()
	err = smadmin2.Enable(fsname)
	require.NoError(t, err)
	defer func() {
		err := smadmin2.Disable(fsname)
		require.NoError(t, err)
	}()

	// from https://docs.ceph.com/en/pacific/dev/cephfs-mirroring/
	//  "Peer bootstrap involves creating a bootstrap token on the peer cluster"
	// and "Import the bootstrap token in the primary cluster"
	token, err := smadmin2.CreatePeerBootstrapToken(fsname, mirrorClient, "ceph_b")
	require.NoError(t, err)
	err = smadmin1.ImportPeerBoostrapToken(fsname, token)
	require.NoError(t, err)

	// we need a path to mirror
	path := "/wonderland"

	mount1 := fsConnect(t, "")
	defer func(mount *cephfs.MountInfo) {
		assert.NoError(t, mount.Unmount())
		assert.NoError(t, mount.Release())
	}(mount1)

	mount2 := fsConnect(t, mirrorConfig())
	defer func(mount *cephfs.MountInfo) {
		assert.NoError(t, mount.Unmount())
		assert.NoError(t, mount.Release())
	}(mount2)

	err = mount1.MakeDir(path, 0770)
	require.NoError(t, err)
	defer func() {
		err = mount2.ChangeDir("/")
		assert.NoError(t, err)
		err = mount1.RemoveDir(path)
		assert.NoError(t, err)
	}()
	err = mount2.MakeDir(path, 0770)
	require.NoError(t, err)
	defer func() {
		err = mount2.ChangeDir("/")
		assert.NoError(t, err)
		err = mount2.RemoveDir(path)
		assert.NoError(t, err)
	}()

	err = smadmin1.Add(fsname, path)
	require.NoError(t, err)

	err = mount1.ChangeDir(path)
	require.NoError(t, err)

	// write some dirs & files
	err = mount1.MakeDir("drink_me", 0770)
	require.NoError(t, err)
	err = mount1.MakeDir("eat_me", 0770)
	require.NoError(t, err)
	writeFile(t, mount1, "drink_me/bottle1.txt",
		[]byte("magic potions #1\n"))

	snapname1 := "alice"
	err = mount1.MakeDir(pth.Join(snapDir, snapname1), 0700)
	require.NoError(t, err)
	defer func() {
		err := mount1.RemoveDir(pth.Join(snapDir, snapname1))
		assert.NoError(t, err)
		err = mount2.RemoveDir(pth.Join(snapDir, snapname1))
		assert.NoError(t, err)
	}()

	err = mount2.ChangeDir(path)
	require.NoError(t, err)

	// wait a bit for the snapshot to propagate and the dirs to be created on
	// the remote fs.
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		_, err1 := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
		_, err2 := mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
		if err1 == nil && err2 == nil {
			break
		}
	}

waitforpeers:
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		dstatus, err := smadmin1.DaemonStatus(fsname)
		assert.NoError(t, err)
		for _, dsinfo := range dstatus {
			for _, fsinfo := range dsinfo.FileSystems {
				if len(fsinfo.Peers) > 0 {
					break waitforpeers
				}
			}
		}
	}

	p, err := smadmin1.PeerList(fsname)
	assert.NoError(t, err)
	assert.Len(t, p, 1)
	for _, peer := range p {
		assert.Equal(t, "cephfs", peer.FSName)
	}

	stx, err := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0040000), stx.Mode&0040000) // is dir?
	}

	stx, err = mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0040000), stx.Mode&0040000) // is dir?
	}

	stx, err = mount2.Statx("drink_me/bottle1.txt", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0100000), stx.Mode&0100000) // is reg?
		assert.Equal(t, uint64(17), stx.Size)
	}
	data := readFile(t, mount2, "drink_me/bottle1.txt")
	assert.Equal(t, "magic potions #1\n", string(data))

	err = mount1.Unlink("drink_me/bottle1.txt")
	require.NoError(t, err)
	err = mount1.RemoveDir("drink_me")
	require.NoError(t, err)
	err = mount1.RemoveDir("eat_me")
	require.NoError(t, err)

	snapname2 := "rabbit"
	err = mount1.MakeDir(pth.Join(snapDir, snapname2), 0700)
	require.NoError(t, err)
	defer func() {
		err := mount1.RemoveDir(pth.Join(snapDir, snapname2))
		assert.NoError(t, err)
		err = mount2.RemoveDir(pth.Join(snapDir, snapname2))
		assert.NoError(t, err)
	}()

	// wait a bit for the snapshot to propagate and the dirs to be removed on
	// the remote fs.
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		_, err1 := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
		_, err2 := mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
		if err1 != nil && err2 != nil {
			break
		}

	}
	_, err = mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
	if assert.Error(t, err) {
		var ec errorWithCode
		if assert.True(t, errors.As(err, &ec)) {
			assert.Equal(t, -2, ec.ErrorCode())
		}
	}
	_, err = mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
	if assert.Error(t, err) {
		var ec errorWithCode
		if assert.True(t, errors.As(err, &ec)) {
			assert.Equal(t, -2, ec.ErrorCode())
		}
	}

	err = smadmin1.Remove(fsname, path)
	assert.NoError(t, err)
}

type errorWithCode interface {
	ErrorCode() int
}
<commit_msg>cephfs admin: poll mgr to see if mirroring module is enabled<commit_after>//go:build !nautilus && !octopus
// +build !nautilus,!octopus

package admin

import (
	"errors"
	"os"
	pth "path"
	"testing"
	"time"

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

	"github.com/ceph/go-ceph/cephfs"
)

func mirrorConfig() string {
	return os.Getenv("MIRROR_CONF")
}

const (
	noForce      = false
	mirrorClient = "client.mirror_remote"
)

func waitForMirroring(t *testing.T, fsa *FSAdmin) {
	for i := 0; i < 20; i++ {
		modinfo, err := fsa.listModules()
		require.NoError(t, err)
		for _, emod := range modinfo.EnabledModules {
			if emod == "mirroring" {
				return
			}
		}
		time.Sleep(100 * time.Millisecond)
	}
	t.Fatalf("timed out waiting for mirroring module")
}

func TestMirroring(t *testing.T) {
	if mirrorConfig() == "" {
		t.Skip("no mirror config available")
	}

	fsa1 := getFSAdmin(t)
	fsname := "cephfs"

	require.NotNil(t, fsa1.conn)
	err := fsa1.EnableMirroringModule(noForce)
	assert.NoError(t, err)
	defer func() {
		err := fsa1.DisableMirroringModule()
		assert.NoError(t, err)
	}()
	require.NoError(t, err)
	waitForMirroring(t, fsa1)

	smadmin1 := fsa1.SnapshotMirror()
	err = smadmin1.Enable(fsname)
	require.NoError(t, err)
	defer func() {
		err := smadmin1.Disable(fsname)
		require.NoError(t, err)
	}()

	fsa2 := newFSAdmin(t, mirrorConfig())
	err = fsa2.EnableMirroringModule(noForce)
	require.NoError(t, err)
	defer func() {
		err := fsa2.DisableMirroringModule()
		assert.NoError(t, err)
	}()
	waitForMirroring(t, fsa2)

	smadmin2 := fsa2.SnapshotMirror()
	err = smadmin2.Enable(fsname)
	require.NoError(t, err)
	defer func() {
		err := smadmin2.Disable(fsname)
		require.NoError(t, err)
	}()

	// from https://docs.ceph.com/en/pacific/dev/cephfs-mirroring/
	//  "Peer bootstrap involves creating a bootstrap token on the peer cluster"
	// and "Import the bootstrap token in the primary cluster"
	token, err := smadmin2.CreatePeerBootstrapToken(fsname, mirrorClient, "ceph_b")
	require.NoError(t, err)
	err = smadmin1.ImportPeerBoostrapToken(fsname, token)
	require.NoError(t, err)

	// we need a path to mirror
	path := "/wonderland"

	mount1 := fsConnect(t, "")
	defer func(mount *cephfs.MountInfo) {
		assert.NoError(t, mount.Unmount())
		assert.NoError(t, mount.Release())
	}(mount1)

	mount2 := fsConnect(t, mirrorConfig())
	defer func(mount *cephfs.MountInfo) {
		assert.NoError(t, mount.Unmount())
		assert.NoError(t, mount.Release())
	}(mount2)

	err = mount1.MakeDir(path, 0770)
	require.NoError(t, err)
	defer func() {
		err = mount2.ChangeDir("/")
		assert.NoError(t, err)
		err = mount1.RemoveDir(path)
		assert.NoError(t, err)
	}()
	err = mount2.MakeDir(path, 0770)
	require.NoError(t, err)
	defer func() {
		err = mount2.ChangeDir("/")
		assert.NoError(t, err)
		err = mount2.RemoveDir(path)
		assert.NoError(t, err)
	}()

	err = smadmin1.Add(fsname, path)
	require.NoError(t, err)

	err = mount1.ChangeDir(path)
	require.NoError(t, err)

	// write some dirs & files
	err = mount1.MakeDir("drink_me", 0770)
	require.NoError(t, err)
	err = mount1.MakeDir("eat_me", 0770)
	require.NoError(t, err)
	writeFile(t, mount1, "drink_me/bottle1.txt",
		[]byte("magic potions #1\n"))

	snapname1 := "alice"
	err = mount1.MakeDir(pth.Join(snapDir, snapname1), 0700)
	require.NoError(t, err)
	defer func() {
		err := mount1.RemoveDir(pth.Join(snapDir, snapname1))
		assert.NoError(t, err)
		err = mount2.RemoveDir(pth.Join(snapDir, snapname1))
		assert.NoError(t, err)
	}()

	err = mount2.ChangeDir(path)
	require.NoError(t, err)

	// wait a bit for the snapshot to propagate and the dirs to be created on
	// the remote fs.
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		_, err1 := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
		_, err2 := mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
		if err1 == nil && err2 == nil {
			break
		}
	}

waitforpeers:
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		dstatus, err := smadmin1.DaemonStatus(fsname)
		assert.NoError(t, err)
		for _, dsinfo := range dstatus {
			for _, fsinfo := range dsinfo.FileSystems {
				if len(fsinfo.Peers) > 0 {
					break waitforpeers
				}
			}
		}
	}

	p, err := smadmin1.PeerList(fsname)
	assert.NoError(t, err)
	assert.Len(t, p, 1)
	for _, peer := range p {
		assert.Equal(t, "cephfs", peer.FSName)
	}

	stx, err := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0040000), stx.Mode&0040000) // is dir?
	}

	stx, err = mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0040000), stx.Mode&0040000) // is dir?
	}

	stx, err = mount2.Statx("drink_me/bottle1.txt", cephfs.StatxBasicStats, 0)
	if assert.NoError(t, err) {
		assert.Equal(t, uint16(0100000), stx.Mode&0100000) // is reg?
		assert.Equal(t, uint64(17), stx.Size)
	}
	data := readFile(t, mount2, "drink_me/bottle1.txt")
	assert.Equal(t, "magic potions #1\n", string(data))

	err = mount1.Unlink("drink_me/bottle1.txt")
	require.NoError(t, err)
	err = mount1.RemoveDir("drink_me")
	require.NoError(t, err)
	err = mount1.RemoveDir("eat_me")
	require.NoError(t, err)

	snapname2 := "rabbit"
	err = mount1.MakeDir(pth.Join(snapDir, snapname2), 0700)
	require.NoError(t, err)
	defer func() {
		err := mount1.RemoveDir(pth.Join(snapDir, snapname2))
		assert.NoError(t, err)
		err = mount2.RemoveDir(pth.Join(snapDir, snapname2))
		assert.NoError(t, err)
	}()

	// wait a bit for the snapshot to propagate and the dirs to be removed on
	// the remote fs.
	for i := 0; i < 60; i++ {
		time.Sleep(500 * time.Millisecond)
		_, err1 := mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
		_, err2 := mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
		if err1 != nil && err2 != nil {
			break
		}

	}
	_, err = mount2.Statx("drink_me", cephfs.StatxBasicStats, 0)
	if assert.Error(t, err) {
		var ec errorWithCode
		if assert.True(t, errors.As(err, &ec)) {
			assert.Equal(t, -2, ec.ErrorCode())
		}
	}
	_, err = mount2.Statx("eat_me", cephfs.StatxBasicStats, 0)
	if assert.Error(t, err) {
		var ec errorWithCode
		if assert.True(t, errors.As(err, &ec)) {
			assert.Equal(t, -2, ec.ErrorCode())
		}
	}

	err = smadmin1.Remove(fsname, path)
	assert.NoError(t, err)
}

type errorWithCode interface {
	ErrorCode() int
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"flag"
	"io"
	"log"
	"os"
	"strings"
	"text/template"

	"github.com/russross/blackfriday"
)

const (
	finalTmpl = `

	<meta charset="utf-8" />
	<meta name="viewport" content="width=1024" />
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<title>Show</title>

	<link href="{{with .CSS}}{{.}}{{else}}impress.css{{end}}" rel="stylesheet" />

Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

{{ .Content }}

Use a spacebar or arrow keys to navigate

`
)

type pages struct {
	buf     *bytes.Buffer
	in, out chan string
}

var (
	filename = flag.String("f", "", "markdown file to process")
	css      = flag.String("css", "", "CSS filename")
)

func main() {
	flag.Parse()
	var r io.Reader
	if *filename == "" {
		r = os.Stdin
	} else {
		var err error
		r, err = os.Open(*filename)
		if err != nil {
			log.Fatal(err)
		}
	}
	scanner := bufio.NewScanner(r)
	p := &pages{
		buf: new(bytes.Buffer),
		out: make(chan string),
		in:  make(chan string),
	}
	go p.process()

	done := make(chan int)
	go output(p.out, done)

	for scanner.Scan() {
		line := scanner.Text()
		switch strings.TrimSpace(line) {
		case "---":
			p.emit()
		default:
			p.cache(line)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	if p.buf.String() != "" {
		p.emit()
	}
	p.end()

	<-done
}

func output(ch chan string, done chan int) {
	pageTmpl := `

{{.PageHTML}}

`
	tmpl := template.Must(template.New("").Parse(pageTmpl))
	buf := new(bytes.Buffer)
	n := 0
	for pageHTML := range ch {
		err := tmpl.Execute(buf, struct {
			Xpixels  int
			PageHTML string
		}{
			Xpixels:  n * 1000,
			PageHTML: pageHTML,
		})
		if err != nil {
			log.Fatal(err)
		}
		n++
	}
	tmpl = template.Must(template.New("").Parse(finalTmpl))
	err := tmpl.Execute(os.Stdout, struct {
		CSS, Content string
	}{
		CSS:     *css,
		Content: buf.String(),
	})
	if err != nil {
		log.Fatal(err)
	}
	done <- 1
}

func (p *pages) cache(line string) {
	_, err := p.buf.WriteString(line + "\n")
	if err != nil {
		log.Fatal(err)
	}
}

func (p *pages) emit() {
	p.in <- p.buf.String()
	p.buf.Reset()
}

func (p *pages) end() {
	close(p.in)
}

func (p *pages) process() {
	for page := range p.in {
		p.out <- transfer(page)
	}
	close(p.out)
}

func transfer(md string) string {
	return string(blackfriday.MarkdownCommon([]byte(md)))
}
<commit_msg>can specify impress.js filename<commit_after>package main

import (
	"bufio"
	"bytes"
	"flag"
	"io"
	"log"
	"os"
	"strings"
	"text/template"

	"github.com/russross/blackfriday"
)

const (
	finalTmpl = `

	<meta charset="utf-8" />
	<meta name="viewport" content="width=1024" />
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<title>Show</title>

	<link href="{{with .CSS}}{{.}}{{else}}impress.css{{end}}" rel="stylesheet" />

Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

{{ .Content }}

Use a spacebar or arrow keys to navigate

`
)

type pages struct {
	buf     *bytes.Buffer
	in, out chan string
}

var (
	filename = flag.String("f", "", "markdown file to process")
	css      = flag.String("css", "", "CSS filename")
	js       = flag.String("js", "", "impress.js filename")
)

func main() {
	flag.Parse()
	var r io.Reader
	if *filename == "" {
		r = os.Stdin
	} else {
		var err error
		r, err = os.Open(*filename)
		if err != nil {
			log.Fatal(err)
		}
	}
	scanner := bufio.NewScanner(r)
	p := &pages{
		buf: new(bytes.Buffer),
		out: make(chan string),
		in:  make(chan string),
	}
	go p.process()

	done := make(chan int)
	go output(p.out, done)

	for scanner.Scan() {
		line := scanner.Text()
		switch strings.TrimSpace(line) {
		case "---":
			p.emit()
		default:
			p.cache(line)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	if p.buf.String() != "" {
		p.emit()
	}
	p.end()

	<-done
}

func output(ch chan string, done chan int) {
	pageTmpl := `

{{.PageHTML}}

`
	tmpl := template.Must(template.New("").Parse(pageTmpl))
	buf := new(bytes.Buffer)
	n := 0
	for pageHTML := range ch {
		err := tmpl.Execute(buf, struct {
			Xpixels  int
			PageHTML string
		}{
			Xpixels:  n * 1000,
			PageHTML: pageHTML,
		})
		if err != nil {
			log.Fatal(err)
		}
		n++
	}
	tmpl = template.Must(template.New("").Parse(finalTmpl))
	err := tmpl.Execute(os.Stdout, struct {
		CSS, JS, Content string
	}{
		CSS:     *css,
		JS:      *js,
		Content: buf.String(),
	})
	if err != nil {
		log.Fatal(err)
	}
	done <- 1
}

func (p *pages) cache(line string) {
	_, err := p.buf.WriteString(line + "\n")
	if err != nil {
		log.Fatal(err)
	}
}

func (p *pages) emit() {
	p.in <- p.buf.String()
	p.buf.Reset()
}

func (p *pages) end() {
	close(p.in)
}

func (p *pages) process() {
	for page := range p.in {
		p.out <- transfer(page)
	}
	close(p.out)
}

func transfer(md string) string {
	return string(blackfriday.MarkdownCommon([]byte(md)))
}
<|endoftext|>


<commit_before>package skeleton

// Command store command meta information
type Command struct {
	Name string

	// Flags are flag for the command
	Flags []Flag

	// Synopsis is short help message of the command
	Synopsis string

	// Help is long help message of the command
	Help string
}
<commit_msg>Add default variable<commit_after>package skeleton

const (
	DefaultSynopsis = ""
	DefaultHelp     = ""
)

// Command store command meta information
type Command struct {
	Name string

	// Flags are flag for the command
	Flags []Flag

	// Synopsis is short help message of the command
	Synopsis string

	// Help is long help message of the command
	Help string
}
<|endoftext|>


<commit_before>package slack

import (
	"encoding/json"
	"fmt"
	"github.com/alinpopa/barvin/data"
	"github.com/op/go-logging"
	"golang.org/x/net/websocket"
	"net/http"
	"net/url"
	"strings"
	"time"
)

var log = logging.MustGetLogger("main-logger")

func startRtm(origin string) data.RtmResponse {
	resp, err := http.Get(origin)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	var data data.RtmResponse
	json.NewDecoder(resp.Body).Decode(&data)
	return data
}

func connectWs(url string, origin string) (*websocket.Conn, error) {
	return websocket.Dial(url, "", origin)
}

func currentIpMessage(prefix string) data.WsMessage {
	ipResp, err := http.Get("https://api.ipify.org?format=json")
	if err != nil {
		log.Errorf("Error while fetching the IP: %s", err)
		return data.WsMessage{Msg: fmt.Sprintf("Error: %s", err)}
	}
	defer ipResp.Body.Close()
	var ipInfo data.IpInfo
	json.NewDecoder(ipResp.Body).Decode(&ipInfo)
	if len(prefix) > 0 {
		return data.WsMessage{Msg: prefix + ":" + ipInfo.Ip}
	}
	return data.WsMessage{Msg: ipInfo.Ip}
}

func getHomeWeather() data.WsMessage {
	weatherResp, err := http.Get("https://agent.electricimp.com/ABsCu4F5e-PU/json")
	if err != nil {
		log.Errorf("Error while fetching the weather: %s", err)
		return data.WsMessage{Msg: fmt.Sprintf("Error: %s", err)}
	}
	defer weatherResp.Body.Close()
	var weatherInfo data.WeatherInfo
	json.NewDecoder(weatherResp.Body).Decode(&weatherInfo)
	return data.WsMessage{Msg: fmt.Sprintf("Weather[temp:%f][pressure:%f][day:%t][humid:%f][lux:%f]",
		weatherInfo.Temp,
		weatherInfo.Pressure,
		weatherInfo.Day,
		weatherInfo.Humidity,
		weatherInfo.Lux)}
}

func replyMessage(ws *websocket.Conn, event data.WsEvent, msg string) error {
	return websocket.JSON.Send(ws, &data.WsEvent{
		Id:      event.Id,
		Type:    "message",
		Channel: event.Channel,
		Text:    msg,
		User:    event.User,
	})
}

func sendPrvMessage(to string, msg string, token string) error {
	rsp, err := http.PostForm("https://slack.com/api/chat.postMessage?token="+token, url.Values{"channel": {to}, "as_user": {"true"}, "text": {msg}})
	if err != nil {
		log.Errorf("Error while sending private message: %s", err)
	}
	if rsp != nil {
		rsp.Body.Close()
	}
	return err
}

func restart(msg string, err error, c chan<- string) {
	var m string
	if err != nil {
		m = fmt.Sprintf("%s[%s]", msg, err)
	} else {
		m = msg
	}
	log.Noticef("Restart: %s", m)
	go func() {
		c <- m
	}()
}

func SlackHandler(initMessage string, restartChannel chan<- string, userId string, token string) {
	origin := "https://slack.com/api/rtm.start?token=" + token
	rtm := startRtm(origin)
	log.Debugf("RTM url: %s", rtm.Url)
	ws, err := connectWs(rtm.Url, origin)
	for err != nil {
		log.Infof("Got error; trying to reconnect to WS: %s", err)
		time.Sleep(35 * time.Second)
		rtm := startRtm(origin)
		ws, err = connectWs(rtm.Url, origin)
	}
	sendPrvMessage(userId, currentIpMessage(initMessage).Msg, token)
	sendPrvMessage(userId, getHomeWeather().Msg, token)
	for {
		var msg string
		var event data.WsEvent
		err := websocket.Message.Receive(ws, &msg)
		if err != nil {
			restart("Error while receiving message", err, restartChannel)
			break
		}
		unmarshallErr := json.Unmarshal([]byte(msg), &event)
		if unmarshallErr != nil {
			log.Errorf("Error while unmarshaling message: %s", msg)
			restart("Error unmarshalling message", unmarshallErr, restartChannel)
			break
		}
		log.Debugf("Raw message: %s", msg)
		log.Infof("Got event %+v", event)
		if strings.ToLower(event.Text) == "ip" && event.User == userId {
			replyMessage(ws, event, currentIpMessage("").Msg)
		} else if strings.ToLower(event.Text) == "weather" && event.User == userId {
			replyMessage(ws, event, getHomeWeather().Msg)
		}
	}
}
<commit_msg>Reformat the weather message.<commit_after>package slack

import (
	"encoding/json"
	"fmt"
	"github.com/alinpopa/barvin/data"
	"github.com/op/go-logging"
	"golang.org/x/net/websocket"
	"net/http"
	"net/url"
	"strings"
	"time"
)

var log = logging.MustGetLogger("main-logger")

func startRtm(origin string) data.RtmResponse {
	resp, err := http.Get(origin)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	var data data.RtmResponse
	json.NewDecoder(resp.Body).Decode(&data)
	return data
}

func connectWs(url string, origin string) (*websocket.Conn, error) {
	return websocket.Dial(url, "", origin)
}

func currentIpMessage(prefix string) data.WsMessage {
	ipResp, err := http.Get("https://api.ipify.org?format=json")
	if err != nil {
		log.Errorf("Error while fetching the IP: %s", err)
		return data.WsMessage{Msg: fmt.Sprintf("Error: %s", err)}
	}
	defer ipResp.Body.Close()
	var ipInfo data.IpInfo
	json.NewDecoder(ipResp.Body).Decode(&ipInfo)
	if len(prefix) > 0 {
		return data.WsMessage{Msg: prefix + ":" + ipInfo.Ip}
	}
	return data.WsMessage{Msg: ipInfo.Ip}
}

func getHomeWeather() data.WsMessage {
	weatherResp, err := http.Get("https://agent.electricimp.com/ABsCu4F5e-PU/json")
	if err != nil {
		log.Errorf("Error while fetching the weather: %s", err)
		return data.WsMessage{Msg: fmt.Sprintf("Error: %s", err)}
	}
	defer weatherResp.Body.Close()
	var weatherInfo data.WeatherInfo
	json.NewDecoder(weatherResp.Body).Decode(&weatherInfo)
	return data.WsMessage{Msg: fmt.Sprintf("Weather\ntemperature:%f\npressure:%f\nlastPressure:%f\nis day:%t\nhumidity:%f\nlux:%f",
		weatherInfo.Temp,
		weatherInfo.Pressure,
		weatherInfo.LastPressure,
		weatherInfo.Day,
		weatherInfo.Humidity,
		weatherInfo.Lux)}
}

func replyMessage(ws *websocket.Conn, event data.WsEvent, msg string) error {
	return websocket.JSON.Send(ws, &data.WsEvent{
		Id:      event.Id,
		Type:    "message",
		Channel: event.Channel,
		Text:    msg,
		User:    event.User,
	})
}

func sendPrvMessage(to string, msg string, token string) error {
	rsp, err := http.PostForm("https://slack.com/api/chat.postMessage?token="+token, url.Values{"channel": {to}, "as_user": {"true"}, "text": {msg}})
	if err != nil {
		log.Errorf("Error while sending private message: %s", err)
	}
	if rsp != nil {
		rsp.Body.Close()
	}
	return err
}

func restart(msg string, err error, c chan<- string) {
	var m string
	if err != nil {
		m = fmt.Sprintf("%s[%s]", msg, err)
	} else {
		m = msg
	}
	log.Noticef("Restart: %s", m)
	go func() {
		c <- m
	}()
}

func SlackHandler(initMessage string, restartChannel chan<- string, userId string, token string) {
	origin := "https://slack.com/api/rtm.start?token=" + token
	rtm := startRtm(origin)
	log.Debugf("RTM url: %s", rtm.Url)
	ws, err := connectWs(rtm.Url, origin)
	for err != nil {
		log.Infof("Got error; trying to reconnect to WS: %s", err)
		time.Sleep(35 * time.Second)
		rtm := startRtm(origin)
		ws, err = connectWs(rtm.Url, origin)
	}
	sendPrvMessage(userId, currentIpMessage(initMessage).Msg, token)
	sendPrvMessage(userId, getHomeWeather().Msg, token)
	for {
		var msg string
		var event data.WsEvent
		err := websocket.Message.Receive(ws, &msg)
		if err != nil {
			restart("Error while receiving message", err, restartChannel)
			break
		}
		unmarshallErr := json.Unmarshal([]byte(msg), &event)
		if unmarshallErr != nil {
			log.Errorf("Error while unmarshaling message: %s", msg)
			restart("Error unmarshalling message", unmarshallErr, restartChannel)
			break
		}
		log.Debugf("Raw message: %s", msg)
		log.Infof("Got event %+v", event)
		if strings.ToLower(event.Text) == "ip" && event.User == userId {
			replyMessage(ws, event, currentIpMessage("").Msg)
		} else if strings.ToLower(event.Text) == "weather" && event.User == userId {
			replyMessage(ws, event, getHomeWeather().Msg)
		}
	}
}
<|endoftext|>


<commit_before>/*Package configurationFile represents Encodable/Decodable Golang structures from/to TOML structures.
 *
 *The global structure is ConfigurationFile, which is the simple way to store accurtely your local informations.
 *
 */
package configurationFile

import (
	"bytes"
	"errors"
	"path/filepath"

	"fmt"

	"os/user"

	"github.com/BurntSushi/toml"
	"github.com/k0pernicus/goyave/consts"
	"github.com/k0pernicus/goyave/utils"
)

/*ConfigurationFile represents the TOML structure of the Goyave configuration file.
 *
 *The structure of the configuration file is:
 *	Author:
 *		The name of the user.
 *	VisibleRepositories:
 *		A list of local visible git repositories.
 *	HiddenRepositories:
 *		A list of local ignored git repositories.
 *	Groups:
 *		A list of groups.
 */
type ConfigurationFile struct {
	Author              string
	Local               LocalInformations `toml:"local"`
	VisibleRepositories []GitRepository   `toml:"visible"`
	HiddenRepositories  []GitRepository   `toml:"hidden"`
	Groups              []Group           `toml:"group"`
}

/*Default returns a default ConfigurationFile structure.
 */
func Default() *ConfigurationFile {
	usr, _ := user.Current()
	return &ConfigurationFile{
		Author: usr.Username,
		Local: LocalInformations{
			DefaultEntry: consts.VisibleFlag,
			Group:        utils.GetLocalhost(),
		},
	}
}

/*GetDefaultEntry returns the default entry to store a new git repository.
 *This methods returns HiddenFlag, or VisibleFlag
 */
func (c *ConfigurationFile) GetDefaultEntry() (string, error) {
	defaultEntry := c.Local.DefaultEntry
	if defaultEntry != consts.VisibleFlag && defaultEntry != consts.HiddenFlag {
		return consts.VisibleFlag, fmt.Errorf("the default entry is not set to %s or %s. Please check your configuration file", consts.HiddenFlag, consts.VisibleFlag)
	}
	return defaultEntry, nil
}

/*AddRepository will add a single path in the TOML's target if the path does not exists.
 *
 *This method uses both methods addVisibleRepository and addHiddenRepository.
 */
func (c *ConfigurationFile) AddRepository(path string, target string) error {
	if target == consts.VisibleFlag {
		return c.addVisibleRepository(path)
	}
	if target == consts.HiddenFlag {
		return c.addHiddenRepository(path)
	}
	return errors.New("the target does not exists")
}

/*addVisibleRepository adds a given git repo path as a visible repository
 *
 *If the repository already exists in the VisibleRepository field, the method throws an error: RepositoryAlreadyExists.
 *Else, the repository is append to the VisibleRepository field, and the method returns nil.
 */
func (c *ConfigurationFile) addVisibleRepository(path string) error {
	for _, registeredRepository := range c.VisibleRepositories {
		if registeredRepository.Path == path {
			return errors.New(consts.RepositoryAlreadyExists)
		}
	}
	var newVisibleRepository = GitRepository{
		Name: filepath.Base(path),
		Path: path,
	}
	c.VisibleRepositories = append(c.VisibleRepositories, newVisibleRepository)
	return nil
}

/*addHiddenRepository adds a given git repo path as an hidden repository
 *
 *If the repository already exists in the HiddenRepository field, the method throws an error: RepositoryAlreadyExists.
 *Else, the repository is append to the HiddenRepository field, and the method returns nil.
 */
func (c *ConfigurationFile) addHiddenRepository(path string) error {
	for _, registeredRepository := range c.HiddenRepositories {
		if registeredRepository.Path == path {
			return errors.New(consts.RepositoryAlreadyExists)
		}
	}
	var newHiddenRepository = GitRepository{
		Name: filepath.Base(path),
		Path: path,
	}
	c.HiddenRepositories = append(c.HiddenRepositories, newHiddenRepository)
	return nil
}

/*GitRepository represents the structure of a local git repository.
 *
 *Properties of this structure are:
 *	Name:
 * 		The custom name of the repository.
 *	Path:
 *		The path of the repository.
 */
type GitRepository struct {
	Name string
	Path string
}

/*Group represents a group of git repositories.
 *
 *The structure of a Group type is:
 *
 *	Name:
 *		The group name.
 *	Repositories:
 *		A list of git repositories id, tagged in the group.
 */
type Group struct {
	Name         string
	Repositories []string
}

/*LocalInformations represents your local configuration of Goyave.
 *
 *The structure contains:
 *  DefaultEntry:
 *		The default entry to store a git repository (hidden or visible).
 *	Group:
 *		The current group name.
 */
type LocalInformations struct {
	DefaultEntry string
	Group        string
}

/*DecodeString is a function to decode an entire string (which is the content of a given TOML file) to a ConfigurationFile structure.
 */
func (c *ConfigurationFile) DecodeString(data string) error {
	_, err := toml.Decode(data, c)
	return err
}

/*DecodeBytesArray is a function to decode an entire string (which is the content of a given TOML file) to a ConfigurationFile structure.
 */
func (c *ConfigurationFile) DecodeBytesArray(data []byte) error {
	_, err := toml.Decode(string(data[:]), c)
	return err
}

/*Encode is a function to encode a ConfigurationFile structure to a byffer of bytes.
 */
func (c *ConfigurationFile) Encode(buffer *bytes.Buffer) error {
	return toml.NewEncoder(buffer).Encode(c)
}
<commit_msg>New method to check is a git repository is existing (as a local repository)<commit_after>/*Package configurationFile represents Encodable/Decodable Golang structures from/to TOML structures.
 *
 *The global structure is ConfigurationFile, which is the simple way to store accurtely your local informations.
 *
 */
package configurationFile

import (
	"bytes"
	"errors"
	"path/filepath"

	"fmt"

	"os/user"

	"os"

	"github.com/BurntSushi/toml"
	"github.com/k0pernicus/goyave/consts"
	"github.com/k0pernicus/goyave/utils"
)

/*ConfigurationFile represents the TOML structure of the Goyave configuration file.
 *
 *The structure of the configuration file is:
 *	Author:
 *		The name of the user.
 *	VisibleRepositories:
 *		A list of local visible git repositories.
 *	HiddenRepositories:
 *		A list of local ignored git repositories.
 *	Groups:
 *		A list of groups.
 */
type ConfigurationFile struct {
	Author              string
	Local               LocalInformations `toml:"local"`
	VisibleRepositories []GitRepository   `toml:"visible"`
	HiddenRepositories  []GitRepository   `toml:"hidden"`
	Groups              []Group           `toml:"group"`
}

/*Default returns a default ConfigurationFile structure.
 */
func Default() *ConfigurationFile {
	usr, _ := user.Current()
	return &ConfigurationFile{
		Author: usr.Username,
		Local: LocalInformations{
			DefaultEntry: consts.VisibleFlag,
			Group:        utils.GetLocalhost(),
		},
	}
}

/*GetDefaultEntry returns the default entry to store a new git repository.
 *This methods returns HiddenFlag, or VisibleFlag
 */
func (c *ConfigurationFile) GetDefaultEntry() (string, error) {
	defaultEntry := c.Local.DefaultEntry
	if defaultEntry != consts.VisibleFlag && defaultEntry != consts.HiddenFlag {
		return consts.VisibleFlag, fmt.Errorf("the default entry is not set to %s or %s. Please check your configuration file", consts.HiddenFlag, consts.VisibleFlag)
	}
	return defaultEntry, nil
}

/*AddRepository will add a single path in the TOML's target if the path does not exists.
 *
 *This method uses both methods addVisibleRepository and addHiddenRepository.
 */
func (c *ConfigurationFile) AddRepository(path string, target string) error {
	if target == consts.VisibleFlag {
		return c.addVisibleRepository(path)
	}
	if target == consts.HiddenFlag {
		return c.addHiddenRepository(path)
	}
	return errors.New("the target does not exists")
}

/*addVisibleRepository adds a given git repo path as a visible repository
 *
 *If the repository already exists in the VisibleRepository field, the method throws an error: RepositoryAlreadyExists.
 *Else, the repository is append to the VisibleRepository field, and the method returns nil.
 */
func (c *ConfigurationFile) addVisibleRepository(path string) error {
	for _, registeredRepository := range c.VisibleRepositories {
		if registeredRepository.Path == path {
			return errors.New(consts.RepositoryAlreadyExists)
		}
	}
	var newVisibleRepository = GitRepository{
		Name: filepath.Base(path),
		Path: path,
	}
	c.VisibleRepositories = append(c.VisibleRepositories, newVisibleRepository)
	return nil
}

/*addHiddenRepository adds a given git repo path as an hidden repository
 *
 *If the repository already exists in the HiddenRepository field, the method throws an error: RepositoryAlreadyExists.
 *Else, the repository is append to the HiddenRepository field, and the method returns nil.
 */
func (c *ConfigurationFile) addHiddenRepository(path string) error {
	for _, registeredRepository := range c.HiddenRepositories {
		if registeredRepository.Path == path {
			return errors.New(consts.RepositoryAlreadyExists)
		}
	}
	var newHiddenRepository = GitRepository{
		Name: filepath.Base(path),
		Path: path,
	}
	c.HiddenRepositories = append(c.HiddenRepositories, newHiddenRepository)
	return nil
}

/*GitRepository represents the structure of a local git repository.
 *
 *Properties of this structure are:
 *	Name:
 * 		The custom name of the repository.
 *	Path:
 *		The path of the repository.
 */
type GitRepository struct {
	Name string
	Path string
}

/*isExists check if the current path of the git repository is correct or not,
 *and if the current repository exists again or not.
 *This methods returns a boolean value.
 */
func (g *GitRepository) isExists() bool {
	_, err := os.Stat(g.Path)
	return os.IsNotExist(err)
}

/*Group represents a group of git repositories.
 *
 *The structure of a Group type is:
 *
 *	Name:
 *		The group name.
 *	Repositories:
 *		A list of git repositories id, tagged in the group.
 */
type Group struct {
	Name         string
	Repositories []string
}

/*LocalInformations represents your local configuration of Goyave.
 *
 *The structure contains:
 *  DefaultEntry:
 *		The default entry to store a git repository (hidden or visible).
 *	Group:
 *		The current group name.
 */
type LocalInformations struct {
	DefaultEntry string
	Group        string
}

/*DecodeString is a function to decode an entire string (which is the content of a given TOML file) to a ConfigurationFile structure.
 */
func (c *ConfigurationFile) DecodeString(data string) error {
	_, err := toml.Decode(data, c)
	return err
}

/*DecodeBytesArray is a function to decode an entire string (which is the content of a given TOML file) to a ConfigurationFile structure.
 */
func (c *ConfigurationFile) DecodeBytesArray(data []byte) error {
	_, err := toml.Decode(string(data[:]), c)
	return err
}

/*Encode is a function to encode a ConfigurationFile structure to a byffer of bytes.
 */
func (c *ConfigurationFile) Encode(buffer *bytes.Buffer) error {
	return toml.NewEncoder(buffer).Encode(c)
}
<|endoftext|>