<commit_before>//  Copyright 2018 Google LLC
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at

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

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

package overlord

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

	"github.com/google/minions/go/grpcutil"
	"github.com/google/minions/go/overlord/interests"
	"github.com/google/minions/go/overlord/state"
	"github.com/google/uuid"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	mpb "github.com/google/minions/proto/minions"
	pb "github.com/google/minions/proto/overlord"
)

// Server implements the OverlordServer interface, the orchestrator of Minions' infrastructure.
type Server struct {
	minions          map[string]mpb.MinionClient // The minions we know about and their address
	initialInterests []*state.MappedInterest     // The initial interests all scans will get
	stateManager     StateManager                // Manages local stage between scans.
}

// StateManager handles the state of an Overlord through multiple
// scans.
type StateManager interface {
	// AddFiles atomically sets the state of a minion during a scan.
	AddFiles(scanID string, files []*pb.File) error
	// AddInterest adds a new interest for a given minion to the state of the scan.
	AddInterest(scanID string, interest *mpb.Interest, minion string) error
	// CreateScan initializes the state for a scan.
	CreateScan(scanID string) error
	// GetFiles returns all the files known for a given ScanID
	GetFiles(scanID string) ([]*pb.File, error)
	// GetInterests returns all the interests known for a given ScanID, mapped to minions
	GetInterests(scanID string) ([]*state.MappedInterest, error)
	// RemoveFile atomically removes a given file from the state.
	RemoveFile(scanID string, file *pb.File) (bool, error)
	// ScanExists returns true if any state at all is known about the scan.
	ScanExists(scanID string) bool
}

// New returns an initialized Server, which connects to a set of pre-specified minions
// to initialize them. It accepts the path of a CA certificate to use to check the
// minions server certs
func New(ctx context.Context, minionAddresses []string, caCertPath string) (*Server, error) {
	server := &Server{
		minions:      make(map[string]mpb.MinionClient),
		stateManager: state.NewLocal(),
	}

	log.Println("Reaching out to all minions.")
	// Build map of minions.
	for _, addr := range minionAddresses {
		log.Printf("Reaching out to minion at %s\n", addr)
		host := strings.Split(addr, ":")[0] // If we have a port, extract hostname
		opts, err := grpcutil.GetSslClientOptions(host, caCertPath)
		if err != nil {
			return nil, err
		}
		c, err := grpc.Dial(addr, opts)
		if err != nil {
			return nil, err
		}
		log.Println("Ok, minion connected")
		server.minions[addr] = mpb.NewMinionClient(c)
	}
	log.Println("Retrieving initial interests")
	interests, err := getInterestsFromMinions(ctx, server.minions)
	if err != nil {
		return nil, err
	}
	server.initialInterests = interests
	log.Printf("Initial interests: %d", len(server.initialInterests))
	return server, nil
}

const minionInitDeadlineMs = 1000

func getInterestsFromMinions(ctx context.Context, minions map[string]mpb.MinionClient) ([]*state.MappedInterest, error) {
	var interests []*state.MappedInterest
	for name, m := range minions {
		// Note how we build a dedicated context for each request
		deadlineCtx, cancel := context.WithTimeout(context.Background(), minionInitDeadlineMs*time.Millisecond)
		defer cancel()
		intResp, err := m.ListInitialInterests(deadlineCtx, &mpb.ListInitialInterestsRequest{})
		if err != nil {
			return nil, err
		}
		for _, v := range intResp.GetInterests() {
			interests = append(interests, &state.MappedInterest{
				Interest: v,
				Minion:   name,
			})
		}
	}
	return interests, nil
}

// CreateScan set up a security scan which can then be fed files via ScanFiles.
// It returns a UUID identifying the scan from now on and the list of initial
// Interests.
func (s *Server) CreateScan(ctx context.Context, req *pb.CreateScanRequest) (*pb.Scan, error) {
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}
	// Scans are tracked by UUID, so let's start by generating it.
	scan := &pb.Scan{}
	scan.ScanId = uuid.New().String()

	s.stateManager.CreateScan(scan.ScanId)
	for _, i := range s.initialInterests {
		s.stateManager.AddInterest(scan.ScanId, i.Interest, i.Minion)
	}

	knownInterests, err := s.stateManager.GetInterests(scan.ScanId)
	if err != nil {
		return nil, err
	}
	for _, interest := range knownInterests {
		scan.Interests = append(scan.Interests, interest.Interest)
	}
	scan.Interests = interests.Minify(scan.Interests)

	return scan, nil
}

// ListInterests returns the interests for a given scan, i.e. the files or metadata
// that have to be fed to the Overlord for security scanning.
func (s *Server) ListInterests(ctx context.Context, req *pb.ListInterestsRequest) (*pb.ListInterestsResponse, error) {
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}
	if req.GetPageToken() != "" {
		return nil, fmt.Errorf("token support is unimplemented")
	}
	if !s.stateManager.ScanExists(req.GetScanId()) {
		return nil, fmt.Errorf("unknown scan ID %s", req.GetScanId())
	}
	scanInterests, err := s.stateManager.GetInterests(req.GetScanId())
	if err != nil {
		return nil, err
	}
	resp := &pb.ListInterestsResponse{}
	for _, interest := range scanInterests {
		resp.Interests = append(resp.Interests, interest.Interest)
	}
	resp.Interests = interests.Minify(resp.Interests)
	return resp, nil
}

// ScanFiles runs security scan on a set of files, assuming they were actually
// needed by the backend minions.
func (s *Server) ScanFiles(ctx context.Context, req *pb.ScanFilesRequest) (*pb.ScanFilesResponse, error) {
	// First of all, check if the request is still valid. Due to queue,
	// throttling or whatever else the client might have changed its mind.
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}

	scanID := req.GetScanId()
	if !s.stateManager.ScanExists(scanID) {
		return nil, fmt.Errorf("unknown scan ID %s", scanID)
	}

	if err := s.stateManager.AddFiles(req.GetScanId(), req.GetFiles()); err != nil {
		return nil, fmt.Errorf("error adding files to the scan state: %v", err)
	}

	// Now distribute all complete files for scanning.
	routedFiles := make(map[string][]*mpb.File)

	files, err := s.stateManager.GetFiles(scanID)
	if err != nil {
		return nil, err
	}
	for _, f := range files {
		interestsForMinions, err := s.stateManager.GetInterests(scanID)
		if err != nil {
			return nil, err
		}
		for _, candidate := range interestsForMinions {
			if match, err := interests.IsMatching(candidate.Interest, f); err != nil {
				return nil, err
			} else if !match {
				continue
			}

			isComplete := f.GetMetadata().GetSize() == int64(len(f.GetDataChunks()[0].GetData()))

			if candidate.Interest.DataType == mpb.Interest_METADATA_AND_DATA && isComplete {
				routedFiles[candidate.Minion] = append(routedFiles[candidate.Minion], &mpb.File{
					Metadata: f.GetMetadata(),
					Data:     f.GetDataChunks()[0].GetData(), // Note we accumulate in the first chunk.
				})
			} else if candidate.Interest.DataType == mpb.Interest_METADATA {
				// Send only metadata.
				routedFiles[candidate.Minion] = append(routedFiles[candidate.Minion], &mpb.File{
					Metadata: f.GetMetadata(),
				})
			}
		}
	}

	resp := &pb.ScanFilesResponse{}
	for address, files := range routedFiles {
		minion, present := s.minions[address]
		if !present {
			return nil, fmt.Errorf("interest expressed by a minion that is not known to the Overlord, %q", address)
		}
		minionResp, err := minion.AnalyzeFiles(ctx, &mpb.AnalyzeFilesRequest{
			ScanId: req.ScanId,
			Files:  files,
		})
		if err != nil {
			return nil, err
		}
		resp.Results = append(resp.Results, minionResp.GetFindings()...)
		resp.NewInterests = append(resp.NewInterests, minionResp.GetNewInterests()...)

		// Now export the new interests back to the state.
		for _, newInterest := range minionResp.GetNewInterests() {
			s.stateManager.AddInterest(scanID, newInterest, address)
		}
	}
	return resp, nil
}
<commit_msg>Minor debugging logging improvement<commit_after>//  Copyright 2018 Google LLC
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at

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

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

package overlord

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

	"github.com/google/minions/go/grpcutil"
	"github.com/google/minions/go/overlord/interests"
	"github.com/google/minions/go/overlord/state"
	"github.com/google/uuid"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	mpb "github.com/google/minions/proto/minions"
	pb "github.com/google/minions/proto/overlord"
)

// Server implements the OverlordServer interface, the orchestrator of Minions' infrastructure.
type Server struct {
	minions          map[string]mpb.MinionClient // The minions we know about and their address
	initialInterests []*state.MappedInterest     // The initial interests all scans will get
	stateManager     StateManager                // Manages local stage between scans.
}

// StateManager handles the state of an Overlord through multiple
// scans.
type StateManager interface {
	// AddFiles atomically sets the state of a minion during a scan.
	AddFiles(scanID string, files []*pb.File) error
	// AddInterest adds a new interest for a given minion to the state of the scan.
	AddInterest(scanID string, interest *mpb.Interest, minion string) error
	// CreateScan initializes the state for a scan.
	CreateScan(scanID string) error
	// GetFiles returns all the files known for a given ScanID
	GetFiles(scanID string) ([]*pb.File, error)
	// GetInterests returns all the interests known for a given ScanID, mapped to minions
	GetInterests(scanID string) ([]*state.MappedInterest, error)
	// RemoveFile atomically removes a given file from the state.
	RemoveFile(scanID string, file *pb.File) (bool, error)
	// ScanExists returns true if any state at all is known about the scan.
	ScanExists(scanID string) bool
}

// New returns an initialized Server, which connects to a set of pre-specified minions
// to initialize them. It accepts the path of a CA certificate to use to check the
// minions server certs
func New(ctx context.Context, minionAddresses []string, caCertPath string) (*Server, error) {
	server := &Server{
		minions:      make(map[string]mpb.MinionClient),
		stateManager: state.NewLocal(),
	}

	log.Printf("Reaching out to all minions. Got %d from flags\n", len(minionAddresses))
	// Build map of minions.
	for _, addr := range minionAddresses {
		log.Printf("Reaching out to minion at %s\n", addr)
		host := strings.Split(addr, ":")[0] // If we have a port, extract hostname
		opts, err := grpcutil.GetSslClientOptions(host, caCertPath)
		if err != nil {
			return nil, err
		}
		c, err := grpc.Dial(addr, opts)
		if err != nil {
			return nil, err
		}
		log.Println("Ok, minion connected")
		server.minions[addr] = mpb.NewMinionClient(c)
	}
	log.Println("Retrieving initial interests")
	interests, err := getInterestsFromMinions(ctx, server.minions)
	if err != nil {
		return nil, err
	}
	server.initialInterests = interests
	log.Printf("Initial interests: %d", len(server.initialInterests))
	return server, nil
}

const minionInitDeadlineMs = 1000

func getInterestsFromMinions(ctx context.Context, minions map[string]mpb.MinionClient) ([]*state.MappedInterest, error) {
	var interests []*state.MappedInterest
	for name, m := range minions {
		// Note how we build a dedicated context for each request
		deadlineCtx, cancel := context.WithTimeout(context.Background(), minionInitDeadlineMs*time.Millisecond)
		defer cancel()
		log.Printf("Fetching initial interests from %s\n", name)
		intResp, err := m.ListInitialInterests(deadlineCtx, &mpb.ListInitialInterestsRequest{})
		if err != nil {
			return nil, err
		}
		for _, v := range intResp.GetInterests() {
			interests = append(interests, &state.MappedInterest{
				Interest: v,
				Minion:   name,
			})
		}
	}
	return interests, nil
}

// CreateScan set up a security scan which can then be fed files via ScanFiles.
// It returns a UUID identifying the scan from now on and the list of initial
// Interests.
func (s *Server) CreateScan(ctx context.Context, req *pb.CreateScanRequest) (*pb.Scan, error) {
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}
	// Scans are tracked by UUID, so let's start by generating it.
	scan := &pb.Scan{}
	scan.ScanId = uuid.New().String()

	s.stateManager.CreateScan(scan.ScanId)
	for _, i := range s.initialInterests {
		s.stateManager.AddInterest(scan.ScanId, i.Interest, i.Minion)
	}

	knownInterests, err := s.stateManager.GetInterests(scan.ScanId)
	if err != nil {
		return nil, err
	}
	for _, interest := range knownInterests {
		scan.Interests = append(scan.Interests, interest.Interest)
	}
	scan.Interests = interests.Minify(scan.Interests)

	return scan, nil
}

// ListInterests returns the interests for a given scan, i.e. the files or metadata
// that have to be fed to the Overlord for security scanning.
func (s *Server) ListInterests(ctx context.Context, req *pb.ListInterestsRequest) (*pb.ListInterestsResponse, error) {
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}
	if req.GetPageToken() != "" {
		return nil, fmt.Errorf("token support is unimplemented")
	}
	if !s.stateManager.ScanExists(req.GetScanId()) {
		return nil, fmt.Errorf("unknown scan ID %s", req.GetScanId())
	}
	scanInterests, err := s.stateManager.GetInterests(req.GetScanId())
	if err != nil {
		return nil, err
	}
	resp := &pb.ListInterestsResponse{}
	for _, interest := range scanInterests {
		resp.Interests = append(resp.Interests, interest.Interest)
	}
	resp.Interests = interests.Minify(resp.Interests)
	return resp, nil
}

// ScanFiles runs security scan on a set of files, assuming they were actually
// needed by the backend minions.
func (s *Server) ScanFiles(ctx context.Context, req *pb.ScanFilesRequest) (*pb.ScanFilesResponse, error) {
	// First of all, check if the request is still valid. Due to queue,
	// throttling or whatever else the client might have changed its mind.
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "Client cancelled, abandoning.")
	}

	scanID := req.GetScanId()
	if !s.stateManager.ScanExists(scanID) {
		return nil, fmt.Errorf("unknown scan ID %s", scanID)
	}

	if err := s.stateManager.AddFiles(req.GetScanId(), req.GetFiles()); err != nil {
		return nil, fmt.Errorf("error adding files to the scan state: %v", err)
	}

	// Now distribute all complete files for scanning.
	routedFiles := make(map[string][]*mpb.File)

	files, err := s.stateManager.GetFiles(scanID)
	if err != nil {
		return nil, err
	}
	for _, f := range files {
		interestsForMinions, err := s.stateManager.GetInterests(scanID)
		if err != nil {
			return nil, err
		}
		for _, candidate := range interestsForMinions {
			if match, err := interests.IsMatching(candidate.Interest, f); err != nil {
				return nil, err
			} else if !match {
				continue
			}

			isComplete := f.GetMetadata().GetSize() == int64(len(f.GetDataChunks()[0].GetData()))

			if candidate.Interest.DataType == mpb.Interest_METADATA_AND_DATA && isComplete {
				routedFiles[candidate.Minion] = append(routedFiles[candidate.Minion], &mpb.File{
					Metadata: f.GetMetadata(),
					Data:     f.GetDataChunks()[0].GetData(), // Note we accumulate in the first chunk.
				})
			} else if candidate.Interest.DataType == mpb.Interest_METADATA {
				// Send only metadata.
				routedFiles[candidate.Minion] = append(routedFiles[candidate.Minion], &mpb.File{
					Metadata: f.GetMetadata(),
				})
			}
		}
	}

	resp := &pb.ScanFilesResponse{}
	for address, files := range routedFiles {
		minion, present := s.minions[address]
		if !present {
			return nil, fmt.Errorf("interest expressed by a minion that is not known to the Overlord, %q", address)
		}
		minionResp, err := minion.AnalyzeFiles(ctx, &mpb.AnalyzeFilesRequest{
			ScanId: req.ScanId,
			Files:  files,
		})
		if err != nil {
			return nil, err
		}
		resp.Results = append(resp.Results, minionResp.GetFindings()...)
		resp.NewInterests = append(resp.NewInterests, minionResp.GetNewInterests()...)

		// Now export the new interests back to the state.
		for _, newInterest := range minionResp.GetNewInterests() {
			s.stateManager.AddInterest(scanID, newInterest, address)
		}
	}
	return resp, nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 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 httpext

import (
	"bytes"
	"context"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
)

type reader func([]byte) (int, error)

func (r reader) Read(a []byte) (int, error) {
	return ((func([]byte) (int, error))(r))(a)
}

const badReadMsg = "bad read error for test"
const badCloseMsg = "bad close error for test"

func badReadBody() io.Reader {
	return reader(func(_ []byte) (int, error) {
		return 0, errors.New(badReadMsg)
	})
}

type closer func() error

func (c closer) Close() error {
	return ((func() error)(c))()
}

func badCloseBody() io.ReadCloser {
	return struct {
		io.Reader
		io.Closer
	}{
		Reader: reader(func(_ []byte) (int, error) {
			return 0, io.EOF
		}),
		Closer: closer(func() error {
			return errors.New(badCloseMsg)
		}),
	}
}

func TestCompressionBodyError(t *testing.T) {
	var algos = []CompressionType{CompressionTypeGzip}
	t.Run("bad read body", func(t *testing.T) {
		_, _, err := compressBody(algos, ioutil.NopCloser(badReadBody()))
		require.Error(t, err)
		require.Equal(t, err.Error(), badReadMsg)
	})

	t.Run("bad close body", func(t *testing.T) {
		_, _, err := compressBody(algos, badCloseBody())
		require.Error(t, err)
		require.Equal(t, err.Error(), badCloseMsg)
	})
}

func TestMakeRequestError(t *testing.T) {
	var ctx, cancel = context.WithCancel(context.Background())
	defer cancel()

	t.Run("bad compression algorithm body", func(t *testing.T) {
		var req, err = http.NewRequest("GET", "https://wont.be.used", nil)

		require.NoError(t, err)
		var badCompressionType = CompressionType(13)
		require.False(t, badCompressionType.IsACompressionType())
		var preq = &ParsedHTTPRequest{
			Req:          req,
			Body:         new(bytes.Buffer),
			Compressions: []CompressionType{badCompressionType},
		}
		_, err = MakeRequest(ctx, preq)
		require.Error(t, err)
		require.Equal(t, err.Error(), "unknown compressionType CompressionType(13)")
	})

	t.Run("invalid upgrade response", func(t *testing.T) {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Connection", "Upgrade")
			w.Header().Add("Upgrade", "h2c")
			w.WriteHeader(http.StatusSwitchingProtocols)
		}))
		defer srv.Close()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		logger := logrus.New()
		logger.Level = logrus.DebugLevel
		state := &lib.State{
			Options:   lib.Options{RunTags: &stats.SampleTags{}},
			Transport: srv.Client().Transport,
			Logger:    logger,
		}
		ctx = lib.WithState(ctx, state)
		req, _ := http.NewRequest("GET", srv.URL, nil)
		var preq = &ParsedHTTPRequest{
			Req:     req,
			URL:     &URL{u: req.URL},
			Body:    new(bytes.Buffer),
			Timeout: 10 * time.Second,
		}

		res, err := MakeRequest(ctx, preq)

		assert.Nil(t, res)
		assert.EqualError(t, err, "unsupported response status: 101 Switching Protocols")
	})
}

func TestResponseStatus(t *testing.T) {
	testCases := []struct {
		name                     string
		statusCode               int
		statusCodeExpected       int
		statusCodeStringExpected string
	}{
		{"status 200", 200, 200, "200 OK"},
		{"status 201", 201, 201, "201 Created"},
		{"status 202", 202, 202, "202 Accepted"},
		{"status 203", 203, 203, "203 Non-Authoritative Information"},
		{"status 204", 204, 204, "204 No Content"},
		{"status 205", 205, 205, "205 Reset Content"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				//_, err := w.Write([]byte("some body"))
				//require.NoError(t, err)
				w.WriteHeader(tc.statusCode)
			}))
			defer server.Close()
			logger := logrus.New()
			logger.Level = logrus.DebugLevel
			state := &lib.State{
				Options:   lib.Options{RunTags: &stats.SampleTags{}},
				Transport: server.Client().Transport,
				Logger:    logger,
				Samples:   make(chan<- stats.SampleContainer, 1),
			}
			ctx := lib.WithState(context.Background(), state)
			req, err := http.NewRequest("GET", server.URL, nil)
			require.NoError(t, err)

			var preq = &ParsedHTTPRequest{
				Req:          req,
				URL:          &URL{u: req.URL},
				Body:         new(bytes.Buffer),
				Timeout:      10 * time.Second,
				ResponseType: ResponseTypeNone,
			}

			res, err := MakeRequest(ctx, preq)
			require.NoError(t, err)
			//require.NotNil(t, res)
			assert.Equal(t, tc.statusCodeExpected, res.Status)
			assert.Equal(t, tc.statusCodeStringExpected, res.StatusText)
		})
	}
}

func TestURL(t *testing.T) {
	t.Run("Clean", func(t *testing.T) {
		testCases := []struct {
			url      string
			expected string
		}{
			{"https://example.com/", "https://example.com/"},
			{"https://example.com/${}", "https://example.com/${}"},
			{"https://user@example.com/", "https://****@example.com/"},
			{"https://user:pass@example.com/", "https://****:****@example.com/"},
			{"https://user:pass@example.com/path?a=1&b=2", "https://****:****@example.com/path?a=1&b=2"},
			{"https://user:pass@example.com/${}/${}", "https://****:****@example.com/${}/${}"},
			{"@malformed/url", "@malformed/url"},
			{"not a url", "not a url"},
		}

		for _, tc := range testCases {
			tc := tc
			t.Run(tc.url, func(t *testing.T) {
				u, err := url.Parse(tc.url)
				require.NoError(t, err)
				ut := URL{u: u, URL: tc.url}
				require.Equal(t, tc.expected, ut.Clean())
			})
		}
	})
}

func TestMakeRequestTimeout(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(100 * time.Millisecond)
	}))
	defer srv.Close()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	samples := make(chan stats.SampleContainer, 10)
	logger := logrus.New()
	logger.Level = logrus.DebugLevel
	state := &lib.State{
		Options: lib.Options{
			RunTags:    &stats.SampleTags{},
			SystemTags: &stats.DefaultSystemTagSet,
		},
		Transport: srv.Client().Transport,
		Samples:   samples,
		Logger:    logger,
	}
	ctx = lib.WithState(ctx, state)
	req, _ := http.NewRequest("GET", srv.URL, nil)
	var preq = &ParsedHTTPRequest{
		Req:     req,
		URL:     &URL{u: req.URL, URL: srv.URL},
		Body:    new(bytes.Buffer),
		Timeout: 10 * time.Millisecond,
	}

	res, err := MakeRequest(ctx, preq)
	require.NoError(t, err)
	assert.NotNil(t, res)
	assert.Len(t, samples, 1)
	sampleCont := <-samples
	allSamples := sampleCont.GetSamples()
	require.Len(t, allSamples, 8)
	expTags := map[string]string{
		"error":      "context deadline exceeded",
		"error_code": "1000",
		"status":     "0",
		"method":     "GET",
		"url":        srv.URL,
		"name":       srv.URL,
	}
	for _, s := range allSamples {
		assert.Equal(t, expTags, s.Tags.CloneTags())
	}
}

func BenchmarkWrapDecompressionError(b *testing.B) {
	err := errors.New("error")
	b.ResetTimer()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = wrapDecompressionError(err)
	}
}
<commit_msg>Fix linter<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 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 httpext

import (
	"bytes"
	"context"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
)

type reader func([]byte) (int, error)

func (r reader) Read(a []byte) (int, error) {
	return ((func([]byte) (int, error))(r))(a)
}

const (
	badReadMsg  = "bad read error for test"
	badCloseMsg = "bad close error for test"
)

func badReadBody() io.Reader {
	return reader(func(_ []byte) (int, error) {
		return 0, errors.New(badReadMsg)
	})
}

type closer func() error

func (c closer) Close() error {
	return ((func() error)(c))()
}

func badCloseBody() io.ReadCloser {
	return struct {
		io.Reader
		io.Closer
	}{
		Reader: reader(func(_ []byte) (int, error) {
			return 0, io.EOF
		}),
		Closer: closer(func() error {
			return errors.New(badCloseMsg)
		}),
	}
}

func TestCompressionBodyError(t *testing.T) {
	algos := []CompressionType{CompressionTypeGzip}
	t.Run("bad read body", func(t *testing.T) {
		_, _, err := compressBody(algos, ioutil.NopCloser(badReadBody()))
		require.Error(t, err)
		require.Equal(t, err.Error(), badReadMsg)
	})

	t.Run("bad close body", func(t *testing.T) {
		_, _, err := compressBody(algos, badCloseBody())
		require.Error(t, err)
		require.Equal(t, err.Error(), badCloseMsg)
	})
}

func TestMakeRequestError(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	t.Run("bad compression algorithm body", func(t *testing.T) {
		req, err := http.NewRequest("GET", "https://wont.be.used", nil)

		require.NoError(t, err)
		badCompressionType := CompressionType(13)
		require.False(t, badCompressionType.IsACompressionType())
		preq := &ParsedHTTPRequest{
			Req:          req,
			Body:         new(bytes.Buffer),
			Compressions: []CompressionType{badCompressionType},
		}
		_, err = MakeRequest(ctx, preq)
		require.Error(t, err)
		require.Equal(t, err.Error(), "unknown compressionType CompressionType(13)")
	})

	t.Run("invalid upgrade response", func(t *testing.T) {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Connection", "Upgrade")
			w.Header().Add("Upgrade", "h2c")
			w.WriteHeader(http.StatusSwitchingProtocols)
		}))
		defer srv.Close()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		logger := logrus.New()
		logger.Level = logrus.DebugLevel
		state := &lib.State{
			Options:   lib.Options{RunTags: &stats.SampleTags{}},
			Transport: srv.Client().Transport,
			Logger:    logger,
		}
		ctx = lib.WithState(ctx, state)
		req, _ := http.NewRequest("GET", srv.URL, nil)
		preq := &ParsedHTTPRequest{
			Req:     req,
			URL:     &URL{u: req.URL},
			Body:    new(bytes.Buffer),
			Timeout: 10 * time.Second,
		}

		res, err := MakeRequest(ctx, preq)

		assert.Nil(t, res)
		assert.EqualError(t, err, "unsupported response status: 101 Switching Protocols")
	})
}

func TestResponseStatus(t *testing.T) {
	testCases := []struct {
		name                     string
		statusCode               int
		statusCodeExpected       int
		statusCodeStringExpected string
	}{
		{"status 200", 200, 200, "200 OK"},
		{"status 201", 201, 201, "201 Created"},
		{"status 202", 202, 202, "202 Accepted"},
		{"status 203", 203, 203, "203 Non-Authoritative Information"},
		{"status 204", 204, 204, "204 No Content"},
		{"status 205", 205, 205, "205 Reset Content"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				//_, err := w.Write([]byte("some body"))
				//require.NoError(t, err)
				w.WriteHeader(tc.statusCode)
			}))
			defer server.Close()
			logger := logrus.New()
			logger.Level = logrus.DebugLevel
			state := &lib.State{
				Options:   lib.Options{RunTags: &stats.SampleTags{}},
				Transport: server.Client().Transport,
				Logger:    logger,
				Samples:   make(chan<- stats.SampleContainer, 1),
			}
			ctx := lib.WithState(context.Background(), state)
			req, err := http.NewRequest("GET", server.URL, nil)
			require.NoError(t, err)

			preq := &ParsedHTTPRequest{
				Req:          req,
				URL:          &URL{u: req.URL},
				Body:         new(bytes.Buffer),
				Timeout:      10 * time.Second,
				ResponseType: ResponseTypeNone,
			}

			res, err := MakeRequest(ctx, preq)
			require.NoError(t, err)
			assert.Equal(t, tc.statusCodeExpected, res.Status)
			assert.Equal(t, tc.statusCodeStringExpected, res.StatusText)
		})
	}
}

func TestURL(t *testing.T) {
	t.Run("Clean", func(t *testing.T) {
		testCases := []struct {
			url      string
			expected string
		}{
			{"https://example.com/", "https://example.com/"},
			{"https://example.com/${}", "https://example.com/${}"},
			{"https://user@example.com/", "https://****@example.com/"},
			{"https://user:pass@example.com/", "https://****:****@example.com/"},
			{"https://user:pass@example.com/path?a=1&b=2", "https://****:****@example.com/path?a=1&b=2"},
			{"https://user:pass@example.com/${}/${}", "https://****:****@example.com/${}/${}"},
			{"@malformed/url", "@malformed/url"},
			{"not a url", "not a url"},
		}

		for _, tc := range testCases {
			tc := tc
			t.Run(tc.url, func(t *testing.T) {
				u, err := url.Parse(tc.url)
				require.NoError(t, err)
				ut := URL{u: u, URL: tc.url}
				require.Equal(t, tc.expected, ut.Clean())
			})
		}
	})
}

func TestMakeRequestTimeout(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(100 * time.Millisecond)
	}))
	defer srv.Close()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	samples := make(chan stats.SampleContainer, 10)
	logger := logrus.New()
	logger.Level = logrus.DebugLevel
	state := &lib.State{
		Options: lib.Options{
			RunTags:    &stats.SampleTags{},
			SystemTags: &stats.DefaultSystemTagSet,
		},
		Transport: srv.Client().Transport,
		Samples:   samples,
		Logger:    logger,
	}
	ctx = lib.WithState(ctx, state)
	req, _ := http.NewRequest("GET", srv.URL, nil)
	preq := &ParsedHTTPRequest{
		Req:     req,
		URL:     &URL{u: req.URL, URL: srv.URL},
		Body:    new(bytes.Buffer),
		Timeout: 10 * time.Millisecond,
	}

	res, err := MakeRequest(ctx, preq)
	require.NoError(t, err)
	assert.NotNil(t, res)
	assert.Len(t, samples, 1)
	sampleCont := <-samples
	allSamples := sampleCont.GetSamples()
	require.Len(t, allSamples, 8)
	expTags := map[string]string{
		"error":      "context deadline exceeded",
		"error_code": "1000",
		"status":     "0",
		"method":     "GET",
		"url":        srv.URL,
		"name":       srv.URL,
	}
	for _, s := range allSamples {
		assert.Equal(t, expTags, s.Tags.CloneTags())
	}
}

func BenchmarkWrapDecompressionError(b *testing.B) {
	err := errors.New("error")
	b.ResetTimer()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = wrapDecompressionError(err)
	}
}
<|endoftext|>


<commit_before>package stacktrace

import "runtime"

// Caputure captures a stacktrace for the current calling go program
//
// skip is the number of frames to skip
func Capture(userSkip int) Stacktrace {
	var (
		skip   = userSkip + 1 // add one for our own function
		frames []Frame
	)
	for i := skip; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		frames = append(frames, NewFrame(pc, file, line))
	}
	return Stacktrace{
		Frames: frames,
	}
}
<commit_msg>avoid infinite loop with GCCGO<commit_after>package stacktrace

import "runtime"

// Caputure captures a stacktrace for the current calling go program
//
// skip is the number of frames to skip
func Capture(userSkip int) Stacktrace {
	var (
		skip   = userSkip + 1 // add one for our own function
		frames []Frame
		prevPc uintptr = 0
	)
	for i := skip; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		//detect if caller is repeated to avoid loop, gccgo
		//currently runs  into a loop without this check
		if !ok || pc == prevPc {
			break
		}
		frames = append(frames, NewFrame(pc, file, line))
		prevPc = pc
	}
	return Stacktrace{
		Frames: frames,
	}
}
<|endoftext|>


<commit_before>// A set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
//
// The following is a complete example using assert in a standard test function:
//    import (
//      "testing"
//      "assert"
//    )
//
//    func TestSomething(t *testing.T) {
//
//      var a string = "Hello"
//      var b string = "Hello"
//
//      assert.Equal(t, a, b, "The two words should be the same.")
//
//    }
//
// Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
// testing framework. This allows the assertion funcs to write the failings and other details to
// the correct place.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
//
// For example, here is the method signature for assert.Equal:
//
//     assert.Equal(t, expected, actual [, message])
package assert
<commit_msg>added assert function cheat sheet to doc<commit_after>// A set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
//
// The following is a complete example using assert in a standard test function:
//    import (
//      "testing"
//      "assert"
//    )
//
//    func TestSomething(t *testing.T) {
//
//      var a string = "Hello"
//      var b string = "Hello"
//
//      assert.Equal(t, a, b, "The two words should be the same.")
//
//    }
//
// Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
// testing framework. This allows the assertion funcs to write the failings and other details to
// the correct place.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
//
// Here is an overview of the assert functions:
//
//    assert.Equal(t, expected, actual [, message])
//
//    assert.NotEqual(t, notExpected, actual [, message])
//
//    assert.True(t, actualBool [, message])
//
//    assert.False(t, actualBool [, message])
//
//    assert.Nil(t, actualObject [, message])
//
//    assert.NotNil(t, actualObject [, message])
//
//    assert.Implements(t, (*MyInterface)(nil), new(MyObject) [,message])
//
//    assert.IsType(t, expectedObject, actualObject [, message])
//
//    assert.Contains(t, string, substring [, message])
//
//    assert.NotContains(t, string, substring [, message])
//
//    assert.Panics(t, func(){
//
//	    // call code that should panic
//
//    } [, message])
//
//    assert.NotPanics(t, func(){
//
//	    // call code that should not panic
//
//    } [, message])
package assert
<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

func main() {
	first := make([]byte, 5, 15)
	second := make([]byte, 3)
	var third []bool
	fourth := []int{1, 2, 3, 4, 5, 6}

	fmt.Printf("first  len(%d) cap(%d) = %v\n", len(first), cap(first),  first)   // first  len(5) cap(15) = [0 0 0 0 0]
	fmt.Printf("second len(%d) cap(%d) = %v\n", len(second), cap(second), second) // second len(7) cap(3)  = [0 0 0]
	fmt.Printf("third  len(%d) cap(%d) = %v\n", len(third), cap(third),  third)   // third  len(0) cap(0)  = []
	fmt.Printf("fourth len(%d) cap(%d) = %v\n", len(fourth), cap(fourth), fourth) // fourth len(6) cap(6) = [1 2 3 4 5 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	part1 := fourth[:2]
	part2 := fourth[2:4]
	part3 := fourth[4:]
	part4 := fourth[:]

	fmt.Printf("part1 len(%d) cap(%d) = %v\n", len(part1), cap(part1), part1) // part1 len(2) cap(6) = [1 2]
	fmt.Printf("part2 len(%d) cap(%d) = %v\n", len(part2), cap(part2), part2) // part2 len(2) cap(4) = [3 4]
	fmt.Printf("part3 len(%d) cap(%d) = %v\n", len(part3), cap(part3), part3) // part3 len(2) cap(2) = [5 6]
	fmt.Printf("part4 len(%d) cap(%d) = %v\n", len(part4), cap(part4), part4) // part4 len(6) cap(6) = [1 2 3 4 5 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	part1[0] = 100
	part2[0] = 300
	part3[0] = 500

	fmt.Println("fourth = ", fourth) // fourth = [100 2 300 4 500 6]
	fmt.Println("part4  = ", part4)  // part4  = [100 2 300 4 500 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	copyOfFourth := make([]int, 6)
	count := copy(copyOfFourth, fourth)
	fourth = []int {1, 2, 3, 4, 5, 6}

	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6]
	fmt.Println("copyOfFourth  = ", copyOfFourth)  // copyOfFourth  = [100 2 300 4 500 6]
	fmt.Println("count         = ", count)         // count         = 6

	fmt.Println("\n------------------------------------------------------------------\n")

	fourth = append(fourth, copyOfFourth[:3]...)

	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6 100 2 300]
	fmt.Println("copyOfFourth  = ", copyOfFourth)  // copyOfFourth  = [100 2 300 4 500 6]

	fourth = append(fourth, 111, 222, 333)
	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6 100 2 300 111 222 333]

	fmt.Println("\n------------------------------------------------------------------\n")

	fmt.Printf("fourth len(%d) cap(%d)\n", len(fourth), cap(fourth))
	fourth = append(fourth, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	fmt.Printf("fourth len(%d) cap(%d)\n", len(fourth), cap(fourth))
}

<commit_msg>fix bug in lesson<commit_after>package main

import (
	"fmt"
)

func main() {
	first := make([]byte, 5, 15)
	second := make([]byte, 3)
	var third []bool
	fourth := []int{1, 2, 3, 4, 5, 6}

	fmt.Printf("first  len(%d) cap(%d) = %v\n", len(first), cap(first),  first)   // first  len(5) cap(15) = [0 0 0 0 0]
	fmt.Printf("second len(%d) cap(%d) = %v\n", len(second), cap(second), second) // second len(7) cap(3)  = [0 0 0]
	fmt.Printf("third  len(%d) cap(%d) = %v\n", len(third), cap(third),  third)   // third  len(0) cap(0)  = []
	fmt.Printf("fourth len(%d) cap(%d) = %v\n", len(fourth), cap(fourth), fourth) // fourth len(6) cap(6)  = [1 2 3 4 5 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	part1 := fourth[:2]
	part2 := fourth[2:4]
	part3 := fourth[4:]
	part4 := fourth[:]

	fmt.Printf("part1 len(%d) cap(%d) = %v\n", len(part1), cap(part1), part1) // part1 len(2) cap(6) = [1 2]
	fmt.Printf("part2 len(%d) cap(%d) = %v\n", len(part2), cap(part2), part2) // part2 len(2) cap(4) = [3 4]
	fmt.Printf("part3 len(%d) cap(%d) = %v\n", len(part3), cap(part3), part3) // part3 len(2) cap(2) = [5 6]
	fmt.Printf("part4 len(%d) cap(%d) = %v\n", len(part4), cap(part4), part4) // part4 len(6) cap(6) = [1 2 3 4 5 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	part1[0] = 100
	part2[0] = 300
	part3[0] = 500

	fmt.Println("fourth = ", fourth) // fourth = [100 2 300 4 500 6]
	fmt.Println("part4  = ", part4)  // part4  = [100 2 300 4 500 6]

	fmt.Println("\n------------------------------------------------------------------\n")

	copyOfFourth := make([]int, 6)
	count := copy(copyOfFourth, fourth)
	fourth = []int {1, 2, 3, 4, 5, 6}

	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6]
	fmt.Println("copyOfFourth  = ", copyOfFourth)  // copyOfFourth  = [100 2 300 4 500 6]
	fmt.Println("count         = ", count)         // count         = 6

	fmt.Println("\n------------------------------------------------------------------\n")

	fourth = append(fourth, copyOfFourth[:3]...)

	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6 100 2 300]
	fmt.Println("copyOfFourth  = ", copyOfFourth)  // copyOfFourth  = [100 2 300 4 500 6]

	fourth = append(fourth, 111, 222, 333)
	fmt.Println("fourth        = ", fourth)        // fourth        = [1 2 3 4 5 6 100 2 300 111 222 333]

	fmt.Println("\n------------------------------------------------------------------\n")

	fmt.Printf("fourth len(%d) cap(%d)\n", len(fourth), cap(fourth))
	fourth = append(fourth, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	fmt.Printf("fourth len(%d) cap(%d)\n", len(fourth), cap(fourth))
}

<|endoftext|>


<commit_before>package libp2p

// This file contains all the default configuration options.

import (
	"crypto/rand"

	crypto "github.com/libp2p/go-libp2p-crypto"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	secio "github.com/libp2p/go-libp2p-secio"
	tcp "github.com/libp2p/go-tcp-transport"
	ws "github.com/libp2p/go-ws-transport"
	multiaddr "github.com/multiformats/go-multiaddr"
	mplex "github.com/whyrusleeping/go-smux-multiplex"
	yamux "github.com/whyrusleeping/go-smux-yamux"
)

// DefaultSecurity is the default security option.
//
// Useful when you want to extend, but not replace, the supported transport
// security protocols.
var DefaultSecurity = Security(secio.ID, secio.New)

// DefaultMuxers configures libp2p to use the stream connection multiplexers.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultMuxers = ChainOptions(
	Muxer("/yamux/1.0.0", yamux.DefaultTransport),
	Muxer("/mplex/6.3.0", mplex.DefaultTransport),
)

// DefaultTransports are the default libp2p transports.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultTransports = ChainOptions(
	Transport(tcp.NewTCPTransport),
	Transport(ws.New),
)

// DefaultPeerstore configures libp2p to use the default peerstore.
var DefaultPeerstore Option = func(cfg *Config) error {
	return cfg.Apply(Peerstore(pstore.NewPeerstore()))
}

// RandomIdentity generates a random identity (default behaviour)
var RandomIdentity = func(cfg *Config) error {
	priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader)
	if err != nil {
		return err
	}
	return cfg.Apply(Identity(priv))
}

// DefaultListenAddrs configures libp2p to use default listen address
var DefaultListenAddrs = func(cfg *Config) error {
	defaultIP4ListenAddr, err := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/0")
	if err != nil {
		return err
	}

	defaultIP6ListenAddr, err := multiaddr.NewMultiaddr("/ip6/::/tcp/0")
	if err != nil {
		return err
	}
	return cfg.Apply(ListenAddrs(
		defaultIP4ListenAddr,
		defaultIP6ListenAddr,
	))
}

// Complete list of default options and when to fallback on them.
//
// Please *DON'T* specify default options any other way. Putting this all here
// makes tracking defaults *much* easier.
var defaults = []struct {
	fallback func(cfg *Config) bool
	opt      Option
}{
	{
		fallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.ListenAddrs == nil },
		opt:      DefaultListenAddrs,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Transports == nil },
		opt:      DefaultTransports,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Muxers == nil },
		opt:      DefaultMuxers,
	},
	{
		fallback: func(cfg *Config) bool { return !cfg.Insecure && cfg.SecurityTransports == nil },
		opt:      DefaultSecurity,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.PeerKey == nil },
		opt:      RandomIdentity,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Peerstore == nil },
		opt:      DefaultPeerstore,
	},
}

// Defaults configures libp2p to use the default options. Can be combined with
// other options to *extend* the default options.
var Defaults Option = func(cfg *Config) error {
	for _, def := range defaults {
		if err := cfg.Apply(def.opt); err != nil {
			return err
		}
	}
	return nil
}

// FallbackDefaults applies default options to the libp2p node if and only if no
// other relevent options have been applied. will be appended to the options
// passed into New.
var FallbackDefaults Option = func(cfg *Config) error {
	for _, def := range defaults {
		if !def.fallback(cfg) {
			continue
		}
		if err := cfg.Apply(def.opt); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>[muxer] set mplex version to 6.7.0<commit_after>package libp2p

// This file contains all the default configuration options.

import (
	"crypto/rand"

	crypto "github.com/libp2p/go-libp2p-crypto"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	secio "github.com/libp2p/go-libp2p-secio"
	tcp "github.com/libp2p/go-tcp-transport"
	ws "github.com/libp2p/go-ws-transport"
	multiaddr "github.com/multiformats/go-multiaddr"
	mplex "github.com/whyrusleeping/go-smux-multiplex"
	yamux "github.com/whyrusleeping/go-smux-yamux"
)

// DefaultSecurity is the default security option.
//
// Useful when you want to extend, but not replace, the supported transport
// security protocols.
var DefaultSecurity = Security(secio.ID, secio.New)

// DefaultMuxers configures libp2p to use the stream connection multiplexers.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultMuxers = ChainOptions(
	Muxer("/yamux/1.0.0", yamux.DefaultTransport),
	Muxer("/mplex/6.7.0", mplex.DefaultTransport),
)

// DefaultTransports are the default libp2p transports.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultTransports = ChainOptions(
	Transport(tcp.NewTCPTransport),
	Transport(ws.New),
)

// DefaultPeerstore configures libp2p to use the default peerstore.
var DefaultPeerstore Option = func(cfg *Config) error {
	return cfg.Apply(Peerstore(pstore.NewPeerstore()))
}

// RandomIdentity generates a random identity (default behaviour)
var RandomIdentity = func(cfg *Config) error {
	priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader)
	if err != nil {
		return err
	}
	return cfg.Apply(Identity(priv))
}

// DefaultListenAddrs configures libp2p to use default listen address
var DefaultListenAddrs = func(cfg *Config) error {
	defaultIP4ListenAddr, err := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/0")
	if err != nil {
		return err
	}

	defaultIP6ListenAddr, err := multiaddr.NewMultiaddr("/ip6/::/tcp/0")
	if err != nil {
		return err
	}
	return cfg.Apply(ListenAddrs(
		defaultIP4ListenAddr,
		defaultIP6ListenAddr,
	))
}

// Complete list of default options and when to fallback on them.
//
// Please *DON'T* specify default options any other way. Putting this all here
// makes tracking defaults *much* easier.
var defaults = []struct {
	fallback func(cfg *Config) bool
	opt      Option
}{
	{
		fallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.ListenAddrs == nil },
		opt:      DefaultListenAddrs,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Transports == nil },
		opt:      DefaultTransports,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Muxers == nil },
		opt:      DefaultMuxers,
	},
	{
		fallback: func(cfg *Config) bool { return !cfg.Insecure && cfg.SecurityTransports == nil },
		opt:      DefaultSecurity,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.PeerKey == nil },
		opt:      RandomIdentity,
	},
	{
		fallback: func(cfg *Config) bool { return cfg.Peerstore == nil },
		opt:      DefaultPeerstore,
	},
}

// Defaults configures libp2p to use the default options. Can be combined with
// other options to *extend* the default options.
var Defaults Option = func(cfg *Config) error {
	for _, def := range defaults {
		if err := cfg.Apply(def.opt); err != nil {
			return err
		}
	}
	return nil
}

// FallbackDefaults applies default options to the libp2p node if and only if no
// other relevent options have been applied. will be appended to the options
// passed into New.
var FallbackDefaults Option = func(cfg *Config) error {
	for _, def := range defaults {
		if !def.fallback(cfg) {
			continue
		}
		if err := cfg.Apply(def.opt); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package chardet

import (
	"errors"
	"sort"
)

type Result struct {
	Charset    string
	Language   string
	Confidence int
}

type Detector struct {
	recognizers []recognizer
	stripTag    bool
}

// List of charset recognizers
var recognizers = []recognizer{
	newRecognizer_utf8(),
	newRecognizer_utf16be(),
	newRecognizer_utf16le(),
	newRecognizer_utf32be(),
	newRecognizer_utf32le(),
	newRecognizer_8859_1_en(),
	newRecognizer_8859_1_da(),
	newRecognizer_8859_1_de(),
	newRecognizer_8859_1_es(),
	newRecognizer_8859_1_fr(),
	newRecognizer_8859_1_it(),
	newRecognizer_8859_1_nl(),
	newRecognizer_8859_1_no(),
	newRecognizer_8859_1_pt(),
	newRecognizer_8859_1_sv(),
	newRecognizer_8859_2_cs(),
	newRecognizer_8859_2_hu(),
	newRecognizer_8859_2_pl(),
	newRecognizer_8859_2_ro(),
	newRecognizer_8859_5_ru(),
	newRecognizer_8859_6_ar(),
	newRecognizer_8859_7_el(),
	newRecognizer_8859_8_I_he(),
	newRecognizer_8859_8_he(),
	newRecognizer_windows_1251(),
	newRecognizer_windows_1256(),
	newRecognizer_KOI8_R(),
	newRecognizer_8859_9_tr(),

	newRecognizer_sjis(),
	newRecognizer_gb_18030(),
	newRecognizer_euc_jp(),
	newRecognizer_euc_kr(),
	newRecognizer_big5(),

	newRecognizer_2022JP(),
	newRecognizer_2022KR(),
	newRecognizer_2022CN(),

	newRecognizer_IBM424_he_rtl(),
	newRecognizer_IBM424_he_ltr(),
	newRecognizer_IBM420_ar_rtl(),
	newRecognizer_IBM420_ar_ltr(),
}

func NewTextDetector() *Detector {
	return &Detector{recognizers, false}
}

func NewHtmlDetector() *Detector {
	return &Detector{recognizers, true}
}

var (
	NotDetectedError = errors.New("Charset not detected.")
)

func (d *Detector) DetectBest(b []byte) (r *Result, err error) {
	var all []Result
	if all, err = d.DetectAll(b); err == nil {
		r = &all[0]
	}
	return
}

func (d *Detector) DetectAll(b []byte) ([]Result, error) {
	input := newRecognizerInput(b, d.stripTag)
	outputChan := make(chan recognizerOutput)
	for _, r := range d.recognizers {
		go matchHelper(r, input, outputChan)
	}
	outputs := make([]recognizerOutput, 0, len(d.recognizers))
	for i := 0; i < len(d.recognizers); i++ {
		o := <-outputChan
		if o.Confidence > 0 {
			outputs = append(outputs, o)
		}
	}
	if len(outputs) == 0 {
		return nil, NotDetectedError
	}

	sort.Sort(recognizerOutputs(outputs))
	dedupOutputs := make([]Result, 0, len(outputs))
	foundCharsets := make(map[string]struct{}, len(outputs))
	for _, o := range outputs {
		if _, found := foundCharsets[o.Charset]; !found {
			dedupOutputs = append(dedupOutputs, Result(o))
			foundCharsets[o.Charset] = struct{}{}
		}
	}
	if len(dedupOutputs) == 0 {
		return nil, NotDetectedError
	}
	return dedupOutputs, nil
}

func matchHelper(r recognizer, input *recognizerInput, outputChan chan<- recognizerOutput) {
	outputChan <- r.Match(input)
}

type recognizerOutputs []recognizerOutput

func (r recognizerOutputs) Len() int           { return len(r) }
func (r recognizerOutputs) Less(i, j int) bool { return r[i].Confidence > r[j].Confidence }
func (r recognizerOutputs) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
<commit_msg>Add documentation for public api<commit_after>// Package chardet ports character set detection from ICU.
package chardet

import (
	"errors"
	"sort"
)

// Result contains all the information that charset detector gives.
type Result struct {
	// IANA name of the detected charset.
	Charset string
	// IANA name of the detected language. It may be empty for some charsets.
	Language string
	// Confidence of the Result. Scale from 1 to 100. The bigger, the more confident.
	Confidence int
}

// Detector implements charset detection.
type Detector struct {
	recognizers []recognizer
	stripTag    bool
}

// List of charset recognizers
var recognizers = []recognizer{
	newRecognizer_utf8(),
	newRecognizer_utf16be(),
	newRecognizer_utf16le(),
	newRecognizer_utf32be(),
	newRecognizer_utf32le(),
	newRecognizer_8859_1_en(),
	newRecognizer_8859_1_da(),
	newRecognizer_8859_1_de(),
	newRecognizer_8859_1_es(),
	newRecognizer_8859_1_fr(),
	newRecognizer_8859_1_it(),
	newRecognizer_8859_1_nl(),
	newRecognizer_8859_1_no(),
	newRecognizer_8859_1_pt(),
	newRecognizer_8859_1_sv(),
	newRecognizer_8859_2_cs(),
	newRecognizer_8859_2_hu(),
	newRecognizer_8859_2_pl(),
	newRecognizer_8859_2_ro(),
	newRecognizer_8859_5_ru(),
	newRecognizer_8859_6_ar(),
	newRecognizer_8859_7_el(),
	newRecognizer_8859_8_I_he(),
	newRecognizer_8859_8_he(),
	newRecognizer_windows_1251(),
	newRecognizer_windows_1256(),
	newRecognizer_KOI8_R(),
	newRecognizer_8859_9_tr(),

	newRecognizer_sjis(),
	newRecognizer_gb_18030(),
	newRecognizer_euc_jp(),
	newRecognizer_euc_kr(),
	newRecognizer_big5(),

	newRecognizer_2022JP(),
	newRecognizer_2022KR(),
	newRecognizer_2022CN(),

	newRecognizer_IBM424_he_rtl(),
	newRecognizer_IBM424_he_ltr(),
	newRecognizer_IBM420_ar_rtl(),
	newRecognizer_IBM420_ar_ltr(),
}

// NewTextDetector creates a Detector for plain text.
func NewTextDetector() *Detector {
	return &Detector{recognizers, false}
}

// NewHtmlDetector creates a Detector for Html.
func NewHtmlDetector() *Detector {
	return &Detector{recognizers, true}
}

var (
	NotDetectedError = errors.New("Charset not detected.")
)

// DetectBest returns the Result with highest Confidence.
func (d *Detector) DetectBest(b []byte) (r *Result, err error) {
	var all []Result
	if all, err = d.DetectAll(b); err == nil {
		r = &all[0]
	}
	return
}

// DetectAll returns all Results which have non-zero Confidence. The Results are sorted by Confidence in descending order.
func (d *Detector) DetectAll(b []byte) ([]Result, error) {
	input := newRecognizerInput(b, d.stripTag)
	outputChan := make(chan recognizerOutput)
	for _, r := range d.recognizers {
		go matchHelper(r, input, outputChan)
	}
	outputs := make([]recognizerOutput, 0, len(d.recognizers))
	for i := 0; i < len(d.recognizers); i++ {
		o := <-outputChan
		if o.Confidence > 0 {
			outputs = append(outputs, o)
		}
	}
	if len(outputs) == 0 {
		return nil, NotDetectedError
	}

	sort.Sort(recognizerOutputs(outputs))
	dedupOutputs := make([]Result, 0, len(outputs))
	foundCharsets := make(map[string]struct{}, len(outputs))
	for _, o := range outputs {
		if _, found := foundCharsets[o.Charset]; !found {
			dedupOutputs = append(dedupOutputs, Result(o))
			foundCharsets[o.Charset] = struct{}{}
		}
	}
	if len(dedupOutputs) == 0 {
		return nil, NotDetectedError
	}
	return dedupOutputs, nil
}

func matchHelper(r recognizer, input *recognizerInput, outputChan chan<- recognizerOutput) {
	outputChan <- r.Match(input)
}

type recognizerOutputs []recognizerOutput

func (r recognizerOutputs) Len() int           { return len(r) }
func (r recognizerOutputs) Less(i, j int) bool { return r[i].Confidence > r[j].Confidence }
func (r recognizerOutputs) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
<|endoftext|>


<commit_before>package dispatcher_test

import (
	"bytes"
	"errors"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "bosh-google-cpi/api/dispatcher"

	boshlog "github.com/cloudfoundry/bosh-utils/logger"

	fakeaction "bosh-google-cpi/action/fakes"
	bgcapi "bosh-google-cpi/api"
	fakedisp "bosh-google-cpi/api/dispatcher/fakes"
	fakeapi "bosh-google-cpi/api/fakes"
)

var _ = Describe("JSON", func() {
	var (
		actionFactory *fakeaction.FakeFactory
		caller        *fakedisp.FakeCaller
		logger        boshlog.Logger
		dispatcher    JSON
	)

	BeforeEach(func() {
		actionFactory = fakeaction.NewFakeFactory()
		caller = &fakedisp.FakeCaller{}
		logger = boshlog.NewLogger(boshlog.LevelNone)
		dispatcher = NewJSON(actionFactory, caller, bgcapi.MultiLogger{Logger: logger, LogBuff: &bytes.Buffer{}})
	})

	Describe("Dispatch", func() {
		Context("when method is known", func() {
			var (
				action *fakeaction.FakeAction
			)

			BeforeEach(func() {
				action = &fakeaction.FakeAction{}
				actionFactory.RegisterAction("fake-action", action)
			})

			It("runs action with provided arguments", func() {
				dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
				Expect(caller.CallAction).To(Equal(action))
				Expect(caller.CallArgs).To(Equal([]interface{}{"fake-arg"}))

				dispatcher.Dispatch([]byte(`{
          "method":"fake-action",
          "arguments":[
            123,
            "fake-arg",
            [123, "fake-arg"],
            {"fake-arg2-key":"fake-arg2-value"}
          ]
        }`))
				Expect(caller.CallAction).To(Equal(action))
				Expect(caller.CallArgs).To(Equal([]interface{}{
					float64(123),
					"fake-arg",
					[]interface{}{float64(123), "fake-arg"},
					map[string]interface{}{"fake-arg2-key": "fake-arg2-value"},
				}))
			})

			Context("when running action succeeds", func() {
				Context("when result can be serialized", func() {
					BeforeEach(func() {
						caller.CallResult = "fake-result"
					})

					It("returns serialized result without including error", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": "fake-result",
							"error": null,
							"log": ""
						}`))
					})
				})

				Context("when result cannot be serialized", func() {
					BeforeEach(func() {
						caller.CallResult = func() {} // funcs do not serialize
					})

					It("returns Bosh::Clouds::CpiError", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CpiError",
                "message":"Failed to serialize result",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})
			})

			Context("when running action fails", func() {
				Context("when action error is a CloudError", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeCloudError("fake-type", "fake-message")
					})

					It("returns error without result", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"fake-type",
                "message":"fake-message",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is a RetryableError and it can be retried", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeRetryableError("fake-error", true)
					})

					It("returns error with ok_to_retry set to true", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-error",
                "ok_to_retry": true
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is a RetryableError and it cannot be retried", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeRetryableError("fake-error", false)
					})

					It("returns error with ok_to_retry set to false", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-error",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is neither CloudError or RetryableError", func() {
					BeforeEach(func() {
						caller.CallErr = errors.New("fake-run-err")
					})

					It("returns error without result", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-run-err",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})
			})
		})

		Context("when method is unknown", func() {
			It("responds with Bosh::Clouds::NotImplemented error", func() {
				response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":[]}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::NotImplemented",
            "message":"Must call implemented method",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when method key is missing", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide method key",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when arguments key is missing", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{"method":"fake-action"}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide arguments key",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when payload cannot be deserialized", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{-}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide valid JSON payload",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})
	})
})
<commit_msg>action: Fix JSON test<commit_after>package dispatcher_test

import (
	"bytes"
	"encoding/json"
	"errors"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "bosh-google-cpi/api/dispatcher"

	boshlog "github.com/cloudfoundry/bosh-utils/logger"

	fakeaction "bosh-google-cpi/action/fakes"
	bgcapi "bosh-google-cpi/api"
	fakedisp "bosh-google-cpi/api/dispatcher/fakes"
	fakeapi "bosh-google-cpi/api/fakes"
)

var _ = Describe("JSON", func() {
	var (
		actionFactory *fakeaction.FakeFactory
		caller        *fakedisp.FakeCaller
		logger        boshlog.Logger
		dispatcher    JSON
	)

	BeforeEach(func() {
		actionFactory = fakeaction.NewFakeFactory()
		caller = &fakedisp.FakeCaller{}
		logger = boshlog.NewLogger(boshlog.LevelNone)
		dispatcher = NewJSON(actionFactory, caller, bgcapi.MultiLogger{Logger: logger, LogBuff: &bytes.Buffer{}})
	})

	Describe("Dispatch", func() {
		Context("when method is known", func() {
			var (
				action *fakeaction.FakeAction
			)

			BeforeEach(func() {
				action = &fakeaction.FakeAction{}
				actionFactory.RegisterAction("fake-action", action)
			})

			It("runs action with provided arguments", func() {
				dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
				Expect(caller.CallAction).To(Equal(action))
				Expect(caller.CallArgs).To(Equal([]interface{}{"fake-arg"}))

				dispatcher.Dispatch([]byte(`{
          "method":"fake-action",
          "arguments":[
            123,
            "fake-arg",
            [123, "fake-arg"],
            {"fake-arg2-key":"fake-arg2-value"}
          ]
        }`))
				Expect(caller.CallAction).To(Equal(action))
				Expect(caller.CallArgs).To(Equal([]interface{}{
					json.Number("123"),
					"fake-arg",
					[]interface{}{json.Number("123"), "fake-arg"},
					map[string]interface{}{"fake-arg2-key": "fake-arg2-value"},
				}))
			})

			Context("when running action succeeds", func() {
				Context("when result can be serialized", func() {
					BeforeEach(func() {
						caller.CallResult = "fake-result"
					})

					It("returns serialized result without including error", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": "fake-result",
							"error": null,
							"log": ""
						}`))
					})
				})

				Context("when result cannot be serialized", func() {
					BeforeEach(func() {
						caller.CallResult = func() {} // funcs do not serialize
					})

					It("returns Bosh::Clouds::CpiError", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CpiError",
                "message":"Failed to serialize result",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})
			})

			Context("when running action fails", func() {
				Context("when action error is a CloudError", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeCloudError("fake-type", "fake-message")
					})

					It("returns error without result", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"fake-type",
                "message":"fake-message",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is a RetryableError and it can be retried", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeRetryableError("fake-error", true)
					})

					It("returns error with ok_to_retry set to true", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-error",
                "ok_to_retry": true
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is a RetryableError and it cannot be retried", func() {
					BeforeEach(func() {
						caller.CallErr = fakeapi.NewFakeRetryableError("fake-error", false)
					})

					It("returns error with ok_to_retry set to false", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-error",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})

				Context("when action error is neither CloudError or RetryableError", func() {
					BeforeEach(func() {
						caller.CallErr = errors.New("fake-run-err")
					})

					It("returns error without result", func() {
						response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":["fake-arg"]}`))
						Expect(response).To(MatchJSON(`{
							"result": null,
              "error": {
                "type":"Bosh::Clouds::CloudError",
                "message":"fake-run-err",
                "ok_to_retry": false
              },
              "log": ""
            }`))
					})
				})
			})
		})

		Context("when method is unknown", func() {
			It("responds with Bosh::Clouds::NotImplemented error", func() {
				response := dispatcher.Dispatch([]byte(`{"method":"fake-action","arguments":[]}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::NotImplemented",
            "message":"Must call implemented method",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when method key is missing", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide method key",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when arguments key is missing", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{"method":"fake-action"}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide arguments key",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})

		Context("when payload cannot be deserialized", func() {
			It("responds with Bosh::Clouds::CpiError error", func() {
				response := dispatcher.Dispatch([]byte(`{-}`))
				Expect(response).To(MatchJSON(`{
					"result": null,
          "error": {
            "type":"Bosh::Clouds::CpiError",
            "message":"Must provide valid JSON payload",
            "ok_to_retry": false
          },
          "log": ""
        }`))
			})
		})
	})
})
<|endoftext|>


<commit_before>package GoSDK

import (
	"errors"
	"fmt"
	"log"
	"strings"
)

const (
	_DEV_HEADER_KEY = "ClearBlade-DevToken"
	_DEV_PREAMBLE   = "/admin"
)

type System struct {
	Key         string
	Secret      string
	Name        string
	Description string
	Users       bool
}

func (d *DevClient) NewSystem(name, description string, users bool) (string, error) {
	creds, err := d.credentials()
	if err != nil {
		return "", err
	}
	resp, err := post("/admin/systemmanagement", map[string]interface{}{
		"name":          name,
		"description":   description,
		"auth_required": users,
	}, creds)
	if err != nil {
		return "", fmt.Errorf("Error creating new system: %v", err)
	}
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("Error Creating new system: %v", resp.Body)
	}

	// TODO we need to make this json
	return strings.TrimSpace(strings.Split(resp.Body.(string), ":")[1]), nil
}

func (d *DevClient) GetSystem(key string) (*System, error) {
	creds, err := d.credentials()
	if err != nil {
		return &System{}, err
	} else if len(creds) != 1 {
		return nil, fmt.Errorf("Error getting system: No DevToken Supplied")
	}
	sysResp, sysErr := get("/admin/systemmanagement", map[string]string{"id": key}, creds)
	if sysErr != nil {
		return nil, fmt.Errorf("Error gathering system information: %v", sysErr)
	}
	if sysResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error gathering system information: %v", sysResp.Body)
	}
	sysMap, isMap := sysResp.Body.(map[string]interface{})
	if !isMap {
		return nil, fmt.Errorf("Error gathering system information: incorrect return type\n")
	}
	newSys := &System{
		Key:         sysMap["appID"].(string),
		Secret:      sysMap["appSecret"].(string),
		Name:        sysMap["name"].(string),
		Description: sysMap["description"].(string),
		Users:       sysMap["auth_required"].(bool),
	}
	return newSys, nil

}

func (d *DevClient) DeleteSystem(s string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := delete("/admin/systemmanagement", map[string]string{"id": s}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting system: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting system: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemName(system_key, system_name string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":   system_key,
		"name": system_name,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system name: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system name: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemDescription(system_key, system_description string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":          system_key,
		"description": system_description,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system description: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system description: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemAuthOn(system_key string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":            system_key,
		"auth_required": true,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system auth: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system auth: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemAuthOff(system_key string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":            system_key,
		"auth_required": false,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system auth: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system auth: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) DevUserInfo() error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := get("/admin/userinfo", nil, creds)
	if err != nil {
		return fmt.Errorf("Error getting userdata: %v", err)
	}
	log.Printf("HERE IS THE BODY: %+v\n", resp)
	return nil
}

func (d *DevClient) NewCollection(systemKey, name string) (string, error) {
	creds, err := d.credentials()
	if err != nil {
		return "", err
	}
	resp, err := post("/admin/collectionmanagement", map[string]interface{}{
		"name":  name,
		"appID": systemKey,
	}, creds)
	if err != nil {
		return "", fmt.Errorf("Error creating collection: %v", err)
	}
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("Error creating collection %v", resp.Body)
	}
	return resp.Body.(map[string]interface{})["collectionID"].(string), nil
}

func (d *DevClient) DeleteCollection(colId string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := delete("/admin/collectionmanagement", map[string]string{
		"id": colId,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting collection %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting collection %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddColumn(collection_id, column_name, column_type string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/collectionmanagement", map[string]interface{}{
		"id": collection_id,
		"addColumn": map[string]interface{}{
			"name": column_name,
			"type": column_type,
		},
	}, creds)
	if err != nil {
		return fmt.Errorf("Error adding column: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error adding column: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) DeleteColumn(collection_id, column_name string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/collectionmanagement", map[string]interface{}{
		"id":           collection_id,
		"deleteColumn": column_name,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting column: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting column: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) GetCollectionInfo(collection_id string) (map[string]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return map[string]interface{}{}, err
	}
	resp, err := get("/admin/collectionmanagement", map[string]string{
		"id": collection_id,
	}, creds)
	if err != nil {
		return nil, fmt.Errorf("Error getting collection info: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Error getting collection info: %v", resp.Body)
	}
	return resp.Body.(map[string]interface{}), nil
}

//get collections list in system
func (d *DevClient) GetAllCollections(SystemKey string) ([]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return nil, err
	}
	resp, err := get("/admin/allcollections", map[string]string{
		"appid": SystemKey,
	}, creds)
	if err != nil {
		return nil, fmt.Errorf("Error getting collection info: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Error getting collection info: %v", resp.Body)
	}

	//fmt.Printf("body: %+v\n", resp.Body)
	return resp.Body.([]interface{}), nil
}

func (d *DevClient) GetAllRoles(SystemKey string) ([]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return nil, err
	}
	resp, err := get("/admin/user/"+SystemKey+"/roles", map[string]string{
		"appid": SystemKey,
	}, creds)
	//fmt.Printf("roles: %+v\n", resp.Body)
	return resp.Body.([]interface{}), nil
}

func (d *DevClient) AddCollectionToRole(systemKey, collection_id, role_id string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"collections": []map[string]interface{}{
				map[string]interface{}{
					"itemInfo": map[string]interface{}{
						"id": collection_id,
					},
					"permissions": level,
				},
			},
			"topics":   []map[string]interface{}{},
			"services": []map[string]interface{}{},
		},
	}
	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a collection: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddServiceToRole(systemKey, service, role_id string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"services": []map[string]interface{}{
				map[string]interface{}{
					"itemInfo": map[string]interface{}{
						"name": service,
					},
					"permissions": level,
				},
			},
			"topics":      []map[string]interface{}{},
			"collections": []map[string]interface{}{},
		},
	}
	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a service: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddGenericRole(systemKey, role_id, permission string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}

	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"services":    []map[string]interface{}{},
			"topics":      []map[string]interface{}{},
			"collections": []map[string]interface{}{},
		},
	}

	data["changes"].(map[string]interface{})[permission] = map[string]interface{}{
		"permissions": level,
	}

	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a service: %v", resp.Body)
	}
	return nil
}

//second verse, same as the first, eh?
func (d *DevClient) credentials() ([][]string, error) {
	if d.DevToken != "" {
		return [][]string{
			[]string{
				_DEV_HEADER_KEY,
				d.DevToken,
			},
		}, nil
	} else {
		return [][]string{}, errors.New("No SystemSecret/SystemKey combo, or UserToken found")
	}
}

func (d *DevClient) preamble() string {
	return _DEV_PREAMBLE
}

func (d *DevClient) getSystemInfo() (string, string) {
	return "", ""
}

func (d *DevClient) setToken(t string) {
	d.DevToken = t
}
func (d *DevClient) getToken() string {
	return d.DevToken
}

func (d *DevClient) getMessageId() uint16 {
	return uint16(d.mrand.Int())
}
<commit_msg>added create role func<commit_after>package GoSDK

import (
	"errors"
	"fmt"
	"log"
	"strings"
)

const (
	_DEV_HEADER_KEY = "ClearBlade-DevToken"
	_DEV_PREAMBLE   = "/admin"
)

type System struct {
	Key         string
	Secret      string
	Name        string
	Description string
	Users       bool
}

func (d *DevClient) NewSystem(name, description string, users bool) (string, error) {
	creds, err := d.credentials()
	if err != nil {
		return "", err
	}
	resp, err := post("/admin/systemmanagement", map[string]interface{}{
		"name":          name,
		"description":   description,
		"auth_required": users,
	}, creds)
	if err != nil {
		return "", fmt.Errorf("Error creating new system: %v", err)
	}
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("Error Creating new system: %v", resp.Body)
	}

	// TODO we need to make this json
	return strings.TrimSpace(strings.Split(resp.Body.(string), ":")[1]), nil
}

func (d *DevClient) GetSystem(key string) (*System, error) {
	creds, err := d.credentials()
	if err != nil {
		return &System{}, err
	} else if len(creds) != 1 {
		return nil, fmt.Errorf("Error getting system: No DevToken Supplied")
	}
	sysResp, sysErr := get("/admin/systemmanagement", map[string]string{"id": key}, creds)
	if sysErr != nil {
		return nil, fmt.Errorf("Error gathering system information: %v", sysErr)
	}
	if sysResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error gathering system information: %v", sysResp.Body)
	}
	sysMap, isMap := sysResp.Body.(map[string]interface{})
	if !isMap {
		return nil, fmt.Errorf("Error gathering system information: incorrect return type\n")
	}
	newSys := &System{
		Key:         sysMap["appID"].(string),
		Secret:      sysMap["appSecret"].(string),
		Name:        sysMap["name"].(string),
		Description: sysMap["description"].(string),
		Users:       sysMap["auth_required"].(bool),
	}
	return newSys, nil

}

func (d *DevClient) DeleteSystem(s string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := delete("/admin/systemmanagement", map[string]string{"id": s}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting system: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting system: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemName(system_key, system_name string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":   system_key,
		"name": system_name,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system name: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system name: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemDescription(system_key, system_description string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":          system_key,
		"description": system_description,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system description: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system description: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemAuthOn(system_key string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":            system_key,
		"auth_required": true,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system auth: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system auth: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) SetSystemAuthOff(system_key string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/systemmanagement", map[string]interface{}{
		"id":            system_key,
		"auth_required": false,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error changing system auth: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error changing system auth: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) DevUserInfo() error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := get("/admin/userinfo", nil, creds)
	if err != nil {
		return fmt.Errorf("Error getting userdata: %v", err)
	}
	log.Printf("HERE IS THE BODY: %+v\n", resp)
	return nil
}

func (d *DevClient) NewCollection(systemKey, name string) (string, error) {
	creds, err := d.credentials()
	if err != nil {
		return "", err
	}
	resp, err := post("/admin/collectionmanagement", map[string]interface{}{
		"name":  name,
		"appID": systemKey,
	}, creds)
	if err != nil {
		return "", fmt.Errorf("Error creating collection: %v", err)
	}
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("Error creating collection %v", resp.Body)
	}
	return resp.Body.(map[string]interface{})["collectionID"].(string), nil
}

func (d *DevClient) DeleteCollection(colId string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := delete("/admin/collectionmanagement", map[string]string{
		"id": colId,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting collection %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting collection %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddColumn(collection_id, column_name, column_type string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/collectionmanagement", map[string]interface{}{
		"id": collection_id,
		"addColumn": map[string]interface{}{
			"name": column_name,
			"type": column_type,
		},
	}, creds)
	if err != nil {
		return fmt.Errorf("Error adding column: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error adding column: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) DeleteColumn(collection_id, column_name string) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	resp, err := put("/admin/collectionmanagement", map[string]interface{}{
		"id":           collection_id,
		"deleteColumn": column_name,
	}, creds)
	if err != nil {
		return fmt.Errorf("Error deleting column: %v", err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error deleting column: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) GetCollectionInfo(collection_id string) (map[string]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return map[string]interface{}{}, err
	}
	resp, err := get("/admin/collectionmanagement", map[string]string{
		"id": collection_id,
	}, creds)
	if err != nil {
		return nil, fmt.Errorf("Error getting collection info: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Error getting collection info: %v", resp.Body)
	}
	return resp.Body.(map[string]interface{}), nil
}

//get collections list in system
func (d *DevClient) GetAllCollections(SystemKey string) ([]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return nil, err
	}
	resp, err := get("/admin/allcollections", map[string]string{
		"appid": SystemKey,
	}, creds)
	if err != nil {
		return nil, fmt.Errorf("Error getting collection info: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Error getting collection info: %v", resp.Body)
	}

	//fmt.Printf("body: %+v\n", resp.Body)
	return resp.Body.([]interface{}), nil
}

func (d *DevClient) GetAllRoles(SystemKey string) ([]interface{}, error) {
	creds, err := d.credentials()
	if err != nil {
		return nil, err
	}
	resp, err := get("/admin/user/"+SystemKey+"/roles", map[string]string{
		"appid": SystemKey,
	}, creds)
	//fmt.Printf("roles: %+v\n", resp.Body)
	return resp.Body.([]interface{}), nil
}

func (d *DevClient) CreateRole(systemKey, role_id string) (interface{}, error) {
	fmt.Println("creating role for " + role_id)
	creds, err := d.credentials()
	if err != nil {
		return nil, err
	}
	data := map[string]interface{}{
		"name":        role_id,
		"collections": []map[string]interface{}{},
		"topics":      []map[string]interface{}{},
		"services":    []map[string]interface{}{},
	}
	resp, err := post("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Error updating a role to have a collection: %v", resp.Body)
	}
	return resp.Body, nil
}

func (d *DevClient) AddCollectionToRole(systemKey, collection_id, role_id string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"collections": []map[string]interface{}{
				map[string]interface{}{
					"itemInfo": map[string]interface{}{
						"id": collection_id,
					},
					"permissions": level,
				},
			},
			"topics":   []map[string]interface{}{},
			"services": []map[string]interface{}{},
		},
	}
	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a collection: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddServiceToRole(systemKey, service, role_id string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"services": []map[string]interface{}{
				map[string]interface{}{
					"itemInfo": map[string]interface{}{
						"name": service,
					},
					"permissions": level,
				},
			},
			"topics":      []map[string]interface{}{},
			"collections": []map[string]interface{}{},
		},
	}
	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a service: %v", resp.Body)
	}
	return nil
}

func (d *DevClient) AddGenericPermissionToRole(systemKey, role_id, permission string, level int) error {
	creds, err := d.credentials()
	if err != nil {
		return err
	}

	data := map[string]interface{}{
		"id": role_id,
		"changes": map[string]interface{}{
			"services":    []map[string]interface{}{},
			"topics":      []map[string]interface{}{},
			"collections": []map[string]interface{}{},
		},
	}

	data["changes"].(map[string]interface{})[permission] = map[string]interface{}{
		"permissions": level,
	}

	resp, err := put("/admin/user/"+systemKey+"/roles", data, creds)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Error updating a role to have a service: %v", resp.Body)
	}
	return nil
}

//second verse, same as the first, eh?
func (d *DevClient) credentials() ([][]string, error) {
	if d.DevToken != "" {
		return [][]string{
			[]string{
				_DEV_HEADER_KEY,
				d.DevToken,
			},
		}, nil
	} else {
		return [][]string{}, errors.New("No SystemSecret/SystemKey combo, or UserToken found")
	}
}

func (d *DevClient) preamble() string {
	return _DEV_PREAMBLE
}

func (d *DevClient) getSystemInfo() (string, string) {
	return "", ""
}

func (d *DevClient) setToken(t string) {
	d.DevToken = t
}
func (d *DevClient) getToken() string {
	return d.DevToken
}

func (d *DevClient) getMessageId() uint16 {
	return uint16(d.mrand.Int())
}
<|endoftext|>