<commit_before>package ar

import (
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"
)

// test whether fileInfo implements os.FileInfo
var _ os.FileInfo = new(fileInfo)

var testCommon = "!<arch>\n" +
	"debian-binary   1385068169  0     0     100644  4         `\n" +
	"2.0\n" +
	"control.tar.gz  1385068169  0     0     100644  0         `\n"

var testCommonFileHeaders = []struct {
	in   string
	want *fileInfo
	err  string
}{
	{
		in: "debian-binary   1385068169  0     0     100644  4         `\n",
		want: &fileInfo{
			name:  "debian-binary",
			mtime: time.Unix(1385068169, 0),
			mode:  os.FileMode(0100644) & os.ModePerm,
			size:  4,
		},
	},
	{
		in: "debian-binary   1385068169  0     0     644     4         `\n",
		want: &fileInfo{
			name:  "debian-binary",
			mtime: time.Unix(1385068169, 0),
			mode:  os.FileMode(0644),
			size:  4,
		},
	},
	{
		in:  "debian-binary   1385068169  0     0     120644  4         `\n",
		err: "feature not implemented: non-regular files",
	},
	{
		in:  "debian-binary   1385068169  0     0     220644  4         `\n",
		err: "corrupt archive: invalid file mode",
	},
}

func TestReadFileHeader(t *testing.T) {
	for i, test := range testCommonFileHeaders {
		got, err := readFileHeader(strings.NewReader(test.in))
		switch {
		case err == nil && test.err != "":
			t.Errorf("%d: got no err, expected err %v", i, test.err)
			continue
		case err != nil && test.err != err.Error():
			t.Errorf("%d: got err %q, expected err %q", i, err, test.err)
			continue
		case err == nil && test.err == "":
			// no error as expected
		case err != nil && test.err == err.Error():
			t.Logf("%d: got expected error %q", i, err)
			continue
		}

		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%d: got %#v, expected %+v", i, got, test.want)
		} else {
			t.Logf("%d: got %#v", i, got)
		}
	}
}

func BenchmarkReadFileHeader(b *testing.B) {
	r := strings.NewReader("debian-binary   1385068169  0     0     100644  4         `\n")
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = readFileHeader(r)
		b.SetBytes(60)
		r.Seek(0, 0)
	}

}

var testMagic = []struct {
	in   io.Reader
	want error
}{
	{
		in: strings.NewReader(magic),
	},
	{
		in:   strings.NewReader(strings.Repeat("a", len(magic))),
		want: CorruptArchiveError("global archive header not found"),
	},
	{
		in:   strings.NewReader("!"),
		want: io.ErrUnexpectedEOF,
	},
	{
		in:   strings.NewReader(""),
		want: io.EOF,
	},
}

func TestReadMagic(t *testing.T) {
	for i, test := range testMagic {
		got := checkMagic(test.in)
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%d: got %#v, expected %+v", i, got, test.want)
		} else {
			t.Logf("%d: got %#v", i, got)
		}
	}
}

func TestFileInfo(t *testing.T) {
	test := &fileInfo{
		name:  "debian-binary",
		mtime: time.Unix(1385068169, 0),
		mode:  os.FileMode(0644),
		size:  4,
	}

	if test.IsDir() != false {
		t.Error("IsDir")
	}
	if test.Mode() != os.FileMode(0644) {
		t.Error("Mode")
	}
	if test.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime")
	}
	if test.Name() != "debian-binary" {
		t.Error("Name")
	}
	if test.Size() != 4 {
		t.Error("Size")
	}
	if test.Sys() != nil {
		t.Error("Sys")
	}
}

func TestReaderBasics(t *testing.T) {
	test := strings.NewReader(testCommon)
	r := NewReader(test)
	fi, err := r.Next()
	if err != nil {
		t.Fatal(err)
		return
	}
	if fi.Mode() != os.FileMode(0644) {
		t.Error("Mode")
	}
	if fi.Name() != "debian-binary" {
		t.Error("Name")
	}
	if fi.Size() != 4 {
		t.Error("Size")
	}
	if fi.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime")
	}

	if content, err := ioutil.ReadAll(r); err != nil {
		t.Error(err)
	} else if string(content) != "2.0\n" {
		t.Error("Content")
	}
	fi, err = r.Next()
	if err != nil {
		t.Fatal(err)
		return
	}
	if fi.Mode() != os.FileMode(0644) {
		t.Error("Mode2")
	}
	if fi.Name() != "control.tar.gz" {
		t.Error("Name2")
	}
	if fi.Size() != 0 {
		t.Error("Size2")
	}
	if fi.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime2")
	}

	if content, err := ioutil.ReadAll(r); err != nil {
		t.Error(err)
	} else if string(content) != "" {
		t.Error("Content2")
	}

	fi, err = r.Next()
	if err != io.EOF {
		t.Errorf("expected EOF, got %v", err)
	}
}

func BenchmarkReader(b *testing.B) {
	// contains 2 files
	test := strings.NewReader(testCommon)
	r := NewReader(test)

	var err error
	var read int64

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < 2; j++ {
			_, err = r.Next()
			if err != nil {
				b.Fatal(err)
			}
			read += 60
			n, err := io.Copy(ioutil.Discard, r)
			if err != nil {
				b.Fatal(err)
			}
			read += n
		}
		b.SetBytes(read)
		read = 0
		test.Seek(0, 0)
		r.Reset(test)
	}

}
<commit_msg>Use bytes Reader to avoid copies.<commit_after>package ar

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"
)

// test whether fileInfo implements os.FileInfo
var _ os.FileInfo = new(fileInfo)

var testCommon = "!<arch>\n" +
	"debian-binary   1385068169  0     0     100644  4         `\n" +
	"2.0\n" +
	"control.tar.gz  1385068169  0     0     100644  0         `\n"

var testCommonFileHeaders = []struct {
	in   string
	want *fileInfo
	err  string
}{
	{
		in: "debian-binary   1385068169  0     0     100644  4         `\n",
		want: &fileInfo{
			name:  "debian-binary",
			mtime: time.Unix(1385068169, 0),
			mode:  os.FileMode(0100644) & os.ModePerm,
			size:  4,
		},
	},
	{
		in: "debian-binary   1385068169  0     0     644     4         `\n",
		want: &fileInfo{
			name:  "debian-binary",
			mtime: time.Unix(1385068169, 0),
			mode:  os.FileMode(0644),
			size:  4,
		},
	},
	{
		in:  "debian-binary   1385068169  0     0     120644  4         `\n",
		err: "feature not implemented: non-regular files",
	},
	{
		in:  "debian-binary   1385068169  0     0     220644  4         `\n",
		err: "corrupt archive: invalid file mode",
	},
}

func TestReadFileHeader(t *testing.T) {
	for i, test := range testCommonFileHeaders {
		got, err := readFileHeader(strings.NewReader(test.in))
		switch {
		case err == nil && test.err != "":
			t.Errorf("%d: got no err, expected err %v", i, test.err)
			continue
		case err != nil && test.err != err.Error():
			t.Errorf("%d: got err %q, expected err %q", i, err, test.err)
			continue
		case err == nil && test.err == "":
			// no error as expected
		case err != nil && test.err == err.Error():
			t.Logf("%d: got expected error %q", i, err)
			continue
		}

		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%d: got %#v, expected %+v", i, got, test.want)
		} else {
			t.Logf("%d: got %#v", i, got)
		}
	}
}

func BenchmarkReadFileHeader(b *testing.B) {
	r := bytes.NewReader([]byte("debian-binary   1385068169  0     0     100644  4         `\n"))
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = readFileHeader(r)
		b.SetBytes(60)
		r.Seek(0, 0)
	}

}

var testMagic = []struct {
	in   io.Reader
	want error
}{
	{
		in: strings.NewReader(magic),
	},
	{
		in:   strings.NewReader(strings.Repeat("a", len(magic))),
		want: CorruptArchiveError("global archive header not found"),
	},
	{
		in:   strings.NewReader("!"),
		want: io.ErrUnexpectedEOF,
	},
	{
		in:   strings.NewReader(""),
		want: io.EOF,
	},
}

func TestReadMagic(t *testing.T) {
	for i, test := range testMagic {
		got := checkMagic(test.in)
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%d: got %#v, expected %+v", i, got, test.want)
		} else {
			t.Logf("%d: got %#v", i, got)
		}
	}
}

func TestFileInfo(t *testing.T) {
	test := &fileInfo{
		name:  "debian-binary",
		mtime: time.Unix(1385068169, 0),
		mode:  os.FileMode(0644),
		size:  4,
	}

	if test.IsDir() != false {
		t.Error("IsDir")
	}
	if test.Mode() != os.FileMode(0644) {
		t.Error("Mode")
	}
	if test.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime")
	}
	if test.Name() != "debian-binary" {
		t.Error("Name")
	}
	if test.Size() != 4 {
		t.Error("Size")
	}
	if test.Sys() != nil {
		t.Error("Sys")
	}
}

func TestReaderBasics(t *testing.T) {
	test := strings.NewReader(testCommon)
	r := NewReader(test)
	fi, err := r.Next()
	if err != nil {
		t.Fatal(err)
		return
	}
	if fi.Mode() != os.FileMode(0644) {
		t.Error("Mode")
	}
	if fi.Name() != "debian-binary" {
		t.Error("Name")
	}
	if fi.Size() != 4 {
		t.Error("Size")
	}
	if fi.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime")
	}

	if content, err := ioutil.ReadAll(r); err != nil {
		t.Error(err)
	} else if string(content) != "2.0\n" {
		t.Error("Content")
	}
	fi, err = r.Next()
	if err != nil {
		t.Fatal(err)
		return
	}
	if fi.Mode() != os.FileMode(0644) {
		t.Error("Mode2")
	}
	if fi.Name() != "control.tar.gz" {
		t.Error("Name2")
	}
	if fi.Size() != 0 {
		t.Error("Size2")
	}
	if fi.ModTime() != time.Unix(1385068169, 0) {
		t.Error("ModTime2")
	}

	if content, err := ioutil.ReadAll(r); err != nil {
		t.Error(err)
	} else if string(content) != "" {
		t.Error("Content2")
	}

	fi, err = r.Next()
	if err != io.EOF {
		t.Errorf("expected EOF, got %v", err)
	}
}

func BenchmarkReader(b *testing.B) {
	// contains 2 files
	test := bytes.NewReader([]byte(testCommon))
	r := NewReader(test)

	var err error
	var read int64

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < 2; j++ {
			_, err = r.Next()
			if err != nil {
				b.Fatal(err)
			}
			read += 60
			n, err := io.Copy(ioutil.Discard, r)
			if err != nil {
				b.Fatal(err)
			}
			read += n
		}
		b.SetBytes(read)
		read = 0
		test.Seek(0, 0)
		r.Reset(test)
	}

}
<|endoftext|>


<commit_before>package signprocessor

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"strings"
	"time"

	"github.com/google/go-github/github"
	"github.com/mrjones/oauth"
	"github.com/zabawaba99/fireauth"
	"github.com/zabawaba99/firego"

	"golang.org/x/oauth2"
)

func EventLoop() {
	data := make(fireauth.Data)
	options := fireauth.Option{
		Admin: true,
	}
	token, _ := fireauth.New(Secrets.FireBaseSecret).CreateToken(data, &options)
	f := firego.New(Config.FireBaseDB, nil)
	f.Auth(token)

	t := oauth.NewConsumer(Secrets.TwitterKey, Secrets.TwitterSecret, oauth.ServiceProvider{
		RequestTokenUrl:   "https://api.twitter.com/oauth/request_token",
		AuthorizeTokenUrl: "https://api.twitter.com/oauth/authorize",
		AccessTokenUrl:    "https://api.twitter.com/oauth/access_token",
	})

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: Secrets.GithubToken},
	)
	tc := oauth2.NewClient(oauth2.NoContext, ts)
	gh := github.NewClient(tc)

	notifications := make(chan firego.Event)
	if err := f.Watch(notifications); err != nil {
		log.Fatalf("Error setting up watch: %v", err)
	}

	defer f.StopWatching()
	for event := range notifications {
		if event.Path == "/" && event.Data != nil {
			if users, ok := event.Data.(map[string]interface{})["users"]; ok {
				for uid, d := range users.(map[string]interface{}) {
					details := d.(map[string]interface{})
					if process(uid, details, t, gh) {
						f.Child("users").Child(uid).Remove()
					}
				}
			}
		} else if event.Type == "put" && strings.HasPrefix(event.Path, "/users/") {
			uid := strings.TrimPrefix(event.Path, "/users/")
			if details, ok := event.Data.(map[string]interface{}); ok {
				if process(uid, details, t, gh) {
					f.Child("users").Child(uid).Remove()
				}
			}
		}
	}
	fmt.Printf("Notifications have stopped\n")
}

func process(uid string, details map[string]interface{}, t *oauth.Consumer, gh *github.Client) bool {
	linkProfile := details["linkProfile"].(bool)
	link := details["link"].(string)
	personalPage := details["personalPage"].(string)
	name := details["name"].(string)
	title := details["title"].(string)
	affiliation := details["affiliation"].(string)

	secret := details["twitterSecret"].(string)
	token := details["twitterToken"].(string)
	tclient, err := t.MakeHttpClient(&oauth.AccessToken{Token: token, Secret: secret})
	resp, err := tclient.Get("https://api.twitter.com/1.1/account/verify_credentials.json?skip_status=true&include_entities=false")
	if err != nil {
		log.Printf("Twitter API error: %v processing %s", err, uid)
		return false
	}
	defer resp.Body.Close()

	bits, _ := ioutil.ReadAll(resp.Body)

	var user map[string]interface{}
	json.Unmarshal(bits, &user)
	egg := user["default_profile_image"].(bool)
	created, _ := time.Parse(time.RubyDate, user["created_at"].(string))
	description := user["description"].(string)
	followers := int(user["followers_count"].(float64))
	following := int(user["friends_count"].(float64))
	displayName := user["name"].(string)
	handle := user["screen_name"].(string)
	tweets := int(user["statuses_count"].(float64))
	var url string
	switch user["url"].(type) {
	case string:
		url = user["url"].(string)
	}

	if name == "" {
		log.Printf("No signatory name specified for %s (%s)", uid, handle)
		return false
	}

	if score := Score(handle, displayName, url, created, followers, following, tweets, egg, description, personalPage); score < 0 {
		log.Printf("Not creating pull for %s (%s) due to score %d", uid, handle, score)
		return false
	}

	var linkMd, affiliationMd, titleMd string
	if linkProfile {
		linkMd = fmt.Sprintf("  link: https://twitter.com/%s\n", handle)
	} else if link != "" {
		linkMd = fmt.Sprintf("  link: %s\n", link)
	}
	if affiliation != "" {
		affiliationMd = fmt.Sprintf("  affiliation: \"%s\"\n", affiliation)
	}
	if title != "" {
		titleMd = fmt.Sprintf("  occupation_title: \"%s\"\n", title)
	}
	contents := fmt.Sprintf("---\n  name: \"%s\"\n%s%s%s---", name, linkMd, affiliationMd, titleMd)

	body := fmt.Sprintf(`Twitter user: https://twitter.com/%s
Created: %v, Followers: %d, Following: %d, Tweets: %d, Egg: %v

Twitter profile fields:
Name: %s
Website: %s
Tagline: %s

Personal page: %s

Signature file contents:
%s`,
		handle,
		created, followers, following, tweets, egg,
		displayName,
		url,
		description,
		personalPage,
		fmt.Sprintf("```\n%s\n```", contents),
	)

	// Ensure we are forking from a clean state.
	g := gh.Git
	ref, _, err := g.GetRef("neveragaindottech", "neveragaindottech.github.io", "heads/master")
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	refStr := fmt.Sprintf("heads/signbot/%s", uid)
	ref.Ref = &refStr
	if _, _, err = g.UpdateRef(Config.GithubUser, "neveragaindottech.github.io", ref, true); err != nil {
		if _, _, err = g.CreateRef(Config.GithubUser, "neveragaindottech.github.io", ref); err != nil {
			log.Printf("Error: %v processing %s", err, uid)
			return false
		}
	}
	baseC, _, err := g.GetCommit(Config.GithubUser, "neveragaindottech.github.io", *ref.Object.SHA)
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	path := fmt.Sprintf("_signatures/%s.md", uid)
	mode := "100644"
	kind := "blob"
	newT, _, err := g.CreateTree(Config.GithubUser, "neveragaindottech.github.io", *baseC.Tree.SHA, []github.TreeEntry{
		{Path: &path, Mode: &mode, Type: &kind, Content: &contents},
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	desc := fmt.Sprintf("SignBot: Add signatory '%s' (%s)", name, handle)
	newC, _, err := g.CreateCommit(Config.GithubUser, "neveragaindottech.github.io", &github.Commit{
		Message: &desc,
		Tree:    newT,
		Parents: []github.Commit{*baseC},
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	if _, _, err = g.UpdateRef(Config.GithubUser, "neveragaindottech.github.io", &github.Reference{
		Ref: &refStr,
		Object: &github.GitObject{
			SHA: newC.SHA,
		},
	}, false); err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	p := gh.PullRequests
	branchName := fmt.Sprintf("%s:signbot/%s", Config.GithubUser, uid)
	master := "master"
	_, _, err = p.Create("neveragaindottech", "neveragaindottech.github.io", &github.NewPullRequest{
		Title: &desc,
		Head:  &branchName,
		Base:  &master,
		Body:  &body,
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	fmt.Printf("Processed %s (https://twitter.com/%s)\n", uid, handle)
	return true
}
<commit_msg>add newline after ---<commit_after>package signprocessor

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"strings"
	"time"

	"github.com/google/go-github/github"
	"github.com/mrjones/oauth"
	"github.com/zabawaba99/fireauth"
	"github.com/zabawaba99/firego"

	"golang.org/x/oauth2"
)

func EventLoop() {
	data := make(fireauth.Data)
	options := fireauth.Option{
		Admin: true,
	}
	token, _ := fireauth.New(Secrets.FireBaseSecret).CreateToken(data, &options)
	f := firego.New(Config.FireBaseDB, nil)
	f.Auth(token)

	t := oauth.NewConsumer(Secrets.TwitterKey, Secrets.TwitterSecret, oauth.ServiceProvider{
		RequestTokenUrl:   "https://api.twitter.com/oauth/request_token",
		AuthorizeTokenUrl: "https://api.twitter.com/oauth/authorize",
		AccessTokenUrl:    "https://api.twitter.com/oauth/access_token",
	})

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: Secrets.GithubToken},
	)
	tc := oauth2.NewClient(oauth2.NoContext, ts)
	gh := github.NewClient(tc)

	notifications := make(chan firego.Event)
	if err := f.Watch(notifications); err != nil {
		log.Fatalf("Error setting up watch: %v", err)
	}

	defer f.StopWatching()
	for event := range notifications {
		if event.Path == "/" && event.Data != nil {
			if users, ok := event.Data.(map[string]interface{})["users"]; ok {
				for uid, d := range users.(map[string]interface{}) {
					details := d.(map[string]interface{})
					if process(uid, details, t, gh) {
						f.Child("users").Child(uid).Remove()
					}
				}
			}
		} else if event.Type == "put" && strings.HasPrefix(event.Path, "/users/") {
			uid := strings.TrimPrefix(event.Path, "/users/")
			if details, ok := event.Data.(map[string]interface{}); ok {
				if process(uid, details, t, gh) {
					f.Child("users").Child(uid).Remove()
				}
			}
		}
	}
	fmt.Printf("Notifications have stopped\n")
}

func process(uid string, details map[string]interface{}, t *oauth.Consumer, gh *github.Client) bool {
	linkProfile := details["linkProfile"].(bool)
	link := details["link"].(string)
	personalPage := details["personalPage"].(string)
	name := details["name"].(string)
	title := details["title"].(string)
	affiliation := details["affiliation"].(string)

	secret := details["twitterSecret"].(string)
	token := details["twitterToken"].(string)
	tclient, err := t.MakeHttpClient(&oauth.AccessToken{Token: token, Secret: secret})
	resp, err := tclient.Get("https://api.twitter.com/1.1/account/verify_credentials.json?skip_status=true&include_entities=false")
	if err != nil {
		log.Printf("Twitter API error: %v processing %s", err, uid)
		return false
	}
	defer resp.Body.Close()

	bits, _ := ioutil.ReadAll(resp.Body)

	var user map[string]interface{}
	json.Unmarshal(bits, &user)
	egg := user["default_profile_image"].(bool)
	created, _ := time.Parse(time.RubyDate, user["created_at"].(string))
	description := user["description"].(string)
	followers := int(user["followers_count"].(float64))
	following := int(user["friends_count"].(float64))
	displayName := user["name"].(string)
	handle := user["screen_name"].(string)
	tweets := int(user["statuses_count"].(float64))
	var url string
	switch user["url"].(type) {
	case string:
		url = user["url"].(string)
	}

	if name == "" {
		log.Printf("No signatory name specified for %s (%s)", uid, handle)
		return false
	}

	if score := Score(handle, displayName, url, created, followers, following, tweets, egg, description, personalPage); score < 0 {
		log.Printf("Not creating pull for %s (%s) due to score %d", uid, handle, score)
		return false
	}

	var linkMd, affiliationMd, titleMd string
	if linkProfile {
		linkMd = fmt.Sprintf("  link: https://twitter.com/%s\n", handle)
	} else if link != "" {
		linkMd = fmt.Sprintf("  link: %s\n", link)
	}
	if affiliation != "" {
		affiliationMd = fmt.Sprintf("  affiliation: \"%s\"\n", affiliation)
	}
	if title != "" {
		titleMd = fmt.Sprintf("  occupation_title: \"%s\"\n", title)
	}
	contents := fmt.Sprintf("---\n  name: \"%s\"\n%s%s%s---\n", name, linkMd, affiliationMd, titleMd)

	body := fmt.Sprintf(`Twitter user: https://twitter.com/%s
Created: %v, Followers: %d, Following: %d, Tweets: %d, Egg: %v

Twitter profile fields:
Name: %s
Website: %s
Tagline: %s

Personal page: %s

Signature file contents:
%s`,
		handle,
		created, followers, following, tweets, egg,
		displayName,
		url,
		description,
		personalPage,
		fmt.Sprintf("```\n%s\n```", contents),
	)

	// Ensure we are forking from a clean state.
	g := gh.Git
	ref, _, err := g.GetRef("neveragaindottech", "neveragaindottech.github.io", "heads/master")
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	refStr := fmt.Sprintf("heads/signbot/%s", uid)
	ref.Ref = &refStr
	if _, _, err = g.UpdateRef(Config.GithubUser, "neveragaindottech.github.io", ref, true); err != nil {
		if _, _, err = g.CreateRef(Config.GithubUser, "neveragaindottech.github.io", ref); err != nil {
			log.Printf("Error: %v processing %s", err, uid)
			return false
		}
	}
	baseC, _, err := g.GetCommit(Config.GithubUser, "neveragaindottech.github.io", *ref.Object.SHA)
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	path := fmt.Sprintf("_signatures/%s.md", uid)
	mode := "100644"
	kind := "blob"
	newT, _, err := g.CreateTree(Config.GithubUser, "neveragaindottech.github.io", *baseC.Tree.SHA, []github.TreeEntry{
		{Path: &path, Mode: &mode, Type: &kind, Content: &contents},
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	desc := fmt.Sprintf("SignBot: Add signatory '%s' (%s)", name, handle)
	newC, _, err := g.CreateCommit(Config.GithubUser, "neveragaindottech.github.io", &github.Commit{
		Message: &desc,
		Tree:    newT,
		Parents: []github.Commit{*baseC},
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	if _, _, err = g.UpdateRef(Config.GithubUser, "neveragaindottech.github.io", &github.Reference{
		Ref: &refStr,
		Object: &github.GitObject{
			SHA: newC.SHA,
		},
	}, false); err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	p := gh.PullRequests
	branchName := fmt.Sprintf("%s:signbot/%s", Config.GithubUser, uid)
	master := "master"
	_, _, err = p.Create("neveragaindottech", "neveragaindottech.github.io", &github.NewPullRequest{
		Title: &desc,
		Head:  &branchName,
		Base:  &master,
		Body:  &body,
	})
	if err != nil {
		log.Printf("Error: %v processing %s", err, uid)
		return false
	}
	fmt.Printf("Processed %s (https://twitter.com/%s)\n", uid, handle)
	return true
}
<|endoftext|>


<commit_before>package zim

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"

	xz "github.com/remyoudompheng/go-liblzma"
)

const (
	RedirectEntry   uint16 = 0xffff
	LinkTargetEntry        = 0xfffe
	DeletedEntry           = 0xfffd
)

type Article struct {
	URLPtr     uint64
	Mimetype   uint16
	Namespace  byte
	URL        string
	Title      string
	Blob       uint32
	Cluster    uint32
	RedirectTo *Article
}

func (z *ZimReader) FillArticleAt(a *Article, offset uint64) *Article {
	a.URLPtr = offset

	mimeIdx, err := readInt16(z.mmap[offset : offset+2])
	if err != nil {
		panic(err)
	}
	a.Mimetype = mimeIdx

	// Linktarget or Target Entry
	if mimeIdx == LinkTargetEntry || mimeIdx == DeletedEntry {
		//TODO
		return nil
	}

	a.Namespace = z.mmap[offset+3]

	a.Cluster, err = readInt32(z.mmap[offset+8 : offset+8+4])
	if err != nil {
		panic(err)
	}

	a.Blob, err = readInt32(z.mmap[offset+12 : offset+12+4])
	if err != nil {
		panic(err)
	}

	// Redirect
	if mimeIdx == RedirectEntry {
		// check for a possible loop: the redirect could point to the same target
		if z.GetUrlOffsetAtIdx(a.Cluster) != offset {
			// redirect ptr share the same memory offset than Cluster number
			a.RedirectTo = z.getArticleAt(z.GetUrlOffsetAtIdx(a.Cluster))
		}

		b := bytes.NewBuffer(z.mmap[offset+12:])
		a.URL, err = b.ReadString('\x00')
		if err != nil {
			panic(err)
		}
		a.URL = strings.TrimRight(string(a.URL), "\x00")

		a.Title, err = b.ReadString('\x00')
		if err != nil {
			panic(err)
		}
		a.Title = strings.TrimRight(string(a.Title), "\x00")

		return a
	}

	b := bytes.NewBuffer(z.mmap[offset+16:])
	a.URL, err = b.ReadString('\x00')
	if err != nil {
		panic(err)
	}
	a.URL = strings.TrimRight(string(a.URL), "\x00")

	a.Title, err = b.ReadString('\x00')
	if err != nil {
		panic(err)
	}
	a.Title = strings.TrimRight(string(a.Title), "\x00")

	return a
}

// get the article (Directory) pointed by the offset found in URLpos or Titlepos
func (z *ZimReader) getArticleAt(offset uint64) *Article {
	a := new(Article)
	z.FillArticleAt(a, offset)
	return a
}

// return the uncompressed data associated with this article
func (a *Article) Data(z *ZimReader) []byte {
	start, end := z.getClusterOffsetsAtIdx(a.Cluster)
	compression := uint8(z.mmap[start])

	// LZMA
	if compression == 4 {
		b := bytes.NewBuffer(z.mmap[start+1 : end+1])
		dec, err := xz.NewReader(b)
		defer dec.Close()
		if err != nil {
			panic(err)
		}

		// the decoded chunk are around 1MB
		// TODO: on smaller devices need to read stream rather than ReadAll
		blob, err := ioutil.ReadAll(dec)
		if err != nil {
			panic(err)
		}

		// blob starts at offset, blob ends at offset
		var bs, be uint32

		bs, err = readInt32(blob[a.Blob*4 : a.Blob*4+4])
		if err != nil {
			panic(err)
		}

		be, err = readInt32(blob[a.Blob*4+4 : a.Blob*4+4+4])
		if err != nil {
			panic(err)
		}

		return blob[bs:be]
	}

	return nil
}

// return the url prefixed by the namespace
func (a *Article) FullURL() string {
	return string(a.Namespace) + "/" + a.URL
}

func (a *Article) getBlobOffsetsAtIdx(z *ZimReader) (start, end uint64) {
	idx := a.Blob
	offset := z.clusterPtrPos + uint64(idx)*8
	start, err := readInt64(z.mmap[offset : offset+8])
	if err != nil {
		panic(err)
	}
	offset = z.clusterPtrPos + uint64(idx+1)*8
	end, err = readInt64(z.mmap[offset : offset+8])
	if err != nil {
		panic(err)
	}
	return
}

func (a *Article) String() string {
	return fmt.Sprintf("Mime: 0x%x URL: [%s], Title: [%s], Cluster: 0x%x Blob: 0x%x",
		a.Mimetype, a.URL, a.Title, a.Cluster, a.Blob)
}
<commit_msg>do not try to read data on redirect/deleted entry<commit_after>package zim

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"

	xz "github.com/remyoudompheng/go-liblzma"
)

const (
	RedirectEntry   uint16 = 0xffff
	LinkTargetEntry        = 0xfffe
	DeletedEntry           = 0xfffd
)

type Article struct {
	URLPtr     uint64
	Mimetype   uint16
	Namespace  byte
	URL        string
	Title      string
	Blob       uint32
	Cluster    uint32
	RedirectTo *Article
}

func (z *ZimReader) FillArticleAt(a *Article, offset uint64) *Article {
	a.URLPtr = offset

	mimeIdx, err := readInt16(z.mmap[offset : offset+2])
	if err != nil {
		panic(err)
	}
	a.Mimetype = mimeIdx

	// Linktarget or Target Entry
	if mimeIdx == LinkTargetEntry || mimeIdx == DeletedEntry {
		//TODO
		return nil
	}

	a.Namespace = z.mmap[offset+3]

	a.Cluster, err = readInt32(z.mmap[offset+8 : offset+8+4])
	if err != nil {
		panic(err)
	}

	a.Blob, err = readInt32(z.mmap[offset+12 : offset+12+4])
	if err != nil {
		panic(err)
	}

	// Redirect
	if mimeIdx == RedirectEntry {
		// check for a possible loop: the redirect could point to the same target
		if z.GetUrlOffsetAtIdx(a.Cluster) != offset {
			// redirect ptr share the same memory offset than Cluster number
			a.RedirectTo = z.getArticleAt(z.GetUrlOffsetAtIdx(a.Cluster))
		}

		b := bytes.NewBuffer(z.mmap[offset+12:])
		a.URL, err = b.ReadString('\x00')
		if err != nil {
			panic(err)
		}
		a.URL = strings.TrimRight(string(a.URL), "\x00")

		a.Title, err = b.ReadString('\x00')
		if err != nil {
			panic(err)
		}
		a.Title = strings.TrimRight(string(a.Title), "\x00")

		return a
	}

	b := bytes.NewBuffer(z.mmap[offset+16:])
	a.URL, err = b.ReadString('\x00')
	if err != nil {
		panic(err)
	}
	a.URL = strings.TrimRight(string(a.URL), "\x00")

	a.Title, err = b.ReadString('\x00')
	if err != nil {
		panic(err)
	}
	a.Title = strings.TrimRight(string(a.Title), "\x00")

	return a
}

// get the article (Directory) pointed by the offset found in URLpos or Titlepos
func (z *ZimReader) getArticleAt(offset uint64) *Article {
	a := new(Article)
	z.FillArticleAt(a, offset)
	return a
}

// return the uncompressed data associated with this article
func (a *Article) Data(z *ZimReader) []byte {
	// ensure we have data to read
	if a.Mimetype == RedirectEntry || a.Mimetype == LinkTargetEntry || a.Mimetype == DeletedEntry {
		return nil
	}
	start, end := z.getClusterOffsetsAtIdx(a.Cluster)
	compression := uint8(z.mmap[start])

	// LZMA
	if compression == 4 {
		b := bytes.NewBuffer(z.mmap[start+1 : end+1])
		dec, err := xz.NewReader(b)
		defer dec.Close()
		if err != nil {
			panic(err)
		}

		// the decoded chunk are around 1MB
		// TODO: on smaller devices need to read stream rather than ReadAll
		blob, err := ioutil.ReadAll(dec)
		if err != nil {
			panic(err)
		}

		// blob starts at offset, blob ends at offset
		var bs, be uint32

		bs, err = readInt32(blob[a.Blob*4 : a.Blob*4+4])
		if err != nil {
			panic(err)
		}

		be, err = readInt32(blob[a.Blob*4+4 : a.Blob*4+4+4])
		if err != nil {
			panic(err)
		}

		return blob[bs:be]
	}

	return nil
}

// return the url prefixed by the namespace
func (a *Article) FullURL() string {
	return string(a.Namespace) + "/" + a.URL
}

func (a *Article) getBlobOffsetsAtIdx(z *ZimReader) (start, end uint64) {
	idx := a.Blob
	offset := z.clusterPtrPos + uint64(idx)*8
	start, err := readInt64(z.mmap[offset : offset+8])
	if err != nil {
		panic(err)
	}
	offset = z.clusterPtrPos + uint64(idx+1)*8
	end, err = readInt64(z.mmap[offset : offset+8])
	if err != nil {
		panic(err)
	}
	return
}

func (a *Article) String() string {
	return fmt.Sprintf("Mime: 0x%x URL: [%s], Title: [%s], Cluster: 0x%x Blob: 0x%x",
		a.Mimetype, a.URL, a.Title, a.Cluster, a.Blob)
}
<|endoftext|>


<commit_before>package dummy

import "fmt"

type MinimumError struct {
	Defined, Specified float64
}

func (err MinimumError) Error() string {
	return fmt.Sprintf("MinimumError: %f is defined as minimum, but specified %f", err.Defined, err.Specified)
}

func Minimum(defined float64, specified interface{}) error {
	if specified < defined {
		return MinimuError{defined, specified}
	}
	return nil
}

type MaximumError struct {
	Defined, Specified float64
}

func (err MaximumError) Error() string {
	return fmt.Sprintf("MaximumError: %f is defined as maximum, but specified %f", err.Defined, err.Specified)
}

func Minimum(defined float64, specified interface{}) error {
	if specified > defined {
		return MaximumError{defined, specified}
	}
	return nil
}

type MaxItemsError struct {
	Max    int
	Length int
}

func (err MaxItemsError) Error() string {
	return fmt.Sprintf("MaxItemError: %d is defined as max, but actual %d", err.Max, err.Length)
}

func MaxItems(max int, items []interface{}) error {
	length := len(items)
	if length > max {
		return MaxItemsError{max, length}
	}
	return nil
}

type MinItemError struct {
	Min    int
	Length int
}

func (err MinItemsError) Error() string {
	return fmt.Sprintf("MinItemError: %d is defined as max, but actual %d", err.Min, err.Length)
}

func MinItems(min int, items []interface{}) error {
	length := len(items)
	if length < min {
		return MinItemsError{min, length}
	}
	return nil
}

type UniqueItemError struct {
	Item interface{}
}

func (err UniqueItemError) Error() string {
	return fmt.Sprintf("UniqueItemError: %v is duplicated", err.Item)
}

func UniqueItems(items []interface{}) error {
	for i, item := range items {
		rests := items[i+1:]
		for _, rest := range rests {
			if item == rest {
				return UniqueItemError{item}
			}
		}
	}
	return nil
}
<commit_msg>Fix typo<commit_after>package dummy

import "fmt"

type MaximumError struct {
	Defined, Specified float64
}

func (err MaximumError) Error() string {
	return fmt.Sprintf("MaximumError: %f is defined as maximum, but specified %f", err.Defined, err.Specified)
}

func Maximum(defined float64, specified interface{}) error {
	if specified > defined {
		return MaximumError{defined, specified}
	}
	return nil
}

type MinimumError struct {
	Defined, Specified float64
}

func (err MinimumError) Error() string {
	return fmt.Sprintf("MinimumError: %f is defined as minimum, but specified %f", err.Defined, err.Specified)
}

func Minimum(defined float64, specified interface{}) error {
	if specified < defined {
		return MinimuError{defined, specified}
	}
	return nil
}

type MaxItemsError struct {
	Max    int
	Length int
}

func (err MaxItemsError) Error() string {
	return fmt.Sprintf("MaxItemError: %d is defined as max, but actual %d", err.Max, err.Length)
}

func MaxItems(max int, items []interface{}) error {
	length := len(items)
	if length > max {
		return MaxItemsError{max, length}
	}
	return nil
}

type MinItemError struct {
	Min    int
	Length int
}

func (err MinItemsError) Error() string {
	return fmt.Sprintf("MinItemError: %d is defined as max, but actual %d", err.Min, err.Length)
}

func MinItems(min int, items []interface{}) error {
	length := len(items)
	if length < min {
		return MinItemsError{min, length}
	}
	return nil
}

type UniqueItemError struct {
	Item interface{}
}

func (err UniqueItemError) Error() string {
	return fmt.Sprintf("UniqueItemError: %v is duplicated", err.Item)
}

func UniqueItems(items []interface{}) error {
	for i, item := range items {
		rests := items[i+1:]
		for _, rest := range rests {
			if item == rest {
				return UniqueItemError{item}
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/tendermint/abci/client"
	"github.com/tendermint/abci/types"
	. "github.com/tendermint/go-common"
	"github.com/urfave/cli"
)

//structure for data passed to print response
// variables must be exposed for JSON to read
type response struct {
	Res       types.Result
	Data      string
	PrintCode bool
	Code      string
}

func newResponse(res types.Result, data string, printCode bool) *response {
	rsp := &response{
		Res:       res,
		Data:      data,
		PrintCode: printCode,
		Code:      "",
	}

	if printCode {
		rsp.Code = res.Code.String()
	}

	return rsp
}

// client is a global variable so it can be reused by the console
var client abcicli.Client

func main() {

	//workaround for the cli library (https://github.com/urfave/cli/issues/565)
	cli.OsExiter = func(_ int) {}

	app := cli.NewApp()
	app.Name = "bft-cli"
	app.Usage = "bft-cli [command] [args...]"
	app.Version = "0.0.1"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "address",
			Value: "tcp://127.0.0.1:46658",
			Usage: "address of application socket",
		},
		cli.StringFlag{
			Name:  "bft",
			Value: "socket",
			Usage: "socket or grpc",
		},
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "print the command and results as if it were a console session",
		},
		/*cli.StringFlag{
      		Name: "lang",
      		Value: "english",
      		Usage: "language for the greeting",
    	},*/
	}
	app.Commands = []cli.Command{
		{
			Name:  "batch",
			Usage: "Run a batch of bft commands against an application",
			Action: func(c *cli.Context) error {
				return cmdBatch(app, c)
			},
		},
		{
			Name:  "console",
			Usage: "Start an interactive bft console for multiple commands",
			Action: func(c *cli.Context) error {
				return cmdConsole(app, c)
			},
		},
		{
			Name:  "echo",
			Usage: "Have the application echo a message",
			Action: func(c *cli.Context) error {
				return cmdEcho(c)
			},
		},
		{
			Name:  "info",
			Usage: "Get some info about the application",
			Action: func(c *cli.Context) error {
				return cmdInfo(c)
			},
		},
		{
			Name:  "set_option",
			Usage: "Set an option on the application",
			Action: func(c *cli.Context) error {
				return cmdSetOption(c)
			},
		},
		{
			Name:  "publish_bftx",		//"deliver_tx",
			Usage: "Deliver a new bftx to application",
			Action: func(c *cli.Context) error {
				return cmdDeliverTx(c)
			},
		},
		{
			Name:  "check_bftx",
			Usage: "Validate a bftx",
			Action: func(c *cli.Context) error {
				return cmdCheckTx(c)
			},
		},
		{
			Name:  "commit",
			Usage: "Commit the application state and return the Merkle root hash",
			Action: func(c *cli.Context) error {
				return cmdCommit(c)
			},
		},
		{
			Name:  "query",
			Usage: "Query application state",
			Action: func(c *cli.Context) error {
				return cmdQuery(c)
			},
		},
	}
	app.Before = before
	err := app.Run(os.Args)
	if err != nil {
		Exit(err.Error())
	}

}

func before(c *cli.Context) error {
	introduction(c)
	if client == nil {
		var err error
		client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("bft"), false)
		if err != nil {
			Exit(err.Error())
		}
	}
	return nil
}

// badCmd is called when we invoke with an invalid first argument (just for console for now)
func badCmd(c *cli.Context, cmd string) {
	fmt.Println("Unknown command:", cmd)
	fmt.Println("Please try one of the following:")
	fmt.Println("")
	cli.DefaultAppComplete(c)
}

//Generates new Args array based off of previous call args to maintain flag persistence
func persistentArgs(line []byte) []string {

	//generate the arguments to run from orginal os.Args
	// to maintain flag arguments
	args := os.Args
	args = args[:len(args)-1] // remove the previous command argument

	if len(line) > 0 { //prevents introduction of extra space leading to argument parse errors
		args = append(args, strings.Split(string(line), " ")...)
	}
	return args
}

//--------------------------------------------------------------------------------

func cmdBatch(app *cli.App, c *cli.Context) error {
	bufReader := bufio.NewReader(os.Stdin)
	for {
		line, more, err := bufReader.ReadLine()
		if more {
			return errors.New("Input line is too long")
		} else if err == io.EOF {
			break
		} else if len(line) == 0 {
			continue
		} else if err != nil {
			return err
		}

		args := persistentArgs(line)
		app.Run(args) //cli prints error within its func call
	}
	return nil
}

func cmdConsole(app *cli.App, c *cli.Context) error {
	// don't hard exit on mistyped commands (eg. check vs check_tx)
	app.CommandNotFound = badCmd

	for {
		fmt.Printf("\n> ")
		bufReader := bufio.NewReader(os.Stdin)
		line, more, err := bufReader.ReadLine()
		if more {
			return errors.New("Input is too long")
		} else if err != nil {
			return err
		}

		args := persistentArgs(line)
		app.Run(args) //cli prints error within its func call
	}
}

// Have the application echo a message
func cmdEcho(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command echo takes 1 argument")
	}
	res := client.EchoSync(args[0])
	rsp := newResponse(res, string(res.Data), false)
	printResponse(c, rsp)
	return nil
}

// Get some info from the application
func cmdInfo(c *cli.Context) error {
	resInfo, err := client.InfoSync()
	if err != nil {
		return err
	}
	rsp := newResponse(types.Result{}, string(resInfo.Data), false)
	printResponse(c, rsp)
	return nil
}

// Set an option on the application
func cmdSetOption(c *cli.Context) error {
	args := c.Args()
	if len(args) != 2 {
		return errors.New("Command set_option takes 2 arguments (key, value)")
	}
	res := client.SetOptionSync(args[0], args[1])
	rsp := newResponse(res, Fmt("%s=%s", args[0], args[1]), false)
	printResponse(c, rsp)
	return nil
}

// Append a new tx to application
func cmdDeliverTx(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command deliver_tx takes 1 argument")
	}
	txBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}
	res := client.DeliverTxSync(txBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

// Validate a tx
func cmdCheckTx(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command check_tx takes 1 argument")
	}
	txBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}
	res := client.CheckTxSync(txBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

// Get application Merkle root hash
func cmdCommit(c *cli.Context) error {
	res := client.CommitSync()
	rsp := newResponse(res, Fmt("0x%X", res.Data), false)
	printResponse(c, rsp)
	return nil
}

// Query application state
func cmdQuery(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command query takes 1 argument")
	}
	queryBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}
	res := client.QuerySync(queryBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

//--------------------------------------------------------------------------------

func printResponse(c *cli.Context, rsp *response) {

	verbose := c.GlobalBool("verbose")

	if verbose {
		fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " "))
	}

	if rsp.PrintCode {
		fmt.Printf("-> code: %s\n", rsp.Code)
	}

	//if pr.res.Error != "" {
	//	fmt.Printf("-> error: %s\n", pr.res.Error)
	//}

	if rsp.Data != "" {
		fmt.Printf("-> blockfreight data: %s\n", rsp.Data)
	}
	if rsp.Res.Log != "" {
		fmt.Printf("-> log: %s\n", rsp.Res.Log)
	}

	if verbose {
		fmt.Println("")
	}

}

// NOTE: s is interpreted as a string unless prefixed with 0x
func stringOrHexToBytes(s string) ([]byte, error) {
	if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
		b, err := hex.DecodeString(s[2:])
		if err != nil {
			err = fmt.Errorf("Error decoding hex argument: %s", err.Error())
			return nil, err
		}
		return b, nil
	}

	if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
		err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
		return nil, err
	}

	return []byte(s[1 : len(s)-1]), nil
}

func introduction (c *cli.Context) {
	fmt.Println("\n...........................................")
	fmt.Println("Blockfreight™ Go App")
	fmt.Println("Address "+c.GlobalString("address"))
	fmt.Println("BFT Implementation:  "+c.GlobalString("bft"))
	fmt.Println("...........................................\n")
	/*name := "Blockfreight Community"
    if c.NArg() > 0 {
      name = c.Args().Get(0)
    }
    if c.String("lang") == "ES" {	//ISO 639-1
      fmt.Println("Hola", name)
    } else {
      fmt.Println("Hello", name)
    }*/
}<commit_msg>Modify publish_bftx and query giving []byte as parameter from reading JSON<commit_after>package main

import (
	"bufio"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/tendermint/abci/client"
	"github.com/tendermint/abci/types"
	. "github.com/tendermint/go-common"
	"github.com/urfave/cli"
	"github.com/blockfreight/blockfreight-alpha/blockfreight/bft/validator"
)

//structure for data passed to print response
// variables must be exposed for JSON to read
type response struct {
	Res       types.Result
	Data      string
	PrintCode bool
	Code      string
}

func newResponse(res types.Result, data string, printCode bool) *response {
	rsp := &response{
		Res:       res,
		Data:      data,
		PrintCode: printCode,
		Code:      "",
	}

	if printCode {
		rsp.Code = res.Code.String()
	}

	return rsp
}

// client is a global variable so it can be reused by the console
var client abcicli.Client

func main() {

	//workaround for the cli library (https://github.com/urfave/cli/issues/565)
	cli.OsExiter = func(_ int) {}

	app := cli.NewApp()
	app.Name = "bft-cli"
	app.Usage = "bft-cli [command] [args...]"
	app.Version = "0.0.1"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "address",
			Value: "tcp://127.0.0.1:46658",
			Usage: "address of application socket",
		},
		cli.StringFlag{
			Name:  "bft",
			Value: "socket",
			Usage: "socket or grpc",
		},
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "print the command and results as if it were a console session",
		},
		/*cli.StringFlag{
      		Name: "lang",
      		Value: "english",
      		Usage: "language for the greeting",
    	},*/
    	cli.StringFlag{
      		Name: "json_path",
      		Value: "./files/bf_tx_example.json",
      		Usage: "define the source path where the json is",
    	},
	}
	app.Commands = []cli.Command{
		{
			Name:  "batch",
			Usage: "Run a batch of bft commands against an application",
			Action: func(c *cli.Context) error {
				return cmdBatch(app, c)
			},
		},
		{
			Name:  "console",
			Usage: "Start an interactive bft console for multiple commands",
			Action: func(c *cli.Context) error {
				return cmdConsole(app, c)
			},
		},
		{
			Name:  "echo",
			Usage: "Have the application echo a message",
			Action: func(c *cli.Context) error {
				return cmdEcho(c)
			},
		},
		{
			Name:  "info",
			Usage: "Get some info about the application",
			Action: func(c *cli.Context) error {
				return cmdInfo(c)
			},
		},
		{
			Name:  "set_option",
			Usage: "Set an option on the application",
			Action: func(c *cli.Context) error {
				return cmdSetOption(c)
			},
		},
		{
			Name:  "publish_bftx",		//"deliver_tx",
			Usage: "Deliver a new bftx to application",
			Action: func(c *cli.Context) error {
				return cmdDeliverTx(c)
			},
		},
		{
			Name:  "check_bftx",
			Usage: "Validate a bftx",
			Action: func(c *cli.Context) error {
				return cmdCheckTx(c)
			},
		},
		{
			Name:  "commit",
			Usage: "Commit the application state and return the Merkle root hash",
			Action: func(c *cli.Context) error {
				return cmdCommit(c)
			},
		},
		{
			Name:  "query",
			Usage: "Query application state",
			Action: func(c *cli.Context) error {
				return cmdQuery(c)
			},
		},
		{
			Name:  "validate_bol",
			Usage: "Verify the structure of the bill of lading",
			Action: func(c *cli.Context) error {
				return cmdValidateBol(c)
			},
		},
	}
	app.Before = before
	err := app.Run(os.Args)
	if err != nil {
		Exit(err.Error())
	}

}

func before(c *cli.Context) error {
	introduction(c)
	if client == nil {
		var err error
		client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("bft"), false)
		if err != nil {
			Exit(err.Error())
		}
	}
	return nil
}

// badCmd is called when we invoke with an invalid first argument (just for console for now)
func badCmd(c *cli.Context, cmd string) {
	fmt.Println("Unknown command:", cmd)
	fmt.Println("Please try one of the following:")
	fmt.Println("")
	cli.DefaultAppComplete(c)
}

//Generates new Args array based off of previous call args to maintain flag persistence
func persistentArgs(line []byte) []string {

	//generate the arguments to run from orginal os.Args
	// to maintain flag arguments
	args := os.Args
	args = args[:len(args)-1] // remove the previous command argument

	if len(line) > 0 { //prevents introduction of extra space leading to argument parse errors
		args = append(args, strings.Split(string(line), " ")...)
	}
	return args
}

//--------------------------------------------------------------------------------

func cmdBatch(app *cli.App, c *cli.Context) error {
	bufReader := bufio.NewReader(os.Stdin)
	for {
		line, more, err := bufReader.ReadLine()
		if more {
			return errors.New("Input line is too long")
		} else if err == io.EOF {
			break
		} else if len(line) == 0 {
			continue
		} else if err != nil {
			return err
		}

		args := persistentArgs(line)
		app.Run(args) //cli prints error within its func call
	}
	return nil
}

func cmdConsole(app *cli.App, c *cli.Context) error {
	// don't hard exit on mistyped commands (eg. check vs check_tx)
	app.CommandNotFound = badCmd

	for {
		fmt.Printf("\n> ")
		bufReader := bufio.NewReader(os.Stdin)
		line, more, err := bufReader.ReadLine()
		if more {
			return errors.New("Input is too long")
		} else if err != nil {
			return err
		}

		args := persistentArgs(line)
		app.Run(args) //cli prints error within its func call
	}
}

// Have the application echo a message
func cmdEcho(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command echo takes 1 argument")
	}
	res := client.EchoSync(args[0])
	rsp := newResponse(res, string(res.Data), false)
	printResponse(c, rsp)
	return nil
}

// Get some info from the application
func cmdInfo(c *cli.Context) error {
	resInfo, err := client.InfoSync()
	if err != nil {
		return err
	}
	rsp := newResponse(types.Result{}, string(resInfo.Data), false)
	printResponse(c, rsp)
	return nil
}

// Set an option on the application
func cmdSetOption(c *cli.Context) error {
	args := c.Args()
	if len(args) != 2 {
		return errors.New("Command set_option takes 2 arguments (key, value)")
	}
	res := client.SetOptionSync(args[0], args[1])
	rsp := newResponse(res, Fmt("%s=%s", args[0], args[1]), false)
	printResponse(c, rsp)
	return nil
}

// Append a new tx to application
func cmdDeliverTx(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command deliver_tx takes 1 argument")
	}
	/*txBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}*/
	txBytes := validator.ReadJSON(args[0])
	res := client.DeliverTxSync(txBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

// Validate a tx
func cmdCheckTx(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command check_tx takes 1 argument")
	}
	txBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}
	res := client.CheckTxSync(txBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

// Get application Merkle root hash
func cmdCommit(c *cli.Context) error {
	res := client.CommitSync()
	rsp := newResponse(res, Fmt("0x%X", res.Data), false)
	printResponse(c, rsp)
	return nil
}

// Query application state
func cmdQuery(c *cli.Context) error {
	args := c.Args()
	if len(args) != 1 {
		return errors.New("Command query takes 1 argument")
	}
	/*queryBytes, err := stringOrHexToBytes(c.Args()[0])
	if err != nil {
		return err
	}*/
	queryBytes := validator.ReadJSON(args[0])
	res := client.QuerySync(queryBytes)
	rsp := newResponse(res, string(res.Data), true)
	printResponse(c, rsp)
	return nil
}

//Verify the structure of the bill of lading
func cmdValidateBol(c *cli.Context) error {
	args := c.Args()
	if len(args) > 1 {
		return errors.New("Command validate_bol takes 1 argument")
	}
	res := client.EchoSync(validator.ValidateBoL(args[0], false))
	rsp := newResponse(res, string(res.Data), false)
	printResponse(c, rsp)
	return nil
}

//--------------------------------------------------------------------------------

func printResponse(c *cli.Context, rsp *response) {

	verbose := c.GlobalBool("verbose")

	if verbose {
		fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " "))
	}

	if rsp.PrintCode {
		fmt.Printf("-> code: %s\n", rsp.Code)
	}

	//if pr.res.Error != "" {
	//	fmt.Printf("-> error: %s\n", pr.res.Error)
	//}

	if rsp.Data != "" {
		fmt.Printf("-> blockfreight data: %s\n", rsp.Data)
	}
	if rsp.Res.Log != "" {
		fmt.Printf("-> log: %s\n", rsp.Res.Log)
	}

	if verbose {
		fmt.Println("")
	}

}

// NOTE: s is interpreted as a string unless prefixed with 0x
func stringOrHexToBytes(s string) ([]byte, error) {
	if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
		b, err := hex.DecodeString(s[2:])
		if err != nil {
			err = fmt.Errorf("Error decoding hex argument: %s", err.Error())
			return nil, err
		}
		return b, nil
	}

	if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
		err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
		return nil, err
	}
	return []byte(s[1 : len(s)-1]), nil
}

func introduction (c *cli.Context) {
	fmt.Println("\n...........................................")
	fmt.Println("Blockfreight™ Go App")
	fmt.Println("Address "+c.GlobalString("address"))
	fmt.Println("BFT Implementation:  "+c.GlobalString("bft"))
	fmt.Println("...........................................\n")
	/*name := "Blockfreight Community"
    if c.NArg() > 0 {
      name = c.Args().Get(0)
    }
    if c.String("lang") == "ES" {	//ISO 639-1
      fmt.Println("Hola", name)
    } else {
      fmt.Println("Hello", name)
    }*/
}<|endoftext|>


<commit_before>package toiltest

// ToilRecorder is an implementation of toil.Toiler, as well as an implementation
// of toil.Recovereder and toil.Terminateder too; it counts the number of times
// its Toil() method has been called and has not returned (i.e., is blocking) as
// well as allows custom code to run when its Recovered(), Terminated(), or Toil()
// methods are called.
type ToilRecorder struct {
	panicCh     chan struct{value interface{}}
	terminateCh chan struct{doneCh chan struct{}}
	numToiling int

	recoveredFunc  func(interface{})
	terminatedFunc func()
	toilFunc       func()

	returnedNoticeFunc  func()
	panickedNoticeFunc  func(interface{})
	recoveredNoticeFunc func(interface{})
}

// NewRecorder returns an initialized ToilRecorder.
func NewRecorder() *ToilRecorder {
	panicCh := make(chan struct{value interface{}})

	terminateCh := make(chan struct{doneCh chan struct{}})

	toilRecorder := ToilRecorder{
		panicCh:panicCh,
		terminateCh:terminateCh,
	}

	return &toilRecorder
}

// RecoveredFunc registers the "recovered function" that will be called as part of when the
// ToilRecorder's Recovered() method is called.
func (toiler *ToilRecorder) RecoveredFunc(fn func(interface{})) {
	toiler.recoveredFunc = fn
}

// TerminateFunc registers the "terminated function" that will be called as part of when the
// ToilRecorder's Terminated() method is called.
func (toiler *ToilRecorder) TerminatedFunc(fn func()) {
	toiler.terminatedFunc = fn
}

// ToilFunc registers the "toil function" that will be called as part of when the
// ToilRecorder's Toil() method is called.
func (toiler *ToilRecorder) ToilFunc(fn func()) {
	toiler.toilFunc = fn
}

// ReturnedNoticeFunc registers the func that will be called as part of when the
// ReturnedNotice() method is called.
func (toiler *ToilRecorder) ReturnedNoticeFunc(fn func()) {
	toiler.returnedNoticeFunc = fn
}

// PanickedNoticeFunc registers the func that will be called as part of when the
// PanickedNotice() method is called.
func (toiler *ToilRecorder) PanickedNoticeFunc(fn func(interface{})) {
	toiler.panickedNoticeFunc = fn
}

// RecoveredNoticeFunc registers the func that will be called as part of when the
// RecoveredNotice() method is called.
func (toiler *ToilRecorder) RecoveredNoticeFunc(fn func(interface{})) {
	toiler.recoveredNoticeFunc = fn
}

// NumToiling returns the number of active calls to its Toil() method.
func (toiler *ToilRecorder) NumToiling() int {
	return toiler.numToiling
}

// Panic causes one of the still active (i.e., blocking) calls to Toil()
// on itself to panic().
//
// If there are not active (i.e., blocking) calls to Toil() on itself,
// then it will block until there is one.
//
// One use for this method is to check if its Recovered() method was
// call by the toil.Group it is in (due to the panic()).
func (toiler *ToilRecorder) Panic(value interface{}) {

	toiler.panicCh <- struct{value interface{}}{
		value:value,
	}

//@TODO: Is there a way to wait for this to complete?
}

// Terminate causes one of the still active (i.e., blocking) calls to Toil()
// on itself to return gracefully.
//
// If there are not active (i.e., blocking) calls to Toil() on itself,
// then it will block until there is one.
//
// One use for this method is to check if its Terminated() method was
// call by the toil.Group it is in (due to the gracefull return).
func (toiler *ToilRecorder) Terminate() {
	doneCh := make(chan struct{})

	toiler.terminateCh <- struct{doneCh chan struct{}}{
		doneCh:doneCh,
	}

	<-doneCh
}

// Toil is part of the toil.Toiler interface.
func (toiler *ToilRecorder) Toil() {
	toiler.numToiling++

	if nil != toiler.toilFunc {
		toiler.toilFunc()
	}

	var doneCh chan struct{}

	select {
	case panicRequest := <-toiler.panicCh:
		panic(panicRequest.value)
	case terminateRequest := <-toiler.terminateCh:
		doneCh = terminateRequest.doneCh
	}

	toiler.numToiling--

	if nil != doneCh {
		doneCh <- struct{}{}
	}
}

// Recovered is part of the toil.toilRecovereder interface.
func (toiler *ToilRecorder) Recovered(panicValue interface{}) {
	if nil != toiler.recoveredFunc {
		toiler.recoveredFunc(panicValue)
	}
}

// Terminated is part of the toil.toilTerminateder interface.
func (toiler *ToilRecorder) Terminated() {
	if nil != toiler.terminatedFunc {
		toiler.terminatedFunc()
	}
}

// ReturnedNotice will call the func registerd with the call to the
// ReturnedNoticeFunc method.
func (toiler *ToilRecorder) ReturnedNotice() {
	if nil != toiler.returnedNoticeFunc {
		toiler.returnedNoticeFunc()
	}
}

// PanickedNotice will call the func registerd with the call to the
// PanickedNoticeFunc method.
func (toiler *ToilRecorder) PanickedNotice(panicValue interface{}) {
	if nil != toiler.panickedNoticeFunc {
		toiler.panickedNoticeFunc(panicValue)
	}
}

// RecoveredNotice will call the func registerd with the call to the
// RecoveredNoticeFunc method.
func (toiler *ToilRecorder) RecoveredNotice(panicValue interface{}) {
	if nil != toiler.recoveredNoticeFunc {
		toiler.recoveredNoticeFunc(panicValue)
	}
}
<commit_msg>removed recoveredFunc related stuff.<commit_after>package toiltest

// ToilRecorder is an implementation of toil.Toiler, as well as has PanickedNotice,
// ReturnedNotice and RecoveredNotice methods as well. It counts the number of times
// its Toil() method has been called and has not returned (i.e., is blocking) as
// well as allows custom code to run when its PanickedNotice, ReturnedNotice(),
// RecoveredNotice(), or Toil() methods are called.
type ToilRecorder struct {
	panicCh     chan struct{value interface{}}
	terminateCh chan struct{doneCh chan struct{}}
	numToiling int

	terminatedFunc func()
	toilFunc       func()

	returnedNoticeFunc  func()
	panickedNoticeFunc  func(interface{})
	recoveredNoticeFunc func(interface{})
}

// NewRecorder returns an initialized ToilRecorder.
func NewRecorder() *ToilRecorder {
	panicCh := make(chan struct{value interface{}})

	terminateCh := make(chan struct{doneCh chan struct{}})

	toilRecorder := ToilRecorder{
		panicCh:panicCh,
		terminateCh:terminateCh,
	}

	return &toilRecorder
}

// TerminateFunc registers the "terminated function" that will be called as part of when the
// ToilRecorder's Terminated() method is called.
func (toiler *ToilRecorder) TerminatedFunc(fn func()) {
	toiler.terminatedFunc = fn
}

// ToilFunc registers the "toil function" that will be called as part of when the
// ToilRecorder's Toil() method is called.
func (toiler *ToilRecorder) ToilFunc(fn func()) {
	toiler.toilFunc = fn
}

// ReturnedNoticeFunc registers the func that will be called as part of when the
// ReturnedNotice() method is called.
func (toiler *ToilRecorder) ReturnedNoticeFunc(fn func()) {
	toiler.returnedNoticeFunc = fn
}

// PanickedNoticeFunc registers the func that will be called as part of when the
// PanickedNotice() method is called.
func (toiler *ToilRecorder) PanickedNoticeFunc(fn func(interface{})) {
	toiler.panickedNoticeFunc = fn
}

// RecoveredNoticeFunc registers the func that will be called as part of when the
// RecoveredNotice() method is called.
func (toiler *ToilRecorder) RecoveredNoticeFunc(fn func(interface{})) {
	toiler.recoveredNoticeFunc = fn
}

// NumToiling returns the number of active calls to its Toil() method.
func (toiler *ToilRecorder) NumToiling() int {
	return toiler.numToiling
}

// Panic causes one of the still active (i.e., blocking) calls to Toil()
// on itself to panic().
//
// If there are not active (i.e., blocking) calls to Toil() on itself,
// then it will block until there is one.
//
// One use for this method is to check if its RecoveredNotice() method was
// called by the toil.Group it is in (due to the panic()).
func (toiler *ToilRecorder) Panic(value interface{}) {

	toiler.panicCh <- struct{value interface{}}{
		value:value,
	}

//@TODO: Is there a way to wait for this to complete?
}

// Terminate causes one of the still active (i.e., blocking) calls to Toil()
// on itself to return gracefully.
//
// If there are not active (i.e., blocking) calls to Toil() on itself,
// then it will block until there is one.
//
// One use for this method is to check if its Terminated() method was
// call by the toil.Group it is in (due to the gracefull return).
func (toiler *ToilRecorder) Terminate() {
	doneCh := make(chan struct{})

	toiler.terminateCh <- struct{doneCh chan struct{}}{
		doneCh:doneCh,
	}

	<-doneCh
}

// Toil is part of the toil.Toiler interface.
func (toiler *ToilRecorder) Toil() {
	toiler.numToiling++

	if nil != toiler.toilFunc {
		toiler.toilFunc()
	}

	var doneCh chan struct{}

	select {
	case panicRequest := <-toiler.panicCh:
		panic(panicRequest.value)
	case terminateRequest := <-toiler.terminateCh:
		doneCh = terminateRequest.doneCh
	}

	toiler.numToiling--

	if nil != doneCh {
		doneCh <- struct{}{}
	}
}

// Terminated is part of the toil.toilTerminateder interface.
func (toiler *ToilRecorder) Terminated() {
	if nil != toiler.terminatedFunc {
		toiler.terminatedFunc()
	}
}

// ReturnedNotice will call the func registerd with the call to the
// ReturnedNoticeFunc method.
func (toiler *ToilRecorder) ReturnedNotice() {
	if nil != toiler.returnedNoticeFunc {
		toiler.returnedNoticeFunc()
	}
}

// PanickedNotice will call the func registerd with the call to the
// PanickedNoticeFunc method.
func (toiler *ToilRecorder) PanickedNotice(panicValue interface{}) {
	if nil != toiler.panickedNoticeFunc {
		toiler.panickedNoticeFunc(panicValue)
	}
}

// RecoveredNotice will call the func registerd with the call to the
// RecoveredNoticeFunc method.
func (toiler *ToilRecorder) RecoveredNotice(panicValue interface{}) {
	if nil != toiler.recoveredNoticeFunc {
		toiler.recoveredNoticeFunc(panicValue)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015-2017 Gravitational, Inc.

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

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

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

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"testing"
	"time"

	"golang.org/x/crypto/ssh"

	"github.com/gravitational/teleport/lib/backend"
	"github.com/gravitational/teleport/lib/client"
	"github.com/gravitational/teleport/lib/defaults"
	"github.com/gravitational/teleport/lib/service"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/teleport/tool/tsh/common"

	"gopkg.in/check.v1"
)

// bootstrap check
func TestTshMain(t *testing.T) {
	utils.InitLoggerForTests(testing.Verbose())
	check.TestingT(t)
}

// register test suite
type MainTestSuite struct{}

var _ = check.Suite(&MainTestSuite{})

func (s *MainTestSuite) SetUpSuite(c *check.C) {
	dir := client.FullProfilePath("")
	os.RemoveAll(dir)
}

func (s *MainTestSuite) TestMakeClient(c *check.C) {
	var conf CLIConf

	// empty config won't work:
	tc, err := makeClient(&conf, true)
	c.Assert(tc, check.IsNil)
	c.Assert(err, check.NotNil)

	// minimal configuration (with defaults)
	conf.Proxy = "proxy"
	conf.UserHost = "localhost"
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc, check.NotNil)
	c.Assert(tc.Config.SSHProxyAddr, check.Equals, "proxy:3023")
	c.Assert(tc.Config.WebProxyAddr, check.Equals, "proxy:3080")
	localUser, err := client.Username()
	c.Assert(err, check.IsNil)
	c.Assert(tc.Config.HostLogin, check.Equals, localUser)
	c.Assert(tc.Config.KeyTTL, check.Equals, defaults.CertDuration)

	// specific configuration
	conf.MinsToLive = 5
	conf.UserHost = "root@localhost"
	conf.NodePort = 46528
	conf.LocalForwardPorts = []string{"80:remote:180"}
	conf.DynamicForwardedPorts = []string{":8080"}
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc.Config.KeyTTL, check.Equals, time.Minute*time.Duration(conf.MinsToLive))
	c.Assert(tc.Config.HostLogin, check.Equals, "root")
	c.Assert(tc.Config.LocalForwardPorts, check.DeepEquals, client.ForwardedPorts{
		{
			SrcIP:    "127.0.0.1",
			SrcPort:  80,
			DestHost: "remote",
			DestPort: 180,
		},
	})
	c.Assert(tc.Config.DynamicForwardedPorts, check.DeepEquals, client.DynamicForwardedPorts{
		{
			SrcIP:   "127.0.0.1",
			SrcPort: 8080,
		},
	})

	// Set up a test proxy service.
	ports, err := utils.GetFreeTCPPorts(3)
	c.Assert(err, check.IsNil)
	authAddr := utils.MustParseAddr(fmt.Sprintf("127.0.0.1:%v", ports.Pop()))
	proxyWebAddr := utils.MustParseAddr(fmt.Sprintf("127.0.0.1:%v", ports.Pop()))
	proxyPublicSSHAddr := utils.MustParseAddr(fmt.Sprintf("127.0.0.1:%v", ports.Pop()))

	cfg := service.MakeDefaultConfig()
	cfg.DataDir = c.MkDir()
	cfg.Auth.StorageConfig.Params = backend.Params{defaults.BackendPath: filepath.Join(cfg.DataDir, defaults.BackendDir)}
	cfg.AuthServers = []utils.NetAddr{*authAddr}
	cfg.SSH.Enabled = false
	cfg.Auth.Enabled = true
	cfg.Auth.SSHAddr = *authAddr
	cfg.Proxy.Enabled = true
	cfg.Proxy.WebAddr = *proxyWebAddr
	cfg.Proxy.SSHPublicAddrs = []utils.NetAddr{*proxyPublicSSHAddr}
	cfg.Proxy.DisableReverseTunnel = true
	cfg.Proxy.DisableWebInterface = true

	proxy, err := service.NewTeleport(cfg)
	c.Assert(err, check.IsNil)
	c.Assert(proxy.Start(), check.IsNil)
	defer proxy.Close()

	// Wait for proxy to become ready.
	eventCh := make(chan service.Event, 1)
	proxy.WaitForEvent(proxy.ExitContext(), service.ProxyWebServerReady, eventCh)
	select {
	case <-eventCh:
	case <-time.After(10 * time.Second):
		c.Fatal("proxy web server didn't start after 10s")
	}

	// With provided identity file.
	//
	// makeClient should call Ping on the proxy to fetch SSHProxyAddr, which is
	// different from the default.
	conf = CLIConf{
		Proxy:              proxyWebAddr.String(),
		IdentityFileIn:     "../../fixtures/certs/identities/key-cert-ca.pem",
		Context:            context.Background(),
		InsecureSkipVerify: true,
	}
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc, check.NotNil)
	c.Assert(tc.Config.WebProxyAddr, check.Equals, proxyWebAddr.String())
	c.Assert(tc.Config.SSHProxyAddr, check.Equals, proxyPublicSSHAddr.String())
}

func (s *MainTestSuite) TestIdentityRead(c *check.C) {
	// 3 different types of identities
	ids := []string{
		"cert-key.pem", // cert + key concatenated togther, cert first
		"key-cert.pem", // cert + key concatenated togther, key first
		"key",          // two separate files: key and key-cert.pub
	}
	for _, id := range ids {
		// test reading:
		k, cb, err := common.LoadIdentity(fmt.Sprintf("../../fixtures/certs/identities/%s", id))
		c.Assert(err, check.IsNil)
		c.Assert(k, check.NotNil)
		c.Assert(cb, check.IsNil)

		// test creating an auth method from the key:
		am, err := authFromIdentity(k)
		c.Assert(err, check.IsNil)
		c.Assert(am, check.NotNil)
	}
	k, _, err := common.LoadIdentity("../../fixtures/certs/identities/lonekey")
	c.Assert(k, check.IsNil)
	c.Assert(err, check.NotNil)

	// lets read an indentity which includes a CA cert
	k, hostAuthCallback, err := common.LoadIdentity("../../fixtures/certs/identities/key-cert-ca.pem")
	c.Assert(err, check.IsNil)
	c.Assert(k, check.NotNil)
	c.Assert(hostAuthCallback, check.NotNil)
	// prepare the cluster CA separately
	certBytes, err := ioutil.ReadFile("../../fixtures/certs/identities/ca.pem")
	c.Assert(err, check.IsNil)
	_, hosts, cert, _, _, err := ssh.ParseKnownHosts(certBytes)
	c.Assert(err, check.IsNil)
	var a net.Addr
	// host auth callback must succeed
	err = hostAuthCallback(hosts[0], a, cert)
	c.Assert(err, check.IsNil)

	// load an identity which include TLS certificates
	k, _, err = common.LoadIdentity("../../fixtures/certs/identities/tls.pem")
	c.Assert(err, check.IsNil)
	c.Assert(k, check.NotNil)
	c.Assert(k.TLSCert, check.NotNil)
	// generate a TLS client config
	conf, err := k.ClientTLSConfig()
	c.Assert(err, check.IsNil)
	c.Assert(conf, check.NotNil)
	// ensure that at least root CA was successfully loaded
	if len(conf.RootCAs.Subjects()) < 1 {
		c.Errorf("Failed to load TLS CAs from identity file")
	}
}

func (s *MainTestSuite) TestOptions(c *check.C) {
	tests := []struct {
		inOptions  []string
		outError   bool
		outOptions Options
	}{
		// Valid
		{
			inOptions: []string{
				"AddKeysToAgent yes",
			},
			outError: false,
			outOptions: Options{
				AddKeysToAgent:        true,
				ForwardAgent:          false,
				RequestTTY:            false,
				StrictHostKeyChecking: true,
			},
		},
		// Valid
		{
			inOptions: []string{
				"AddKeysToAgent=yes",
			},
			outError: false,
			outOptions: Options{
				AddKeysToAgent:        true,
				ForwardAgent:          false,
				RequestTTY:            false,
				StrictHostKeyChecking: true,
			},
		},
		// Invalid value.
		{
			inOptions: []string{
				"AddKeysToAgent foo",
			},
			outError:   true,
			outOptions: Options{},
		},
		// Invalid key.
		{
			inOptions: []string{
				"foo foo",
			},
			outError:   true,
			outOptions: Options{},
		},
		// Incomplete option.
		{
			inOptions: []string{
				"AddKeysToAgent",
			},
			outError:   true,
			outOptions: Options{},
		},
	}

	for _, tt := range tests {
		options, err := parseOptions(tt.inOptions)
		if tt.outError {
			c.Assert(err, check.NotNil)
			continue
		} else {
			c.Assert(err, check.IsNil)
		}

		c.Assert(options.AddKeysToAgent, check.Equals, tt.outOptions.AddKeysToAgent)
		c.Assert(options.ForwardAgent, check.Equals, tt.outOptions.ForwardAgent)
		c.Assert(options.RequestTTY, check.Equals, tt.outOptions.RequestTTY)
		c.Assert(options.StrictHostKeyChecking, check.Equals, tt.outOptions.StrictHostKeyChecking)
	}
}
<commit_msg>Auto assign listening ports in tool/tsh tests<commit_after>/*
Copyright 2015-2017 Gravitational, Inc.

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

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

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

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"testing"
	"time"

	"golang.org/x/crypto/ssh"

	"github.com/gravitational/teleport"
	"github.com/gravitational/teleport/lib/backend"
	"github.com/gravitational/teleport/lib/client"
	"github.com/gravitational/teleport/lib/defaults"
	"github.com/gravitational/teleport/lib/service"
	"github.com/gravitational/teleport/lib/services"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/teleport/tool/tsh/common"

	"gopkg.in/check.v1"
)

// bootstrap check
func TestTshMain(t *testing.T) {
	utils.InitLoggerForTests(testing.Verbose())
	check.TestingT(t)
}

// register test suite
type MainTestSuite struct{}

var _ = check.Suite(&MainTestSuite{})

func (s *MainTestSuite) SetUpSuite(c *check.C) {
	dir := client.FullProfilePath("")
	os.RemoveAll(dir)
}

func (s *MainTestSuite) TestMakeClient(c *check.C) {
	var conf CLIConf

	// empty config won't work:
	tc, err := makeClient(&conf, true)
	c.Assert(tc, check.IsNil)
	c.Assert(err, check.NotNil)

	// minimal configuration (with defaults)
	conf.Proxy = "proxy"
	conf.UserHost = "localhost"
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc, check.NotNil)
	c.Assert(tc.Config.SSHProxyAddr, check.Equals, "proxy:3023")
	c.Assert(tc.Config.WebProxyAddr, check.Equals, "proxy:3080")
	localUser, err := client.Username()
	c.Assert(err, check.IsNil)
	c.Assert(tc.Config.HostLogin, check.Equals, localUser)
	c.Assert(tc.Config.KeyTTL, check.Equals, defaults.CertDuration)

	// specific configuration
	conf.MinsToLive = 5
	conf.UserHost = "root@localhost"
	conf.NodePort = 46528
	conf.LocalForwardPorts = []string{"80:remote:180"}
	conf.DynamicForwardedPorts = []string{":8080"}
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc.Config.KeyTTL, check.Equals, time.Minute*time.Duration(conf.MinsToLive))
	c.Assert(tc.Config.HostLogin, check.Equals, "root")
	c.Assert(tc.Config.LocalForwardPorts, check.DeepEquals, client.ForwardedPorts{
		{
			SrcIP:    "127.0.0.1",
			SrcPort:  80,
			DestHost: "remote",
			DestPort: 180,
		},
	})
	c.Assert(tc.Config.DynamicForwardedPorts, check.DeepEquals, client.DynamicForwardedPorts{
		{
			SrcIP:   "127.0.0.1",
			SrcPort: 8080,
		},
	})

	randomLocalAddr := utils.NetAddr{AddrNetwork: "tcp", Addr: "127.0.0.1:0"}
	const staticToken = "test-static-token"

	// Set up a test auth server.
	//
	// We need this to get a random port assigned to it and allow parallel
	// execution of this test.
	cfg := service.MakeDefaultConfig()
	cfg.DataDir = c.MkDir()
	cfg.AuthServers = []utils.NetAddr{randomLocalAddr}
	cfg.Auth.StorageConfig.Params = backend.Params{defaults.BackendPath: filepath.Join(cfg.DataDir, defaults.BackendDir)}
	cfg.Auth.StaticTokens, err = services.NewStaticTokens(services.StaticTokensSpecV2{
		StaticTokens: []services.ProvisionTokenV1{{
			Roles:   []teleport.Role{teleport.RoleProxy},
			Expires: time.Now().Add(time.Minute),
			Token:   staticToken,
		}},
	})
	cfg.SSH.Enabled = false
	cfg.Auth.Enabled = true
	cfg.Auth.SSHAddr = randomLocalAddr
	cfg.Proxy.Enabled = false

	auth, err := service.NewTeleport(cfg)
	c.Assert(err, check.IsNil)
	c.Assert(auth.Start(), check.IsNil)
	defer auth.Close()

	// Wait for proxy to become ready.
	eventCh := make(chan service.Event, 1)
	auth.WaitForEvent(auth.ExitContext(), service.AuthTLSReady, eventCh)
	select {
	case <-eventCh:
	case <-time.After(10 * time.Second):
		c.Fatal("auth server didn't start after 10s")
	}

	authAddr, err := auth.AuthSSHAddr()
	c.Assert(err, check.IsNil)

	// Set up a test proxy service.
	proxyPublicSSHAddr := utils.NetAddr{AddrNetwork: "tcp", Addr: "proxy.example.com:22"}
	cfg = service.MakeDefaultConfig()
	cfg.DataDir = c.MkDir()
	cfg.AuthServers = []utils.NetAddr{*authAddr}
	cfg.Token = staticToken
	cfg.SSH.Enabled = false
	cfg.Auth.Enabled = false
	cfg.Proxy.Enabled = true
	cfg.Proxy.WebAddr = randomLocalAddr
	cfg.Proxy.SSHPublicAddrs = []utils.NetAddr{proxyPublicSSHAddr}
	cfg.Proxy.DisableReverseTunnel = true
	cfg.Proxy.DisableWebInterface = true

	proxy, err := service.NewTeleport(cfg)
	c.Assert(err, check.IsNil)
	c.Assert(proxy.Start(), check.IsNil)
	defer proxy.Close()

	// Wait for proxy to become ready.
	proxy.WaitForEvent(proxy.ExitContext(), service.ProxyWebServerReady, eventCh)
	select {
	case <-eventCh:
	case <-time.After(10 * time.Second):
		c.Fatal("proxy web server didn't start after 10s")
	}

	proxyWebAddr, err := proxy.ProxyWebAddr()
	c.Assert(err, check.IsNil)

	// With provided identity file.
	//
	// makeClient should call Ping on the proxy to fetch SSHProxyAddr, which is
	// different from the default.
	conf = CLIConf{
		Proxy:              proxyWebAddr.String(),
		IdentityFileIn:     "../../fixtures/certs/identities/key-cert-ca.pem",
		Context:            context.Background(),
		InsecureSkipVerify: true,
	}
	tc, err = makeClient(&conf, true)
	c.Assert(err, check.IsNil)
	c.Assert(tc, check.NotNil)
	c.Assert(tc.Config.WebProxyAddr, check.Equals, proxyWebAddr.String())
	c.Assert(tc.Config.SSHProxyAddr, check.Equals, proxyPublicSSHAddr.String())
}

func (s *MainTestSuite) TestIdentityRead(c *check.C) {
	// 3 different types of identities
	ids := []string{
		"cert-key.pem", // cert + key concatenated togther, cert first
		"key-cert.pem", // cert + key concatenated togther, key first
		"key",          // two separate files: key and key-cert.pub
	}
	for _, id := range ids {
		// test reading:
		k, cb, err := common.LoadIdentity(fmt.Sprintf("../../fixtures/certs/identities/%s", id))
		c.Assert(err, check.IsNil)
		c.Assert(k, check.NotNil)
		c.Assert(cb, check.IsNil)

		// test creating an auth method from the key:
		am, err := authFromIdentity(k)
		c.Assert(err, check.IsNil)
		c.Assert(am, check.NotNil)
	}
	k, _, err := common.LoadIdentity("../../fixtures/certs/identities/lonekey")
	c.Assert(k, check.IsNil)
	c.Assert(err, check.NotNil)

	// lets read an indentity which includes a CA cert
	k, hostAuthCallback, err := common.LoadIdentity("../../fixtures/certs/identities/key-cert-ca.pem")
	c.Assert(err, check.IsNil)
	c.Assert(k, check.NotNil)
	c.Assert(hostAuthCallback, check.NotNil)
	// prepare the cluster CA separately
	certBytes, err := ioutil.ReadFile("../../fixtures/certs/identities/ca.pem")
	c.Assert(err, check.IsNil)
	_, hosts, cert, _, _, err := ssh.ParseKnownHosts(certBytes)
	c.Assert(err, check.IsNil)
	var a net.Addr
	// host auth callback must succeed
	err = hostAuthCallback(hosts[0], a, cert)
	c.Assert(err, check.IsNil)

	// load an identity which include TLS certificates
	k, _, err = common.LoadIdentity("../../fixtures/certs/identities/tls.pem")
	c.Assert(err, check.IsNil)
	c.Assert(k, check.NotNil)
	c.Assert(k.TLSCert, check.NotNil)
	// generate a TLS client config
	conf, err := k.ClientTLSConfig()
	c.Assert(err, check.IsNil)
	c.Assert(conf, check.NotNil)
	// ensure that at least root CA was successfully loaded
	if len(conf.RootCAs.Subjects()) < 1 {
		c.Errorf("Failed to load TLS CAs from identity file")
	}
}

func (s *MainTestSuite) TestOptions(c *check.C) {
	tests := []struct {
		inOptions  []string
		outError   bool
		outOptions Options
	}{
		// Valid
		{
			inOptions: []string{
				"AddKeysToAgent yes",
			},
			outError: false,
			outOptions: Options{
				AddKeysToAgent:        true,
				ForwardAgent:          false,
				RequestTTY:            false,
				StrictHostKeyChecking: true,
			},
		},
		// Valid
		{
			inOptions: []string{
				"AddKeysToAgent=yes",
			},
			outError: false,
			outOptions: Options{
				AddKeysToAgent:        true,
				ForwardAgent:          false,
				RequestTTY:            false,
				StrictHostKeyChecking: true,
			},
		},
		// Invalid value.
		{
			inOptions: []string{
				"AddKeysToAgent foo",
			},
			outError:   true,
			outOptions: Options{},
		},
		// Invalid key.
		{
			inOptions: []string{
				"foo foo",
			},
			outError:   true,
			outOptions: Options{},
		},
		// Incomplete option.
		{
			inOptions: []string{
				"AddKeysToAgent",
			},
			outError:   true,
			outOptions: Options{},
		},
	}

	for _, tt := range tests {
		options, err := parseOptions(tt.inOptions)
		if tt.outError {
			c.Assert(err, check.NotNil)
			continue
		} else {
			c.Assert(err, check.IsNil)
		}

		c.Assert(options.AddKeysToAgent, check.Equals, tt.outOptions.AddKeysToAgent)
		c.Assert(options.ForwardAgent, check.Equals, tt.outOptions.ForwardAgent)
		c.Assert(options.RequestTTY, check.Equals, tt.outOptions.RequestTTY)
		c.Assert(options.StrictHostKeyChecking, check.Equals, tt.outOptions.StrictHostKeyChecking)
	}
}
<|endoftext|>


<commit_before>package vmx

import (
	"context"
	"fmt"
	"log"
	"path/filepath"

	vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

// StepCloneVMX takes a VMX file and clones the VM into the output directory.
type StepCloneVMX struct {
	OutputDir string
	Path      string
	VMName    string
}

type vmxAdapter struct {
	// The string portion of the address used in the vmx file
	strAddr string
	// Max address for adapter, controller, or controller channel
	aAddrMax int
	// Max address for device or channel supported by adapter
	dAddrMax int
}

const (
	// VMware Configuration Maximums - Virtual Hardware Versions 13/14
	//
	// Specifying the max numbers for the adapter/controller:bus/channel
	// *address* as opposed to specifying the maximums as per the VMware
	// documentation allows consistent (inclusive) treatment when looping
	// over each adapter/controller type
	//
	// SCSI - Address range: scsi0:0 to scsi3:15
	scsiAddrName       = "scsi" // String part of address used in the vmx file
	maxSCSIAdapterAddr = 3      // Max 4 adapters
	maxSCSIDeviceAddr  = 15     // Max 15 devices per adapter; ID 7 is the HBA
	// SATA - Address range: sata0:0 to scsi3:29
	sataAddrName       = "sata" // String part of address used in the vmx file
	maxSATAAdapterAddr = 3      // Max 4 controllers
	maxSATADeviceAddr  = 29     // Max 30 devices per controller
	// NVMe - Address range: nvme0:0 to nvme3:14
	nvmeAddrName       = "nvme" // String part of address used in the vmx file
	maxNVMeAdapterAddr = 3      // Max 4 adapters
	maxNVMeDeviceAddr  = 14     // Max 15 devices per adapter
	// IDE - Address range: ide0:0 to ide1:1
	ideAddrName       = "ide" // String part of address used in the vmx file
	maxIDEAdapterAddr = 1     // One controller with primary/secondary channels
	maxIDEDeviceAddr  = 1     // Each channel supports master and slave
)

var (
	scsiAdapter = vmxAdapter{
		strAddr:  scsiAddrName,
		aAddrMax: maxSCSIAdapterAddr,
		dAddrMax: maxSCSIDeviceAddr,
	}
	sataAdapter = vmxAdapter{
		strAddr:  sataAddrName,
		aAddrMax: maxSATAAdapterAddr,
		dAddrMax: maxSATADeviceAddr,
	}
	nvmeAdapter = vmxAdapter{
		strAddr:  nvmeAddrName,
		aAddrMax: maxNVMeAdapterAddr,
		dAddrMax: maxNVMeDeviceAddr,
	}
	ideAdapter = vmxAdapter{
		strAddr:  ideAddrName,
		aAddrMax: maxIDEAdapterAddr,
		dAddrMax: maxIDEDeviceAddr,
	}
)

func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	driver := state.Get("driver").(vmwcommon.Driver)
	ui := state.Get("ui").(packer.Ui)

	// Set the path we want for the new .vmx file and clone
	vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx")
	ui.Say("Cloning source VM...")
	log.Printf("Cloning from: %s", s.Path)
	log.Printf("Cloning to: %s", vmxPath)
	if err := driver.Clone(vmxPath, s.Path); err != nil {
		state.Put("error", err)
		return multistep.ActionHalt
	}

	// Read in the machine configuration from the cloned VMX file
	//
	// * The main driver needs the path to the vmx (set above) and the
	// network type so that it can work out things like IP's and MAC
	// addresses
	// * The disk compaction step needs the paths to all attached disks
	vmxData, err := vmwcommon.ReadVMX(vmxPath)
	if err != nil {
		state.Put("error", err)
		return multistep.ActionHalt
	}

	// Search across all adapter types to get the filenames of attached disks
	allDiskAdapters := []vmxAdapter{
		scsiAdapter,
		sataAdapter,
		nvmeAdapter,
		ideAdapter,
	}
	var diskFilenames []string
	for _, adapter := range allDiskAdapters {
		diskFilenames = append(diskFilenames, getAttachedDisks(adapter, vmxData)...)
	}

	// Write out the relative, host filesystem paths to the disks
	var diskFullPaths []string
	for _, diskFilename := range diskFilenames {
		log.Printf("Found attached disk with filename: %s", diskFilename)
		diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, diskFilename))
	}

	if len(diskFullPaths) == 0 {
		state.Put("error", fmt.Errorf("Could not enumerate disk info from the vmx file"))
		return multistep.ActionHalt
	}

	// Determine the network type by reading out of the .vmx
	var networkType string
	if _, ok := vmxData["ethernet0.connectiontype"]; ok {
		networkType = vmxData["ethernet0.connectiontype"]
		log.Printf("Discovered the network type: %s", networkType)
	}
	if networkType == "" {
		networkType = "nat"
		log.Printf("Defaulting to network type: %s", networkType)
	}

	// Stash all required information in our state bag
	state.Put("vmx_path", vmxPath)
	state.Put("disk_full_paths", diskFullPaths)
	state.Put("vmnetwork", networkType)

	return multistep.ActionContinue
}

func (s *StepCloneVMX) Cleanup(state multistep.StateBag) {
}

func getAttachedDisks(a vmxAdapter, data map[string]string) (attachedDisks []string) {
	// Loop over possible adapter, controller or controller channel
	for x := 0; x <= a.aAddrMax; x++ {
		// Loop over possible addresses for attached devices
		for y := 0; y <= a.dAddrMax; y++ {
			address := fmt.Sprintf("%s%d:%d.filename", a.strAddr, x, y)
			if device, _ := data[address]; filepath.Ext(device) == ".vmdk" {
				attachedDisks = append(attachedDisks, device)
			}
		}
	}
	return
}
<commit_msg>Use regex based approach to detect attached disks<commit_after>package vmx

import (
	"context"
	"fmt"
	"log"
	"path/filepath"
	"regexp"

	vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

// StepCloneVMX takes a VMX file and clones the VM into the output directory.
type StepCloneVMX struct {
	OutputDir string
	Path      string
	VMName    string
}

type vmxAdapter struct {
	diskPathKeyRe string
}

var (
	// The VMX file stores the path to a configured disk, and information
	// about that disks attachment to a virtual adapter/controller, as a
	// key/value pair.
	// For a virtual disk attached to bus ID 3 of the virtual machines
	// first SCSI adapter the key/value pair would look something like:
	// scsi0:3.fileName = "relative/path/to/scsiDisk.vmdk"
	// The supported adapter types and configuration maximums for each type
	// vary according to the VMware platform type and version, and the
	// Virtual Machine Hardware version used. See the 'Virtual Machine
	// Maximums' section within VMware's 'Configuration Maximums'
	// documentation for each platform:
	// https://kb.vmware.com/s/article/1003497
	// Information about the supported Virtual Machine Hardware versions:
	// https://kb.vmware.com/s/article/1003746
	// The following regexp's are used to match all possible disk attachment
	// points that may be found in the VMX file across all VMware
	// platforms/versions and Virtual Machine Hardware versions
	scsiAdapter = vmxAdapter{
		diskPathKeyRe: `(?i)^scsi[[:digit:]]:[[:digit:]]{1,2}\.fileName`,
	}
	sataAdapter = vmxAdapter{
		diskPathKeyRe: `(?i)^sata[[:digit:]]:[[:digit:]]{1,2}\.fileName`,
	}
	nvmeAdapter = vmxAdapter{
		diskPathKeyRe: `(?i)^nvme[[:digit:]]:[[:digit:]]{1,2}\.fileName`,
	}
	ideAdapter = vmxAdapter{
		diskPathKeyRe: `(?i)^ide[[:digit:]]:[[:digit:]]\.fileName`,
	}
)

func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	driver := state.Get("driver").(vmwcommon.Driver)
	ui := state.Get("ui").(packer.Ui)

	// Set the path we want for the new .vmx file and clone
	vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx")
	ui.Say("Cloning source VM...")
	log.Printf("Cloning from: %s", s.Path)
	log.Printf("Cloning to: %s", vmxPath)
	if err := driver.Clone(vmxPath, s.Path); err != nil {
		state.Put("error", err)
		return multistep.ActionHalt
	}

	// Read in the machine configuration from the cloned VMX file
	//
	// * The main driver needs the path to the vmx (set above) and the
	// network type so that it can work out things like IP's and MAC
	// addresses
	// * The disk compaction step needs the paths to all attached disks
	vmxData, err := vmwcommon.ReadVMX(vmxPath)
	if err != nil {
		state.Put("error", err)
		return multistep.ActionHalt
	}

	// Search across all adapter types to get the filenames of attached disks
	allDiskAdapters := []vmxAdapter{
		scsiAdapter,
		sataAdapter,
		nvmeAdapter,
		ideAdapter,
	}
	var diskFilenames []string
	for _, adapter := range allDiskAdapters {
		diskFilenames = append(diskFilenames, getAttachedDisks(adapter, vmxData)...)
	}

	// Write out the relative, host filesystem paths to the disks
	var diskFullPaths []string
	for _, diskFilename := range diskFilenames {
		log.Printf("Found attached disk with filename: %s", diskFilename)
		diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, diskFilename))
	}

	if len(diskFullPaths) == 0 {
		state.Put("error", fmt.Errorf("Could not enumerate disk info from the vmx file"))
		return multistep.ActionHalt
	}

	// Determine the network type by reading out of the .vmx
	var networkType string
	if _, ok := vmxData["ethernet0.connectiontype"]; ok {
		networkType = vmxData["ethernet0.connectiontype"]
		log.Printf("Discovered the network type: %s", networkType)
	}
	if networkType == "" {
		networkType = "nat"
		log.Printf("Defaulting to network type: %s", networkType)
	}

	// Stash all required information in our state bag
	state.Put("vmx_path", vmxPath)
	state.Put("disk_full_paths", diskFullPaths)
	state.Put("vmnetwork", networkType)

	return multistep.ActionContinue
}

func (s *StepCloneVMX) Cleanup(state multistep.StateBag) {
}

func getAttachedDisks(a vmxAdapter, data map[string]string) (attachedDisks []string) {
	pathKeyRe := regexp.MustCompile(a.diskPathKeyRe)
	for k, v := range data {
		match := pathKeyRe.FindString(k)
		if match != "" && filepath.Ext(v) == ".vmdk" {
			attachedDisks = append(attachedDisks, v)
		}
	}
	return
}
<|endoftext|>