<commit_before>package main

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"

	"github.com/rebuy-de/kubernetes-deployment/git"
)

type App struct {
	KubeConfigPath    string
	ProjectConfigPath string
	OutputPath        string

	SleepInterval int
	SkipShuffle   bool
}

func (app *App) Run() error {
	config, err := ReadProjectConfigFrom(app.ProjectConfigPath)
	if err != nil {
		return err
	}

	log.Printf("Read the following project configuration:\n%s", config)

	config.Services.Clean()

	if !app.SkipShuffle {
		log.Printf("Shuffling service list")
		config.Services.Shuffle()
	}

	log.Printf("Deploying with this project configuration:\n%s", config)

	log.Printf("Wiping output directory '%s'!", app.OutputPath)
	err = os.RemoveAll(app.OutputPath)
	if err != nil {
		return err
	}

	err = os.MkdirAll(app.OutputPath, 0755)
	if err != nil {
		return err
	}

	projectOutputPath := path.Join(app.OutputPath, "config.yml")
	log.Printf("Writing applying configuration to %s", projectOutputPath)
	config.WriteTo(projectOutputPath)
	if err != nil {
		return err
	}

	for _, service := range *config.Services {
		err := app.DeployService(service)
		if err != nil {
			return err
		}
	}

	return nil
}

func (app *App) DeployService(service *Service) error {
	log.Printf("Deploying %+v", *service)

	tempDir, err := ioutil.TempDir("", "kubernetes-deployment-checkout-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)

	err = git.SparseCheckout(tempDir, service.Repository, service.Branch, service.Path)
	if err != nil {
		return err
	}

	manifests, err := findManifests(path.Join(tempDir, service.Path))
	if err != nil {
		return err
	}

	outputPath := path.Join(app.OutputPath, service.Name)
	err = os.MkdirAll(outputPath, 0755)
	if err != nil {
		return err
	}

	for _, manifest := range manifests {
		name := path.Base(manifest)
		target := path.Join(outputPath, name)
		log.Printf("Copying manifest to '%s'", target)

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

		dst, err := os.Create(target)
		if err != nil {
			return err
		}
		defer src.Close()

		_, err = io.Copy(dst, src)
		if err != nil {
			return err
		}
	}

	return nil
}

func findManifests(dir string) ([]string, error) {
	dir = path.Clean(dir) + "/"
	result := []string{}

	for _, ext := range []string{"*.yml", "*.yaml"} {
		matches, err := filepath.Glob(path.Join(dir, ext))
		if err != nil {
			return nil, err
		}
		result = append(result, matches...)
	}

	return result, nil
}
<commit_msg>add sleep interval<commit_after>package main

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"time"

	"github.com/rebuy-de/kubernetes-deployment/git"
)

type App struct {
	KubeConfigPath    string
	ProjectConfigPath string
	OutputPath        string

	SleepInterval int
	SkipShuffle   bool
}

func (app *App) Run() error {
	config, err := ReadProjectConfigFrom(app.ProjectConfigPath)
	if err != nil {
		return err
	}

	log.Printf("Read the following project configuration:\n%s", config)

	config.Services.Clean()

	if !app.SkipShuffle {
		log.Printf("Shuffling service list")
		config.Services.Shuffle()
	}

	log.Printf("Deploying with this project configuration:\n%s", config)

	log.Printf("Wiping output directory '%s'!", app.OutputPath)
	err = os.RemoveAll(app.OutputPath)
	if err != nil {
		return err
	}

	err = os.MkdirAll(app.OutputPath, 0755)
	if err != nil {
		return err
	}

	projectOutputPath := path.Join(app.OutputPath, "config.yml")
	log.Printf("Writing applying configuration to %s", projectOutputPath)
	config.WriteTo(projectOutputPath)
	if err != nil {
		return err
	}

	for i, service := range *config.Services {
		if i != 0 {
			log.Printf("Sleeping %d seconds...", app.SleepInterval)
			time.Sleep(time.Duration(app.SleepInterval) * time.Second)
		}

		err := app.DeployService(service)
		if err != nil {
			return err
		}
	}

	return nil
}

func (app *App) DeployService(service *Service) error {
	log.Printf("Deploying %+v", *service)

	tempDir, err := ioutil.TempDir("", "kubernetes-deployment-checkout-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)

	err = git.SparseCheckout(tempDir, service.Repository, service.Branch, service.Path)
	if err != nil {
		return err
	}

	manifests, err := findManifests(path.Join(tempDir, service.Path))
	if err != nil {
		return err
	}

	outputPath := path.Join(app.OutputPath, service.Name)
	err = os.MkdirAll(outputPath, 0755)
	if err != nil {
		return err
	}

	for _, manifest := range manifests {
		name := path.Base(manifest)
		target := path.Join(outputPath, name)
		log.Printf("Copying manifest to '%s'", target)

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

		dst, err := os.Create(target)
		if err != nil {
			return err
		}
		defer src.Close()

		_, err = io.Copy(dst, src)
		if err != nil {
			return err
		}
	}

	return nil
}

func findManifests(dir string) ([]string, error) {
	dir = path.Clean(dir) + "/"
	result := []string{}

	for _, ext := range []string{"*.yml", "*.yaml"} {
		matches, err := filepath.Glob(path.Join(dir, ext))
		if err != nil {
			return nil, err
		}
		result = append(result, matches...)
	}

	return result, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
)

const (
	topStoriesUrl = "https://hacker-news.firebaseio.com/v0/topstories.json"
	itemUrl       = "https://hacker-news.firebaseio.com/v0/item/%d.json"

	runInterval = 15 * time.Minute // Interval at which we fetch the items
)

var (
	debug  = true
	db     *Database
	Logger = log.New(os.Stdout, "  ", log.LstdFlags|log.Lshortfile)
)

func main() {
	var err error
	db, err = setupDB()
	if err != nil {
		Logger.Fatal(err)
	}

	// set up a goroutine that will periodically call run()
	go func() {
		run()
		ticker := time.NewTicker(runInterval)
		for {
			select {
			case <-ticker.C:
				run() // TODO: Consider executing run() in a separate goroutine
			}
		}
	}()

	setupHandlers()
	Logger.Println("Listening...")
	http.ListenAndServe(":8080", nil)
}

// Item represents a HN story
type Item struct {
	By    string
	Id    int
	Kids  []int64
	Score int
	Time  int64
	Title string
	Type  string
	Url   string
}

// run fetches the top HN stories and sends notifications according to each user's score threshold
func run() {
	Logger.Println("run() - started...")
	ids, err := getTopStories()
	if err != nil {
		Logger.Println(err)
		return // Just wait till the next cycle.
	}

	// fetcher runs a goroutine to fetch the item. Once completed, the result
	// will be inserted in the returned channel, and the channel closed.
	fetchItem := func(id int) chan Item {
		out := make(chan Item)
		go func() {
			item, err := getItem(id)
			if err != nil {
				Logger.Println(err)
			} else {
				out <- item
			}
			close(out)
		}()
		return out
	}

	// Fetch items concurrently. Each channel will only hold one item
	cs := make([]chan Item, len(ids))
	for i, id := range ids {
		cs[i] = fetchItem(id)
	}

	for item := range merge(cs...) {
		if users := db.findUsersForItem(item.Id, item.Score); len(users) > 0 {
			// Create a slice with all the recipients for this item
			emails := make([]string, len(users))
			for i, u := range users {
				emails[i] = u.Email
			}

			// Send the email
			if err := sendItem(item.Id, item.Title, item.Url, emails); err != nil {
				Logger.Println("Error sending mail: ", err)
				continue
			}
			Logger.Printf("Item %d sent to users: %v\n", item.Id, emails)

			// Update each user's sentItem list. TODO: Use mgo's UpdateAll() function, updating all users at the same time
			for _, u := range users {
				if err := db.updateSentItems(u.Id, item.Id); err != nil {
					Logger.Println(err)
				}
			}
		}
	}

	Logger.Println("run() - finished")
}

// getTopStories reads the top stories IDs from the API
func getTopStories() ([]int, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", topStoriesUrl, nil)
	if err != nil {
		return nil, err
	}
	req.Close = true

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	res := struct {
		Items []int
	}{}

	err = json.NewDecoder(resp.Body).Decode(&res.Items)
	return res.Items, err
}

// getItem reads the HN story item from the API
func getItem(id int) (item Item, err error) {
	url := fmt.Sprintf(itemUrl, id)
	client := &http.Client{}
	var req *http.Request
	req, err = http.NewRequest("GET", url, nil)
	if err != nil {
		return
	}
	req.Close = true

	var resp *http.Response
	resp, err = client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&item)
	return item, nil
}

// merge converts a list of channels to a single channel.
// Based on the example in http://blog.golang.org/pipelines
func merge(cs ...chan Item) <-chan Item {
	var wg sync.WaitGroup
	out := make(chan Item)

	// Start an output goroutine for each input channel in cs. output
	// copies values from c to out until c is closed, then calls wg.Done.
	output := func(c <-chan Item) {
		for n := range c {
			out <- n
		}
		wg.Done()
	}
	wg.Add(len(cs))
	for _, c := range cs {
		go output(c)
	}

	// Start a goroutine to close out once all the output goroutines are
	// done. This must start after the wg.Add call.
	go func() {
		wg.Wait()
		close(out)
	}()
	return out
}
<commit_msg>Change port<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
)

const (
	topStoriesUrl = "https://hacker-news.firebaseio.com/v0/topstories.json"
	itemUrl       = "https://hacker-news.firebaseio.com/v0/item/%d.json"

	runInterval = 15 * time.Minute // Interval at which we fetch the items
)

var (
	debug  = true
	db     *Database
	Logger = log.New(os.Stdout, "  ", log.LstdFlags|log.Lshortfile)
)

func main() {
	var err error
	db, err = setupDB()
	if err != nil {
		Logger.Fatal(err)
	}

	// set up a goroutine that will periodically call run()
	go func() {
		run()
		ticker := time.NewTicker(runInterval)
		for {
			select {
			case <-ticker.C:
				run() // TODO: Consider executing run() in a separate goroutine
			}
		}
	}()

	setupHandlers()
	Logger.Println("Listening...")
	http.ListenAndServe(":3000", nil)
}

// Item represents a HN story
type Item struct {
	By    string
	Id    int
	Kids  []int64
	Score int
	Time  int64
	Title string
	Type  string
	Url   string
}

// run fetches the top HN stories and sends notifications according to each user's score threshold
func run() {
	Logger.Println("run() - started...")
	ids, err := getTopStories()
	if err != nil {
		Logger.Println(err)
		return // Just wait till the next cycle.
	}

	// fetcher runs a goroutine to fetch the item. Once completed, the result
	// will be inserted in the returned channel, and the channel closed.
	fetchItem := func(id int) chan Item {
		out := make(chan Item)
		go func() {
			item, err := getItem(id)
			if err != nil {
				Logger.Println(err)
			} else {
				out <- item
			}
			close(out)
		}()
		return out
	}

	// Fetch items concurrently. Each channel will only hold one item
	cs := make([]chan Item, len(ids))
	for i, id := range ids {
		cs[i] = fetchItem(id)
	}

	for item := range merge(cs...) {
		if users := db.findUsersForItem(item.Id, item.Score); len(users) > 0 {
			// Create a slice with all the recipients for this item
			emails := make([]string, len(users))
			for i, u := range users {
				emails[i] = u.Email
			}

			// Send the email
			if err := sendItem(item.Id, item.Title, item.Url, emails); err != nil {
				Logger.Println("Error sending mail: ", err)
				continue
			}
			Logger.Printf("Item %d sent to users: %v\n", item.Id, emails)

			// Update each user's sentItem list. TODO: Use mgo's UpdateAll() function, updating all users at the same time
			for _, u := range users {
				if err := db.updateSentItems(u.Id, item.Id); err != nil {
					Logger.Println(err)
				}
			}
		}
	}

	Logger.Println("run() - finished")
}

// getTopStories reads the top stories IDs from the API
func getTopStories() ([]int, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", topStoriesUrl, nil)
	if err != nil {
		return nil, err
	}
	req.Close = true

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	res := struct {
		Items []int
	}{}

	err = json.NewDecoder(resp.Body).Decode(&res.Items)
	return res.Items, err
}

// getItem reads the HN story item from the API
func getItem(id int) (item Item, err error) {
	url := fmt.Sprintf(itemUrl, id)
	client := &http.Client{}
	var req *http.Request
	req, err = http.NewRequest("GET", url, nil)
	if err != nil {
		return
	}
	req.Close = true

	var resp *http.Response
	resp, err = client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&item)
	return item, nil
}

// merge converts a list of channels to a single channel.
// Based on the example in http://blog.golang.org/pipelines
func merge(cs ...chan Item) <-chan Item {
	var wg sync.WaitGroup
	out := make(chan Item)

	// Start an output goroutine for each input channel in cs. output
	// copies values from c to out until c is closed, then calls wg.Done.
	output := func(c <-chan Item) {
		for n := range c {
			out <- n
		}
		wg.Done()
	}
	wg.Add(len(cs))
	for _, c := range cs {
		go output(c)
	}

	// Start a goroutine to close out once all the output goroutines are
	// done. This must start after the wg.Add call.
	go func() {
		wg.Wait()
		close(out)
	}()
	return out
}
<|endoftext|>


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

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/tsuru/tsuru/cmd"
)

type createAppOptions struct {
	name        string
	description string
	platform    string
	team        string
	plan        string
	pool        string
}

func (o *createAppOptions) encode() string {
	values := make(url.Values)
	values.Set("name", o.name)
	values.Set("description", o.description)
	values.Set("platform", o.platform)
	values.Set("plan", o.plan)
	values.Set("teamOwner", o.team)
	values.Set("pool", o.pool)
	return values.Encode()
}

func createApp(client *cmd.Client, opts createAppOptions) (map[string]string, error) {
	url, err := cmd.GetURL("/apps")
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", url, strings.NewReader(opts.encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var app map[string]string
	err = json.NewDecoder(resp.Body).Decode(&app)
	return app, err
}

func deleteApps(apps []app, client *cmd.Client, w io.Writer) ([]error, error) {
	var errs []error
	for _, app := range apps {
		fmt.Fprintf(w, "Deleting from env %q... ", app.Env.Name)
		url, err := cmd.GetURL("/apps/" + app.Name)
		if err != nil {
			return nil, err
		}
		req, err := http.NewRequest("DELETE", url, nil)
		if err != nil {
			return nil, err
		}
		resp, err := client.Do(req)
		errs = append(errs, err)
		cmd.StreamJSONResponse(ioutil.Discard, resp)
		fmt.Fprintln(w, "ok")
	}
	return errs, nil
}

func listApps(client *cmd.Client, filters map[string]string) ([]app, error) {
	qs := make(url.Values)
	for k, v := range filters {
		qs.Set(k, v)
	}
	url, err := cmd.GetURL("/apps?" + qs.Encode())
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNoContent {
		return nil, nil
	}
	var apps []app
	err = json.NewDecoder(resp.Body).Decode(&apps)
	return apps, err
}

func lastDeploy(client *cmd.Client, appName string) (deploy, error) {
	var d deploy
	resp, err := doReq(client, "/deploys?limit=1&app="+appName)
	if err != nil {
		return d, err
	}
	defer resp.Body.Close()
	var deploys []deploy
	if resp.StatusCode == http.StatusNoContent {
		return d, nil
	}
	err = json.NewDecoder(resp.Body).Decode(&deploys)
	if err != nil {
		return d, err
	}
	if len(deploys) > 0 {
		d = deploys[0]
	}
	return d, nil
}

func getApp(client *cmd.Client, appName string) (app, error) {
	var a app
	resp, err := doReq(client, "/apps/"+appName)
	if err != nil {
		return a, err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return a, errors.New("app not found")
	}
	err = json.NewDecoder(resp.Body).Decode(&a)
	return a, err
}

func doReq(client *cmd.Client, path string) (*http.Response, error) {
	url, err := cmd.GetURL(path)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

type app struct {
	Name          string        `json:"name"`
	CName         []string      `json:"cname"`
	Description   string        `json:"description"`
	RepositoryURL string        `json:"repository"`
	Platform      string        `json:"platform"`
	Teams         []string      `json:"teams"`
	Owner         string        `json:"owner"`
	TeamOwner     string        `json:"teamowner"`
	Units         []interface{} `json:"units"`
	Env           Environment
	Addr          string
}

type deploy struct {
	ID        string
	Commit    string
	Timestamp time.Time
	Image     string
}
<commit_msg>app: remove unecessary if condition<commit_after>// Copyright 2016 EF CTX. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/tsuru/tsuru/cmd"
)

type createAppOptions struct {
	name        string
	description string
	platform    string
	team        string
	plan        string
	pool        string
}

func (o *createAppOptions) encode() string {
	values := make(url.Values)
	values.Set("name", o.name)
	values.Set("description", o.description)
	values.Set("platform", o.platform)
	values.Set("plan", o.plan)
	values.Set("teamOwner", o.team)
	values.Set("pool", o.pool)
	return values.Encode()
}

func createApp(client *cmd.Client, opts createAppOptions) (map[string]string, error) {
	url, err := cmd.GetURL("/apps")
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", url, strings.NewReader(opts.encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var app map[string]string
	err = json.NewDecoder(resp.Body).Decode(&app)
	return app, err
}

func deleteApps(apps []app, client *cmd.Client, w io.Writer) ([]error, error) {
	var errs []error
	for _, app := range apps {
		fmt.Fprintf(w, "Deleting from env %q... ", app.Env.Name)
		url, err := cmd.GetURL("/apps/" + app.Name)
		if err != nil {
			return nil, err
		}
		req, err := http.NewRequest("DELETE", url, nil)
		if err != nil {
			return nil, err
		}
		resp, err := client.Do(req)
		errs = append(errs, err)
		cmd.StreamJSONResponse(ioutil.Discard, resp)
		fmt.Fprintln(w, "ok")
	}
	return errs, nil
}

func listApps(client *cmd.Client, filters map[string]string) ([]app, error) {
	qs := make(url.Values)
	for k, v := range filters {
		qs.Set(k, v)
	}
	url, err := cmd.GetURL("/apps?" + qs.Encode())
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNoContent {
		return nil, nil
	}
	var apps []app
	err = json.NewDecoder(resp.Body).Decode(&apps)
	return apps, err
}

func lastDeploy(client *cmd.Client, appName string) (deploy, error) {
	var d deploy
	resp, err := doReq(client, "/deploys?limit=1&app="+appName)
	if err != nil {
		return d, err
	}
	defer resp.Body.Close()
	var deploys []deploy
	if resp.StatusCode == http.StatusNoContent {
		return d, nil
	}
	err = json.NewDecoder(resp.Body).Decode(&deploys)
	if err != nil {
		return d, err
	}
	if len(deploys) > 0 {
		d = deploys[0]
	}
	return d, nil
}

func getApp(client *cmd.Client, appName string) (app, error) {
	var a app
	resp, err := doReq(client, "/apps/"+appName)
	if err != nil {
		return a, err
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&a)
	return a, err
}

func doReq(client *cmd.Client, path string) (*http.Response, error) {
	url, err := cmd.GetURL(path)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

type app struct {
	Name          string        `json:"name"`
	CName         []string      `json:"cname"`
	Description   string        `json:"description"`
	RepositoryURL string        `json:"repository"`
	Platform      string        `json:"platform"`
	Teams         []string      `json:"teams"`
	Owner         string        `json:"owner"`
	TeamOwner     string        `json:"teamowner"`
	Units         []interface{} `json:"units"`
	Env           Environment
	Addr          string
}

type deploy struct {
	ID        string
	Commit    string
	Timestamp time.Time
	Image     string
}
<|endoftext|>


<commit_before>package simplyput

// TODO: Add memcache
// TODO: Support ETags, If-Modified-Since, etc. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)
// TODO: PUT requests
// TODO: HEAD requests
// TODO: PATCH requests/semantics
// TODO: Batch requests (via multipart?)
// TODO: User POSTs a JSON schema, future requests are validated against that schema. Would anybody use that?

import (
	"appengine"
	"appengine/datastore"
	"appengine/memcache"
	"appengine/urlfetch"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	idKey        = "_id"
	createdKey   = "_created"
	updatedKey   = "_updated"
	defaultLimit = 10
)

var invalidPath = errors.New("invalid path")

func init() {
	http.HandleFunc("/", handle)
}

type filter struct {
	Key, Value string
}
type userQuery struct {
	Limit                        int
	StartCursor, EndCursor, Sort string
	Filters                      []filter
}

// getUserID gets the Google User ID for an access token.
func getUserID(accessToken string, client http.Client) (string, error) {
	resp, err := client.Get("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + accessToken)
	if err != nil {
		return "", err
	}
	var info struct {
		ID string
	}
	if err = json.NewDecoder(resp.Body).Decode(&info); err != nil {
		return "", err
	}
	resp.Body.Close()
	id := info.ID
	if id == "" {
		return "", errors.New("invalid auth")
	}
	return id, nil
}

// getKindAndID parses the kind and ID from a request path.
func getKindAndID(path string) (string, int64, error) {
	if !strings.HasPrefix(path, "/") || path == "/" {
		return "", int64(0), invalidPath
	}
	parts := strings.Split(path[1:], "/")
	if len(parts) > 2 {
		return "", int64(0), invalidPath
	} else if len(parts) == 1 {
		return parts[0], int64(0), nil
	} else if len(parts) == 2 {
		id, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return "", int64(0), err
		}
		return parts[0], id, nil
	}
	return "", int64(0), invalidPath
}

// handle dispatches requests to the relevant API method and arranges certain common state
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	w.Header().Add("Access-Control-Allow-Origin", "*")

	r.ParseForm()
	client := urlfetch.Client(c)

	var userID string
	if appengine.IsDevAppServer() {
		// For local development, don't require an access token or user ID
		// If the user_id param is set, that's the user ID.
		userID = r.Form.Get("user_id")
	} else {
		// Get the access_token from the request and turn it into a user ID with which we will namespace Kinds in the datastore.
		accessToken := r.Form.Get("access_token")
		if accessToken == "" {
			h := r.Header.Get("Authorization")
			if strings.HasPrefix(h, "Bearer ") {
				accessToken = h[len("Bearer "):]
			}
		}
		if accessToken == "" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		var err error // Needed because otherwise the next line shadows userID...
		userID, err = getUserID(accessToken, *client)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	kind, id, err := getKindAndID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dsKind := fmt.Sprintf("%s--%s", userID, kind)

	resp := make(map[string]interface{}, 0)
	errCode := http.StatusOK
	if id == int64(0) {
		switch r.Method {
		case "POST":
			resp, errCode = insert(c, dsKind, r.Body)
			r.Body.Close()
		case "GET":
			uq, err := newUserQuery(r)
			if err != nil {
				http.Error(w, "Bad Request", http.StatusBadRequest)
				return
			}
			resp, errCode = list(c, dsKind, *uq)
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	} else {
		switch r.Method {
		case "GET":
			resp, errCode = get(c, dsKind, id)
		case "DELETE":
			errCode = delete2(c, dsKind, id)
		case "POST":
			// This is strictly "replace all properties/values", not "add new properties, update existing"
			resp, errCode = update(c, dsKind, id, r.Body)
			r.Body.Close()
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	}
	if errCode != http.StatusOK {
		http.Error(w, "", errCode)
		return
	}
	if err := json.NewEncoder(w).Encode(&resp); err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	// TODO: Buffer and memSet the JSON here
	w.Header().Add("Content-Type", "application/json")
}

func newUserQuery(r *http.Request) (*userQuery, error) {
	uq := userQuery{
		StartCursor: r.FormValue("start"),
		EndCursor:   r.FormValue("end"),
		Sort:        r.FormValue("sort"),
	}
	if r.FormValue("limit") != "" {
		lim, err := strconv.Atoi(r.FormValue("limit"))
		if err != nil {
			return nil, err
		}
		uq.Limit = lim
	}

	for _, f := range map[string][]string(r.Form)["where"] {
		parts := strings.Split(f, "=")
		if len(parts) != 2 {
			return nil, errors.New("invalid where: " + f)
		}
		uq.Filters = append(uq.Filters, filter{Key: parts[0], Value: parts[1]})
	}
	return &uq, nil
}

func delete2(c appengine.Context, kind string, id int64) int {
	k := datastore.NewKey(c, kind, "", id, nil)
	if err := datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return http.StatusNotFound
		} else {
			c.Errorf("%v", err)
			return http.StatusInternalServerError
		}
	}
	return http.StatusOK
}

func get(c appengine.Context, kind string, id int64) (map[string]interface{}, int) {
	k := datastore.NewKey(c, kind, "", id, nil)
	var pl plist
	if err := datastore.Get(c, k, &pl); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return nil, http.StatusNotFound
		}
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m := plistToMap(pl, k.IntID())
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

func insert(c appengine.Context, kind string, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[createdKey] = time.Now().Unix()

	pl := mapToPlist("", m)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err := datastore.Put(c, k, &pl)
	if err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

type prop struct {
	Name     string
	Value    interface{}
	Multiple bool
	NoIndex  bool
}
type plist []prop

// plistToMap transforms a plist such as you would get from the datastore into a map[string]interface{} suitable for JSON-encoding.
func plistToMap(pl plist, id int64) map[string]interface{} {
	m := make(map[string]interface{})
	for _, p := range pl {
		if strings.Contains(p.Name, ".") {
			parts := strings.Split(p.Name, ".")
			sub := m
			for _, p := range parts[:len(parts)-1] {
				// Traverse the path up until the leaf
				if i, exists := sub[p]; exists {
					// Already seen this path, traverse it
					if ii, ok := i.(map[string]interface{}); ok {
						sub = ii
					} else {
						// Got a sub-property of a non-map property. Uh oh...
						// Not sure it's worth failing/logging though...
					}
				} else {
					// First time down this path, add a new empty map
					next := map[string]interface{}{}
					sub[p] = next
					sub = next
				}
			}
			leaf := parts[len(parts)-1]
			if _, exists := sub[leaf]; exists {
				if !p.Multiple {
					// We would expect p.Multiple to be true here.
					// Not sure it's worth failing/logging though...
				}
				if _, isArr := sub[leaf].([]interface{}); isArr {
					// Already an array here, append to it
					sub[leaf] = append(sub[leaf].([]interface{}), p.Value)
				} else {
					// Already a single value here, should be an array now.
					sub[leaf] = []interface{}{sub[leaf], p.Value}
				}
			} else {
				sub[leaf] = p.Value
			}
		} else {
			if _, exists := m[p.Name]; exists {
				if !p.Multiple {
					// We would expect p.Multiple to be true here.
					// Not sure it's worth failing/logging though...
				}
				if _, isArr := m[p.Name].([]interface{}); isArr {
					// Already an array here, append to it
					m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
				} else {
					// Already a single value here, should be an array now.
					m[p.Name] = []interface{}{m[p.Name], p.Value}
				}
			} else {
				m[p.Name] = p.Value
			}
		}
	}
	m[idKey] = id
	return m
}

// mapToPlist transforms a map[string]interface{} such as you would get from decoding JSON into a plist to store in the datastore.
func mapToPlist(prefix string, m map[string]interface{}) plist {
	pl := make(plist, 0, len(m))
	for k, v := range m {
		if m, nest := v.(map[string]interface{}); nest {
			// Generate a plist for this sub-map, and append it
			pl = append(pl, mapToPlist(prefix+k+".", m)...)
		} else if _, mult := v.([]interface{}); mult {
			// Generate a prop for every item in the slice
			for _, mv := range v.([]interface{}) {
				pl = append(pl, prop{
					Name:     prefix + k,
					Value:    mv,
					Multiple: true,
				})
			}
			// TODO: Apparently no way to store an empty list? That seems odd...
		} else {
			pl = append(pl, prop{
				Name:  prefix + k,
				Value: v,
			})
		}
	}
	return pl
}

func list(c appengine.Context, kind string, uq userQuery) (map[string]interface{}, int) {
	q := datastore.NewQuery(kind)

	if uq.Limit != 0 {
		q = q.Limit(uq.Limit)
	}
	if uq.Sort != "" {
		q = q.Order(uq.Sort)
	}
	if c, err := datastore.DecodeCursor(uq.StartCursor); err == nil {
		q = q.Start(c)
	}
	if c, err := datastore.DecodeCursor(uq.EndCursor); err == nil {
		q = q.End(c)
	}
	// TODO: Support numerical filters, not just strings
	for _, f := range uq.Filters {
		q = q.Filter(f.Key, f.Value)
	}

	items := make([]map[string]interface{}, 0)

	var crs datastore.Cursor
	for t := q.Run(c); ; {
		var pl plist
		k, err := t.Next(&pl)
		if err == datastore.Done {
			break
		}
		if err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
		m := plistToMap(pl, k.IntID())
		items = append(items, m)
		if crs, err = t.Cursor(); err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
	}
	r := map[string]interface{}{
		"items":          items,
		"nextStartToken": crs.String(),
	}
	return r, http.StatusOK
}

func update(c appengine.Context, kind string, id int64, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[updatedKey] = time.Now().Unix()

	pl := mapToPlist("", m)

	k := datastore.NewKey(c, kind, "", id, nil)
	if _, err := datastore.Put(c, k, &pl); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = id
	return m, http.StatusOK
}

func memGet(c appengine.Context, k string) (bool, []byte) {
	// Ignore errors in getting. If cache miss, just return nil.
	if i, err := memcache.Get(c, k); err == nil {
		return true, i.Value
	} else if err != memcache.ErrCacheMiss {
		c.Warningf("Error getting from memcache key %s: %v", k, err)
	}
	return false, nil
}

func memSet(c appengine.Context, k string, v []byte) {
	if err := memcache.Set(c, &memcache.Item{
		Key:   k,
		Value: v,
	}); err != nil {
		c.Warningf("Failed to update memcache key %s", k)
	}
}
<commit_msg>Remove memcache for now<commit_after>package simplyput

// TODO: Add memcache
// TODO: Support ETags, If-Modified-Since, etc. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)
// TODO: PUT requests
// TODO: HEAD requests
// TODO: PATCH requests/semantics
// TODO: Batch requests (via multipart?)
// TODO: User POSTs a JSON schema, future requests are validated against that schema. Would anybody use that?

import (
	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	idKey        = "_id"
	createdKey   = "_created"
	updatedKey   = "_updated"
	defaultLimit = 10
)

var invalidPath = errors.New("invalid path")

func init() {
	http.HandleFunc("/", handle)
}

type filter struct {
	Key, Value string
}
type userQuery struct {
	Limit                        int
	StartCursor, EndCursor, Sort string
	Filters                      []filter
}

// getUserID gets the Google User ID for an access token.
func getUserID(accessToken string, client http.Client) (string, error) {
	resp, err := client.Get("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + accessToken)
	if err != nil {
		return "", err
	}
	var info struct {
		ID string
	}
	if err = json.NewDecoder(resp.Body).Decode(&info); err != nil {
		return "", err
	}
	resp.Body.Close()
	id := info.ID
	if id == "" {
		return "", errors.New("invalid auth")
	}
	return id, nil
}

// getKindAndID parses the kind and ID from a request path.
func getKindAndID(path string) (string, int64, error) {
	if !strings.HasPrefix(path, "/") || path == "/" {
		return "", int64(0), invalidPath
	}
	parts := strings.Split(path[1:], "/")
	if len(parts) > 2 {
		return "", int64(0), invalidPath
	} else if len(parts) == 1 {
		return parts[0], int64(0), nil
	} else if len(parts) == 2 {
		id, err := strconv.ParseInt(parts[1], 10, 64)
		if err != nil {
			return "", int64(0), err
		}
		return parts[0], id, nil
	}
	return "", int64(0), invalidPath
}

// handle dispatches requests to the relevant API method and arranges certain common state
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	w.Header().Add("Access-Control-Allow-Origin", "*")

	r.ParseForm()
	client := urlfetch.Client(c)

	var userID string
	if appengine.IsDevAppServer() {
		// For local development, don't require an access token or user ID
		// If the user_id param is set, that's the user ID.
		userID = r.Form.Get("user_id")
	} else {
		// Get the access_token from the request and turn it into a user ID with which we will namespace Kinds in the datastore.
		accessToken := r.Form.Get("access_token")
		if accessToken == "" {
			h := r.Header.Get("Authorization")
			if strings.HasPrefix(h, "Bearer ") {
				accessToken = h[len("Bearer "):]
			}
		}
		if accessToken == "" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		var err error // Needed because otherwise the next line shadows userID...
		userID, err = getUserID(accessToken, *client)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	kind, id, err := getKindAndID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dsKind := fmt.Sprintf("%s--%s", userID, kind)

	resp := make(map[string]interface{}, 0)
	errCode := http.StatusOK
	if id == int64(0) {
		switch r.Method {
		case "POST":
			resp, errCode = insert(c, dsKind, r.Body)
			r.Body.Close()
		case "GET":
			uq, err := newUserQuery(r)
			if err != nil {
				http.Error(w, "Bad Request", http.StatusBadRequest)
				return
			}
			resp, errCode = list(c, dsKind, *uq)
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	} else {
		switch r.Method {
		case "GET":
			resp, errCode = get(c, dsKind, id)
		case "DELETE":
			errCode = delete2(c, dsKind, id)
		case "POST":
			// This is strictly "replace all properties/values", not "add new properties, update existing"
			resp, errCode = update(c, dsKind, id, r.Body)
			r.Body.Close()
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	}
	if errCode != http.StatusOK {
		http.Error(w, "", errCode)
		return
	}
	if err := json.NewEncoder(w).Encode(&resp); err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	w.Header().Add("Content-Type", "application/json")
}

func newUserQuery(r *http.Request) (*userQuery, error) {
	uq := userQuery{
		StartCursor: r.FormValue("start"),
		EndCursor:   r.FormValue("end"),
		Sort:        r.FormValue("sort"),
	}
	if r.FormValue("limit") != "" {
		lim, err := strconv.Atoi(r.FormValue("limit"))
		if err != nil {
			return nil, err
		}
		uq.Limit = lim
	}

	for _, f := range map[string][]string(r.Form)["where"] {
		parts := strings.Split(f, "=")
		if len(parts) != 2 {
			return nil, errors.New("invalid where: " + f)
		}
		uq.Filters = append(uq.Filters, filter{Key: parts[0], Value: parts[1]})
	}
	return &uq, nil
}

func delete2(c appengine.Context, kind string, id int64) int {
	k := datastore.NewKey(c, kind, "", id, nil)
	if err := datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return http.StatusNotFound
		} else {
			c.Errorf("%v", err)
			return http.StatusInternalServerError
		}
	}
	return http.StatusOK
}

func get(c appengine.Context, kind string, id int64) (map[string]interface{}, int) {
	k := datastore.NewKey(c, kind, "", id, nil)
	var pl plist
	if err := datastore.Get(c, k, &pl); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return nil, http.StatusNotFound
		}
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m := plistToMap(pl, k.IntID())
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

func insert(c appengine.Context, kind string, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[createdKey] = time.Now().Unix()

	pl := mapToPlist("", m)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err := datastore.Put(c, k, &pl)
	if err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

type prop struct {
	Name     string
	Value    interface{}
	Multiple bool
	NoIndex  bool
}
type plist []prop

// plistToMap transforms a plist such as you would get from the datastore into a map[string]interface{} suitable for JSON-encoding.
func plistToMap(pl plist, id int64) map[string]interface{} {
	m := make(map[string]interface{})
	for _, p := range pl {
		if strings.Contains(p.Name, ".") {
			parts := strings.Split(p.Name, ".")
			sub := m
			for _, p := range parts[:len(parts)-1] {
				// Traverse the path up until the leaf
				if i, exists := sub[p]; exists {
					// Already seen this path, traverse it
					if ii, ok := i.(map[string]interface{}); ok {
						sub = ii
					} else {
						// Got a sub-property of a non-map property. Uh oh...
						// Not sure it's worth failing/logging though...
					}
				} else {
					// First time down this path, add a new empty map
					next := map[string]interface{}{}
					sub[p] = next
					sub = next
				}
			}
			leaf := parts[len(parts)-1]
			if _, exists := sub[leaf]; exists {
				if !p.Multiple {
					// We would expect p.Multiple to be true here.
					// Not sure it's worth failing/logging though...
				}
				if _, isArr := sub[leaf].([]interface{}); isArr {
					// Already an array here, append to it
					sub[leaf] = append(sub[leaf].([]interface{}), p.Value)
				} else {
					// Already a single value here, should be an array now.
					sub[leaf] = []interface{}{sub[leaf], p.Value}
				}
			} else {
				sub[leaf] = p.Value
			}
		} else {
			if _, exists := m[p.Name]; exists {
				if !p.Multiple {
					// We would expect p.Multiple to be true here.
					// Not sure it's worth failing/logging though...
				}
				if _, isArr := m[p.Name].([]interface{}); isArr {
					// Already an array here, append to it
					m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
				} else {
					// Already a single value here, should be an array now.
					m[p.Name] = []interface{}{m[p.Name], p.Value}
				}
			} else {
				m[p.Name] = p.Value
			}
		}
	}
	m[idKey] = id
	return m
}

// mapToPlist transforms a map[string]interface{} such as you would get from decoding JSON into a plist to store in the datastore.
func mapToPlist(prefix string, m map[string]interface{}) plist {
	pl := make(plist, 0, len(m))
	for k, v := range m {
		if m, nest := v.(map[string]interface{}); nest {
			// Generate a plist for this sub-map, and append it
			pl = append(pl, mapToPlist(prefix+k+".", m)...)
		} else if _, mult := v.([]interface{}); mult {
			// Generate a prop for every item in the slice
			for _, mv := range v.([]interface{}) {
				pl = append(pl, prop{
					Name:     prefix + k,
					Value:    mv,
					Multiple: true,
				})
			}
			// TODO: Apparently no way to store an empty list? That seems odd...
		} else {
			pl = append(pl, prop{
				Name:  prefix + k,
				Value: v,
			})
		}
	}
	return pl
}

func list(c appengine.Context, kind string, uq userQuery) (map[string]interface{}, int) {
	q := datastore.NewQuery(kind)

	if uq.Limit != 0 {
		q = q.Limit(uq.Limit)
	}
	if uq.Sort != "" {
		q = q.Order(uq.Sort)
	}
	if c, err := datastore.DecodeCursor(uq.StartCursor); err == nil {
		q = q.Start(c)
	}
	if c, err := datastore.DecodeCursor(uq.EndCursor); err == nil {
		q = q.End(c)
	}
	// TODO: Support numerical filters, not just strings
	for _, f := range uq.Filters {
		q = q.Filter(f.Key, f.Value)
	}

	items := make([]map[string]interface{}, 0)

	var crs datastore.Cursor
	for t := q.Run(c); ; {
		var pl plist
		k, err := t.Next(&pl)
		if err == datastore.Done {
			break
		}
		if err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
		m := plistToMap(pl, k.IntID())
		items = append(items, m)
		if crs, err = t.Cursor(); err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
	}
	r := map[string]interface{}{
		"items":          items,
		"nextStartToken": crs.String(),
	}
	return r, http.StatusOK
}

func update(c appengine.Context, kind string, id int64, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[updatedKey] = time.Now().Unix()

	pl := mapToPlist("", m)

	k := datastore.NewKey(c, kind, "", id, nil)
	if _, err := datastore.Put(c, k, &pl); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = id
	return m, http.StatusOK
}
<|endoftext|>


<commit_before>package images

import (
	"fmt"
	"log"
	"os"
	"sort"
	"text/tabwriter"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/fatih/color"
)

type AwsImages struct {
	svc *ec2.EC2

	images []*ec2.Image
}

func NewAwsImages(region string) *AwsImages {
	return &AwsImages{
		svc: ec2.New(&aws.Config{Region: region}),
	}
}

func (a *AwsImages) Fetch() error {
	input := &ec2.DescribeImagesInput{
		Owners: stringSlice("self"),
	}

	resp, err := a.svc.DescribeImages(input)
	if err != nil {
		return err
	}

	a.images = make([]*ec2.Image, len(resp.Images))
	for i, image := range resp.Images {
		a.images[i] = image
	}

	// sort from oldest to newest
	sort.Sort(a)

	return err
}

func (a *AwsImages) Print() {
	if len(a.images) == 0 {
		fmt.Println("no images found")
		return
	}

	color.Green("AWS: %d images found\n\n", len(a.images))

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 10, 8, 0, '\t', 0)
	defer w.Flush()

	fmt.Fprintln(w, "    Name\tID\tTags")

	for i, image := range a.images {
		tags := make([]string, len(image.Tags))
		for i, tag := range image.Tags {
			tags[i] = *tag.Key + ":" + *tag.Value
		}

		fmt.Fprintf(w, "[%d] %s\t%s\t%+v\n", i, *image.Name, *image.ImageID, tags)
	}
}

func stringSlice(vals ...string) []*string {
	a := make([]*string, len(vals))

	for i, v := range vals {
		a[i] = aws.String(v)
	}

	return a
}

func (a *AwsImages) Len() int {
	return len(a.images)
}

func (a *AwsImages) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, *a.images[i].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	jt, err := time.Parse(time.RFC3339, *a.images[j].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	return it.Before(jt)
}

func (a *AwsImages) Swap(i, j int) {
	a.images[i], a.images[j] = a.images[j], a.images[i]
}
<commit_msg>aws: only sort if the slice is large<commit_after>package images

import (
	"fmt"
	"log"
	"os"
	"sort"
	"text/tabwriter"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/ec2"
	"github.com/fatih/color"
)

type AwsImages struct {
	svc *ec2.EC2

	images []*ec2.Image
}

func NewAwsImages(region string) *AwsImages {
	return &AwsImages{
		svc: ec2.New(&aws.Config{Region: region}),
	}
}

func (a *AwsImages) Fetch() error {
	input := &ec2.DescribeImagesInput{
		Owners: stringSlice("self"),
	}

	resp, err := a.svc.DescribeImages(input)
	if err != nil {
		return err
	}

	a.images = make([]*ec2.Image, len(resp.Images))
	for i, image := range resp.Images {
		a.images[i] = image
	}

	// sort from oldest to newest
	if len(a.images) > 1 {
		sort.Sort(a)
	}

	return err
}

func (a *AwsImages) Print() {
	if len(a.images) == 0 {
		fmt.Println("no images found")
		return
	}

	color.Green("AWS: %d images found\n\n", len(a.images))

	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 10, 8, 0, '\t', 0)
	defer w.Flush()

	fmt.Fprintln(w, "    Name\tID\tTags")

	for i, image := range a.images {
		tags := make([]string, len(image.Tags))
		for i, tag := range image.Tags {
			tags[i] = *tag.Key + ":" + *tag.Value
		}

		fmt.Fprintf(w, "[%d] %s\t%s\t%+v\n", i, *image.Name, *image.ImageID, tags)
	}
}

func stringSlice(vals ...string) []*string {
	a := make([]*string, len(vals))

	for i, v := range vals {
		a[i] = aws.String(v)
	}

	return a
}

func (a *AwsImages) Len() int {
	return len(a.images)
}

func (a *AwsImages) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, *a.images[i].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	jt, err := time.Parse(time.RFC3339, *a.images[j].CreationDate)
	if err != nil {
		log.Println("aws: sorting err: ", err)
	}

	return it.Before(jt)
}

func (a *AwsImages) Swap(i, j int) {
	a.images[i], a.images[j] = a.images[j], a.images[i]
}
<|endoftext|>


<commit_before>/*
 * ZDNS Copyright 2016 Regents of the University of Michigan
 *
 * 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 (
	"flag"
	"io/ioutil"
	"math/rand"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/miekg/dns"
	log "github.com/sirupsen/logrus"
	"github.com/zmap/zdns"
	_ "github.com/zmap/zdns/modules/alookup"
	_ "github.com/zmap/zdns/modules/axfr"
	_ "github.com/zmap/zdns/modules/bindversion"
	_ "github.com/zmap/zdns/modules/dmarc"
	_ "github.com/zmap/zdns/modules/miekg"
	_ "github.com/zmap/zdns/modules/mxlookup"
	_ "github.com/zmap/zdns/modules/nslookup"
	_ "github.com/zmap/zdns/modules/spf"

	"github.com/zmap/zdns/iohandlers"
)

const defaultResolvers := []string{"8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53"}

func main() {

	var gc zdns.GlobalConf

	// global flags relevant to every lookup module
	flags := flag.NewFlagSet("flags", flag.ExitOnError)
	flags.IntVar(&gc.Threads, "threads", 1000, "number of lightweight go threads")
	flags.IntVar(&gc.GoMaxProcs, "go-processes", 0, "number of OS processes (GOMAXPROCS)")
	flags.StringVar(&gc.NamePrefix, "prefix", "", "name to be prepended to what's passed in (e.g., www.)")
	flags.StringVar(&gc.NameOverride, "override-name", "", "name overrides all passed in names")
	flags.BoolVar(&gc.AlexaFormat, "alexa", false, "is input file from Alexa Top Million download")
	flags.BoolVar(&gc.MetadataFormat, "metadata-passthrough", false, "if input records have the form 'name,METADATA', METADATA will be propagated to the output")
	flags.BoolVar(&gc.IterativeResolution, "iterative", false, "Perform own iteration instead of relying on recursive resolver")
	flags.StringVar(&gc.InputFilePath, "input-file", "-", "names to read")
	flags.StringVar(&gc.OutputFilePath, "output-file", "-", "where should JSON output be saved")
	flags.StringVar(&gc.MetadataFilePath, "metadata-file", "", "where should JSON metadata be saved")
	flags.StringVar(&gc.LogFilePath, "log-file", "", "where should JSON logs be saved")

	flags.StringVar(&gc.ResultVerbosity, "result-verbosity", "normal", "Sets verbosity of each output record. Options: short, normal, long, trace")
	flags.StringVar(&gc.IncludeInOutput, "include-fields", "", "Comma separated list of fields to additionally output beyond result verbosity. Options: class, protocol, ttl, resolver, flags")

	flags.IntVar(&gc.Verbosity, "verbosity", 3, "log verbosity: 1 (lowest)--5 (highest)")
	flags.IntVar(&gc.Retries, "retries", 1, "how many times should zdns retry query if timeout or temporary failure")
	flags.IntVar(&gc.MaxDepth, "max-depth", 10, "how deep should we recurse when performing iterative lookups")
	flags.IntVar(&gc.CacheSize, "cache-size", 10000, "how many items can be stored in internal recursive cache")
	flags.BoolVar(&gc.TCPOnly, "tcp-only", false, "Only perform lookups over TCP")
	flags.BoolVar(&gc.UDPOnly, "udp-only", false, "Only perform lookups over UDP")
	flags.BoolVar(&gc.NameServerMode, "name-server-mode", false, "Treats input as nameservers to query with a static query rather than queries to send to a static name server")
	servers_string := flags.String("name-servers", "", "List of DNS servers to use. Can be passed as comma-delimited string or via @/path/to/file. If no port is specified, defaults to 53.")
	localaddr_string := flags.String("local-addr", "", "comma-delimited list of local addresses to use")
	localif_string := flags.String("local-interface", "", "local interface to use")
	config_file := flags.String("conf-file", "/etc/resolv.conf", "config file for DNS servers")
	timeout := flags.Int("timeout", 15, "timeout for resolving an individual name")
	iterationTimeout := flags.Int("iteration-timeout", 4, "timeout for resolving a single iteration in an iterative query")
	class_string := flags.String("class", "INET", "DNS class to query. Options: INET, CSNET, CHAOS, HESIOD, NONE, ANY. Default: INET.")
	nanoSeconds := flags.Bool("nanoseconds", false, "Use nanosecond resolution timestamps")
	// allow module to initialize and add its own flags before we parse
	if len(os.Args) < 2 {
		log.Fatal("No lookup module specified. Valid modules: ", zdns.ValidlookupsString(), ".")
	}
	gc.Module = strings.ToUpper(os.Args[1])
	factory := zdns.GetLookup(gc.Module)
	if factory == nil {
		flags.Parse(os.Args[1:])
		log.Fatal("Invalid lookup module specified. Valid modules: ", zdns.ValidlookupsString(), ".")
	}
	factory.AddFlags(flags)
	flags.Parse(os.Args[2:])
	// Do some basic sanity checking
	// setup global logging
	if gc.LogFilePath != "" {
		f, err := os.OpenFile(gc.LogFilePath, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Fatalf("Unable to open log file (%s): %s", gc.LogFilePath, err.Error())
		}
		log.SetOutput(f)
	}
	// Translate the assigned verbosity level to a logrus log level.
	switch gc.Verbosity {
	case 1: // Fatal
		log.SetLevel(log.FatalLevel)
	case 2: // Error
		log.SetLevel(log.ErrorLevel)
	case 3: // Warnings  (default)
		log.SetLevel(log.WarnLevel)
	case 4: // Information
		log.SetLevel(log.InfoLevel)
	case 5: // Debugging
		log.SetLevel(log.DebugLevel)
	default:
		log.Fatal("Unknown verbosity level specified. Must be between 1 (lowest)--5 (highest)")
	}
	// complete post facto global initialization based on command line arguments
	gc.Timeout = time.Duration(time.Second * time.Duration(*timeout))
	gc.IterationTimeout = time.Duration(time.Second * time.Duration(*iterationTimeout))
	// class initialization
	switch strings.ToUpper(*class_string) {
	case "INET", "IN":
		gc.Class = dns.ClassINET
	case "CSNET", "CS":
		gc.Class = dns.ClassCSNET
	case "CHAOS", "CH":
		gc.Class = dns.ClassCHAOS
	case "HESIOD", "HS":
		gc.Class = dns.ClassHESIOD
	case "NONE":
		gc.Class = dns.ClassNONE
	case "ANY":
		gc.Class = dns.ClassANY
	default:
		log.Fatal("Unknown record class specified. Valid valued are INET (default), CSNET, CHAOS, HESIOD, NONE, ANY")

	}
	if *servers_string == "" {
		// if we're doing recursive resolution, figure out default OS name servers
		// otherwise, use the set of 13 root name servers
		if gc.IterativeResolution {
			gc.NameServers = zdns.RootServers[:]
		} else {
			ns, err := zdns.GetDNSServers(*config_file)
			if err != nil {
				ns = defaultResolvers
				log.Warn("Unable to parse resolvers file. Using ZDNS defaults: ", strings.Join(defaultResolvers, ", "))
			}
			gc.NameServers = ns
		}
		gc.NameServersSpecified = false
		log.Info("No name servers specified. will use: ", strings.Join(gc.NameServers, ", "))
	} else {
		if gc.NameServerMode {
			log.Fatal("name servers cannot be specified on command line in --name-server-mode")
		}
		var ns []string
		if (*servers_string)[0] == '@' {
			filepath := (*servers_string)[1:]
			f, err := ioutil.ReadFile(filepath)
			if err != nil {
				log.Fatalf("Unable to read file (%s): %s", filepath, err.Error())
			}
			if len(f) == 0 {
				log.Fatalf("Empty file (%s)", filepath)
			}
			ns = strings.Split(strings.Trim(string(f), "\n"), "\n")
		} else {
			ns = strings.Split(*servers_string, ",")
		}
		for i, s := range ns {
			ns[i] = zdns.AddDefaultPortToDNSServerName(s)
		}
		gc.NameServers = ns
		gc.NameServersSpecified = true
	}
	if *localaddr_string != "" {
		for _, la := range strings.Split(*localaddr_string, ",") {
			ip := net.ParseIP(la)
			if ip != nil {
				gc.LocalAddrs = append(gc.LocalAddrs, ip)
			} else {
				log.Fatal("Invalid argument for --local-addr (", la, "). Must be a comma-separated list of valid IP addresses.")
			}
		}
		log.Info("using local address: ", localaddr_string)
		gc.LocalAddrSpecified = true
	} else if *localif_string != "" {
		if gc.LocalAddrSpecified {
			log.Fatal("Both --local-addr and --local-interface specified.")
		} else {
			li, err := net.InterfaceByName(*localif_string)
			if err != nil {
				log.Fatal("Invalid local interface specified: ", err)
			}
			addrs, err := li.Addrs()
			if err != nil {
				log.Fatal("Unable to detect addresses of local interface: ", err)
			}
			for _, la := range addrs {
				gc.LocalAddrs = append(gc.LocalAddrs, la.(*net.IPNet).IP)
				gc.LocalAddrSpecified = true
			}
			log.Info("using local interface: ", localif_string)
		}
	} else {
		// Find local address for use in unbound UDP sockets
		if conn, err := net.Dial("udp", "8.8.8.8:53"); err != nil {
			log.Fatal("Unable to find default IP address: ", err)
		} else {
			gc.LocalAddrs = append(gc.LocalAddrs, conn.LocalAddr().(*net.UDPAddr).IP)
		}
	}
	if *nanoSeconds {
		gc.TimeFormat = time.RFC3339Nano
	} else {
		gc.TimeFormat = time.RFC3339
	}
	if gc.GoMaxProcs < 0 {
		log.Fatal("Invalid argument for --go-processes. Must be >1.")
	}
	if gc.GoMaxProcs != 0 {
		runtime.GOMAXPROCS(gc.GoMaxProcs)
	}
	if gc.UDPOnly && gc.TCPOnly {
		log.Fatal("TCP Only and UDP Only are conflicting")
	}
	if gc.NameServerMode && gc.AlexaFormat {
		log.Fatal("Alexa mode is incompatible with name server mode")
	}
	if gc.NameServerMode && gc.MetadataFormat {
		log.Fatal("Metadata mode is incompatible with name server mode")
	}
	if gc.NameServerMode && gc.NameOverride == "" && gc.Module != "BINDVERSION" {
		log.Fatal("Static Name must be defined with --override-name in --name-server-mode unless DNS module does not expect names (e.g., BINDVERSION).")
	}
	// Output Groups are defined by a base + any additional fields that the user wants
	groups := strings.Split(gc.IncludeInOutput, ",")
	if gc.ResultVerbosity != "short" && gc.ResultVerbosity != "normal" && gc.ResultVerbosity != "long" && gc.ResultVerbosity != "trace" {
		log.Fatal("Invalid result verbosity. Options: short, normal, long, trace")
	}

	gc.OutputGroups = append(gc.OutputGroups, gc.ResultVerbosity)
	gc.OutputGroups = append(gc.OutputGroups, groups...)

	if len(flags.Args()) > 0 {
		stat, _ := os.Stdin.Stat()
		// If stdin is piped from the terminal, and we haven't specified a file, and if we have unparsed args
		// use them for a dig-like resolution
		if (stat.Mode()&os.ModeCharDevice) != 0 && gc.InputFilePath == "-" && len(flags.Args()) == 1 {
			gc.PassedName = flags.Args()[0]
		} else {
			log.Fatal("Unused command line flags: ", flags.Args())
		}
	}

	// Seeding for RandomNameServer()
	rand.Seed(time.Now().UnixNano())

	// some modules require multiple passes over a file (this is really just the case for zone files)
	if !factory.AllowStdIn() && gc.InputFilePath == "-" {
		log.Fatal("Specified module does not allow reading from stdin")
	}

	// setup i/o
	gc.InputHandler = iohandlers.NewFileInputHandler(gc.InputFilePath)
	gc.OutputHandler = iohandlers.NewFileOutputHandler(gc.OutputFilePath)

	// allow the factory to initialize itself
	if err := factory.Initialize(&gc); err != nil {
		log.Fatal("Factory was unable to initialize:", err.Error())
	}
	// run it.
	if err := zdns.DoLookups(factory, &gc); err != nil {
		log.Fatal("Unable to run lookups:", err.Error())
	}
	// allow the factory to finalize itself
	if err := factory.Finalize(); err != nil {
		log.Fatal("Factory was unable to finalize:", err.Error())
	}
}
<commit_msg>define function instead of const for default resolver slice (#261)<commit_after>/*
 * ZDNS Copyright 2016 Regents of the University of Michigan
 *
 * 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 (
	"flag"
	"io/ioutil"
	"math/rand"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/miekg/dns"
	log "github.com/sirupsen/logrus"
	"github.com/zmap/zdns"
	_ "github.com/zmap/zdns/modules/alookup"
	_ "github.com/zmap/zdns/modules/axfr"
	_ "github.com/zmap/zdns/modules/bindversion"
	_ "github.com/zmap/zdns/modules/dmarc"
	_ "github.com/zmap/zdns/modules/miekg"
	_ "github.com/zmap/zdns/modules/mxlookup"
	_ "github.com/zmap/zdns/modules/nslookup"
	_ "github.com/zmap/zdns/modules/spf"

	"github.com/zmap/zdns/iohandlers"
)

func main() {

	var gc zdns.GlobalConf

	// global flags relevant to every lookup module
	flags := flag.NewFlagSet("flags", flag.ExitOnError)
	flags.IntVar(&gc.Threads, "threads", 1000, "number of lightweight go threads")
	flags.IntVar(&gc.GoMaxProcs, "go-processes", 0, "number of OS processes (GOMAXPROCS)")
	flags.StringVar(&gc.NamePrefix, "prefix", "", "name to be prepended to what's passed in (e.g., www.)")
	flags.StringVar(&gc.NameOverride, "override-name", "", "name overrides all passed in names")
	flags.BoolVar(&gc.AlexaFormat, "alexa", false, "is input file from Alexa Top Million download")
	flags.BoolVar(&gc.MetadataFormat, "metadata-passthrough", false, "if input records have the form 'name,METADATA', METADATA will be propagated to the output")
	flags.BoolVar(&gc.IterativeResolution, "iterative", false, "Perform own iteration instead of relying on recursive resolver")
	flags.StringVar(&gc.InputFilePath, "input-file", "-", "names to read")
	flags.StringVar(&gc.OutputFilePath, "output-file", "-", "where should JSON output be saved")
	flags.StringVar(&gc.MetadataFilePath, "metadata-file", "", "where should JSON metadata be saved")
	flags.StringVar(&gc.LogFilePath, "log-file", "", "where should JSON logs be saved")

	flags.StringVar(&gc.ResultVerbosity, "result-verbosity", "normal", "Sets verbosity of each output record. Options: short, normal, long, trace")
	flags.StringVar(&gc.IncludeInOutput, "include-fields", "", "Comma separated list of fields to additionally output beyond result verbosity. Options: class, protocol, ttl, resolver, flags")

	flags.IntVar(&gc.Verbosity, "verbosity", 3, "log verbosity: 1 (lowest)--5 (highest)")
	flags.IntVar(&gc.Retries, "retries", 1, "how many times should zdns retry query if timeout or temporary failure")
	flags.IntVar(&gc.MaxDepth, "max-depth", 10, "how deep should we recurse when performing iterative lookups")
	flags.IntVar(&gc.CacheSize, "cache-size", 10000, "how many items can be stored in internal recursive cache")
	flags.BoolVar(&gc.TCPOnly, "tcp-only", false, "Only perform lookups over TCP")
	flags.BoolVar(&gc.UDPOnly, "udp-only", false, "Only perform lookups over UDP")
	flags.BoolVar(&gc.NameServerMode, "name-server-mode", false, "Treats input as nameservers to query with a static query rather than queries to send to a static name server")
	servers_string := flags.String("name-servers", "", "List of DNS servers to use. Can be passed as comma-delimited string or via @/path/to/file. If no port is specified, defaults to 53.")
	localaddr_string := flags.String("local-addr", "", "comma-delimited list of local addresses to use")
	localif_string := flags.String("local-interface", "", "local interface to use")
	config_file := flags.String("conf-file", "/etc/resolv.conf", "config file for DNS servers")
	timeout := flags.Int("timeout", 15, "timeout for resolving an individual name")
	iterationTimeout := flags.Int("iteration-timeout", 4, "timeout for resolving a single iteration in an iterative query")
	class_string := flags.String("class", "INET", "DNS class to query. Options: INET, CSNET, CHAOS, HESIOD, NONE, ANY. Default: INET.")
	nanoSeconds := flags.Bool("nanoseconds", false, "Use nanosecond resolution timestamps")
	// allow module to initialize and add its own flags before we parse
	if len(os.Args) < 2 {
		log.Fatal("No lookup module specified. Valid modules: ", zdns.ValidlookupsString(), ".")
	}
	gc.Module = strings.ToUpper(os.Args[1])
	factory := zdns.GetLookup(gc.Module)
	if factory == nil {
		flags.Parse(os.Args[1:])
		log.Fatal("Invalid lookup module specified. Valid modules: ", zdns.ValidlookupsString(), ".")
	}
	factory.AddFlags(flags)
	flags.Parse(os.Args[2:])
	// Do some basic sanity checking
	// setup global logging
	if gc.LogFilePath != "" {
		f, err := os.OpenFile(gc.LogFilePath, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Fatalf("Unable to open log file (%s): %s", gc.LogFilePath, err.Error())
		}
		log.SetOutput(f)
	}
	// Translate the assigned verbosity level to a logrus log level.
	switch gc.Verbosity {
	case 1: // Fatal
		log.SetLevel(log.FatalLevel)
	case 2: // Error
		log.SetLevel(log.ErrorLevel)
	case 3: // Warnings  (default)
		log.SetLevel(log.WarnLevel)
	case 4: // Information
		log.SetLevel(log.InfoLevel)
	case 5: // Debugging
		log.SetLevel(log.DebugLevel)
	default:
		log.Fatal("Unknown verbosity level specified. Must be between 1 (lowest)--5 (highest)")
	}
	// complete post facto global initialization based on command line arguments
	gc.Timeout = time.Duration(time.Second * time.Duration(*timeout))
	gc.IterationTimeout = time.Duration(time.Second * time.Duration(*iterationTimeout))
	// class initialization
	switch strings.ToUpper(*class_string) {
	case "INET", "IN":
		gc.Class = dns.ClassINET
	case "CSNET", "CS":
		gc.Class = dns.ClassCSNET
	case "CHAOS", "CH":
		gc.Class = dns.ClassCHAOS
	case "HESIOD", "HS":
		gc.Class = dns.ClassHESIOD
	case "NONE":
		gc.Class = dns.ClassNONE
	case "ANY":
		gc.Class = dns.ClassANY
	default:
		log.Fatal("Unknown record class specified. Valid valued are INET (default), CSNET, CHAOS, HESIOD, NONE, ANY")

	}
	if *servers_string == "" {
		// if we're doing recursive resolution, figure out default OS name servers
		// otherwise, use the set of 13 root name servers
		if gc.IterativeResolution {
			gc.NameServers = zdns.RootServers[:]
		} else {
			ns, err := zdns.GetDNSServers(*config_file)
			if err != nil {
				ns = getDefaultResolvers()
				log.Warn("Unable to parse resolvers file. Using ZDNS defaults: ", strings.Join(ns, ", "))
			}
			gc.NameServers = ns
		}
		gc.NameServersSpecified = false
		log.Info("No name servers specified. will use: ", strings.Join(gc.NameServers, ", "))
	} else {
		if gc.NameServerMode {
			log.Fatal("name servers cannot be specified on command line in --name-server-mode")
		}
		var ns []string
		if (*servers_string)[0] == '@' {
			filepath := (*servers_string)[1:]
			f, err := ioutil.ReadFile(filepath)
			if err != nil {
				log.Fatalf("Unable to read file (%s): %s", filepath, err.Error())
			}
			if len(f) == 0 {
				log.Fatalf("Empty file (%s)", filepath)
			}
			ns = strings.Split(strings.Trim(string(f), "\n"), "\n")
		} else {
			ns = strings.Split(*servers_string, ",")
		}
		for i, s := range ns {
			ns[i] = zdns.AddDefaultPortToDNSServerName(s)
		}
		gc.NameServers = ns
		gc.NameServersSpecified = true
	}
	if *localaddr_string != "" {
		for _, la := range strings.Split(*localaddr_string, ",") {
			ip := net.ParseIP(la)
			if ip != nil {
				gc.LocalAddrs = append(gc.LocalAddrs, ip)
			} else {
				log.Fatal("Invalid argument for --local-addr (", la, "). Must be a comma-separated list of valid IP addresses.")
			}
		}
		log.Info("using local address: ", localaddr_string)
		gc.LocalAddrSpecified = true
	} else if *localif_string != "" {
		if gc.LocalAddrSpecified {
			log.Fatal("Both --local-addr and --local-interface specified.")
		} else {
			li, err := net.InterfaceByName(*localif_string)
			if err != nil {
				log.Fatal("Invalid local interface specified: ", err)
			}
			addrs, err := li.Addrs()
			if err != nil {
				log.Fatal("Unable to detect addresses of local interface: ", err)
			}
			for _, la := range addrs {
				gc.LocalAddrs = append(gc.LocalAddrs, la.(*net.IPNet).IP)
				gc.LocalAddrSpecified = true
			}
			log.Info("using local interface: ", localif_string)
		}
	} else {
		// Find local address for use in unbound UDP sockets
		if conn, err := net.Dial("udp", "8.8.8.8:53"); err != nil {
			log.Fatal("Unable to find default IP address: ", err)
		} else {
			gc.LocalAddrs = append(gc.LocalAddrs, conn.LocalAddr().(*net.UDPAddr).IP)
		}
	}
	if *nanoSeconds {
		gc.TimeFormat = time.RFC3339Nano
	} else {
		gc.TimeFormat = time.RFC3339
	}
	if gc.GoMaxProcs < 0 {
		log.Fatal("Invalid argument for --go-processes. Must be >1.")
	}
	if gc.GoMaxProcs != 0 {
		runtime.GOMAXPROCS(gc.GoMaxProcs)
	}
	if gc.UDPOnly && gc.TCPOnly {
		log.Fatal("TCP Only and UDP Only are conflicting")
	}
	if gc.NameServerMode && gc.AlexaFormat {
		log.Fatal("Alexa mode is incompatible with name server mode")
	}
	if gc.NameServerMode && gc.MetadataFormat {
		log.Fatal("Metadata mode is incompatible with name server mode")
	}
	if gc.NameServerMode && gc.NameOverride == "" && gc.Module != "BINDVERSION" {
		log.Fatal("Static Name must be defined with --override-name in --name-server-mode unless DNS module does not expect names (e.g., BINDVERSION).")
	}
	// Output Groups are defined by a base + any additional fields that the user wants
	groups := strings.Split(gc.IncludeInOutput, ",")
	if gc.ResultVerbosity != "short" && gc.ResultVerbosity != "normal" && gc.ResultVerbosity != "long" && gc.ResultVerbosity != "trace" {
		log.Fatal("Invalid result verbosity. Options: short, normal, long, trace")
	}

	gc.OutputGroups = append(gc.OutputGroups, gc.ResultVerbosity)
	gc.OutputGroups = append(gc.OutputGroups, groups...)

	if len(flags.Args()) > 0 {
		stat, _ := os.Stdin.Stat()
		// If stdin is piped from the terminal, and we haven't specified a file, and if we have unparsed args
		// use them for a dig-like resolution
		if (stat.Mode()&os.ModeCharDevice) != 0 && gc.InputFilePath == "-" && len(flags.Args()) == 1 {
			gc.PassedName = flags.Args()[0]
		} else {
			log.Fatal("Unused command line flags: ", flags.Args())
		}
	}

	// Seeding for RandomNameServer()
	rand.Seed(time.Now().UnixNano())

	// some modules require multiple passes over a file (this is really just the case for zone files)
	if !factory.AllowStdIn() && gc.InputFilePath == "-" {
		log.Fatal("Specified module does not allow reading from stdin")
	}

	// setup i/o
	gc.InputHandler = iohandlers.NewFileInputHandler(gc.InputFilePath)
	gc.OutputHandler = iohandlers.NewFileOutputHandler(gc.OutputFilePath)

	// allow the factory to initialize itself
	if err := factory.Initialize(&gc); err != nil {
		log.Fatal("Factory was unable to initialize:", err.Error())
	}
	// run it.
	if err := zdns.DoLookups(factory, &gc); err != nil {
		log.Fatal("Unable to run lookups:", err.Error())
	}
	// allow the factory to finalize itself
	if err := factory.Finalize(); err != nil {
		log.Fatal("Factory was unable to finalize:", err.Error())
	}
}

// getDefaultResolvers returns a slice of default DNS resolvers to be used when no system resolvers could be discovered.
func getDefaultResolvers() []string {
	return []string{"8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53"}
}
<|endoftext|>


<commit_before>package ber

import (
	"bytes"
	"fmt"
   "io"
   "os"
	"reflect"
)

type Packet struct {
	ClassType uint8
	TagType uint8
	Tag uint8
   Value interface{}
	Data *bytes.Buffer
   Children []*Packet
   Description string
}

const (
	TagEOC              = 0x00
	TagBoolean          = 0x01
	TagInteger          = 0x02
	TagBitString        = 0x03
	TagOctetString      = 0x04
	TagNULL             = 0x05
	TagObjectIdentifier = 0x06
	TagObjectDescriptor = 0x07
	TagExternal         = 0x08
	TagRealFloat        = 0x09
	TagEnumerated       = 0x0a
	TagEmbeddedPDV      = 0x0b
	TagUTF8String       = 0x0c
	TagRelativeOID      = 0x0d
	TagSequence         = 0x10
	TagSet              = 0x11
	TagNumericString    = 0x12
	TagPrintableString  = 0x13
	TagT61String        = 0x14
	TagVideotexString   = 0x15
	TagIA5String        = 0x16
	TagUTCTime          = 0x17
	TagGeneralizedTime  = 0x18
	TagGraphicString    = 0x19
	TagVisibleString    = 0x1a
	TagGeneralString    = 0x1b
	TagUniversalString  = 0x1c
	TagCharacterString  = 0x1d
	TagBMPString        = 0x1e
	TagBitmask          = 0x1f // xxx11111b
)

var TagMap = map[uint8] string {
	TagEOC              : "EOC (End-of-Content)",
	TagBoolean          : "Boolean",
	TagInteger          : "Integer",
	TagBitString        : "Bit String",
	TagOctetString      : "Octet String",
	TagNULL             : "NULL",
	TagObjectIdentifier : "Object Identifier",
	TagObjectDescriptor : "Object Descriptor",
	TagExternal         : "External",
	TagRealFloat        : "Real (float)",
	TagEnumerated       : "Enumerated",
	TagEmbeddedPDV      : "Embedded PDV",
	TagUTF8String       : "UTF8 String",
	TagRelativeOID      : "Relative-OID",
	TagSequence         : "Sequence and Sequence of",
	TagSet              : "Set and Set OF",
	TagNumericString    : "Numeric String",
	TagPrintableString  : "Printable String",
	TagT61String        : "T61 String",
	TagVideotexString   : "Videotex String",
	TagIA5String        : "IA5 String",
	TagUTCTime          : "UTC Time",
	TagGeneralizedTime  : "Generalized Time",
	TagGraphicString    : "Graphic String",
	TagVisibleString    : "Visible String",
	TagGeneralString    : "General String",
	TagUniversalString  : "Universal String",
	TagCharacterString  : "Character String",
	TagBMPString        : "BMP String",
}

const (
	ClassUniversal   =   0  // 00xxxxxxb
	ClassApplication =  64  // 01xxxxxxb
	ClassContext     = 128  // 10xxxxxxb
	ClassPrivate     = 192  // 11xxxxxxb
	ClassBitmask     = 192  // 11xxxxxxb
)

var ClassMap = map[uint8] string {
	ClassUniversal   : "Universal",
	ClassApplication : "Application",
	ClassContext     : "Context",
	ClassPrivate     : "Private",
}

const (
	TypePrimative   =  0  // xx0xxxxxb
	TypeConstructed = 32  // xx1xxxxxb
	TypeBitmask     = 32  // xx1xxxxxb
)

var TypeMap = map[uint8] string {
	TypePrimative   : "Primative",
	TypeConstructed : "Constructed",
}

var Debug bool = false

func PrintBytes( buf []byte, indent string ) {
   data_lines := make( []string, ( len( buf ) / 30 ) + 1 )
   num_lines := make( []string, ( len( buf ) / 30 ) + 1 )

   for i, b := range buf {
      data_lines[ i / 30 ] += fmt.Sprintf( "%02x ", b )
      num_lines[ i / 30 ] += fmt.Sprintf( "%02d ", ( i + 1 ) % 100 )
   }

   for i := 0; i < len( data_lines ); i++ {
      fmt.Print( indent + data_lines[ i ] + "\n" )
      fmt.Print( indent + num_lines[ i ] + "\n\n" )
   }
}

func PrintPacket( p *Packet ) {
   printPacket( p, 0, false )
}

func printPacket( p *Packet, indent int, printBytes bool ) {
	indent_str := ""
	for len(indent_str) != indent {
		indent_str += " "
	}

   class_str := ClassMap[ p.ClassType ]
   tagtype_str := TypeMap[ p.TagType ]
   tag_str := fmt.Sprintf( "0x%02X", p.Tag )

   if p.ClassType == ClassUniversal {
      tag_str = TagMap[ p.Tag ]
   }

   value := fmt.Sprint( p.Value )
   description := ""
   if p.Description != "" {
      description = p.Description + ": "
   }

   fmt.Printf( "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value )

   if printBytes {
      PrintBytes( p.Bytes(), indent_str )
   }

   for _, child := range p.Children {
      printPacket( child, indent + 1, printBytes )
   }
}

func resizeBuffer( in []byte, new_size uint64 ) (out []byte) {
   out = make( []byte, new_size )
   copy( out, in )
   return
}

func readBytes( reader io.Reader, buf []byte ) os.Error {
   idx := 0
   buflen := len( buf )
   for idx < buflen {
      n, err := reader.Read( buf[ idx: ] )
      if err != nil {
         return err
      }
      idx += n
   }
   return nil
}

func ReadPacket( reader io.Reader ) ( *Packet, os.Error) {
   buf := make([]byte, 2)
   err := readBytes( reader, buf )
   if err != nil {
      return nil, err
   }
   idx := uint64(2)
   datalen := uint64(buf[1])
   if Debug {
      fmt.Printf( "Read: datalen = %d len(buf) = %d ", datalen, len( buf ) )
      for _, b := range buf {
         fmt.Printf( "%02X ", b )
      }
      fmt.Printf( "\n" )
   }
   if datalen & 128 != 0 {
      a := datalen - 128
      idx += a
      buf = resizeBuffer( buf, 2 + a )
      err := readBytes( reader, buf[2:] )
      if err != nil {
         return nil, err
      }
      datalen = DecodeInteger( buf[ 2:2+a ] )
      if Debug {
         fmt.Printf( "Read: a = %d  idx = %d  datalen = %d  len(buf) = %d", a, idx, datalen, len( buf ) )
         for _, b := range buf {
            fmt.Printf( "%02X ", b )
         }
         fmt.Printf( "\n" )
      }
   }

   buf = resizeBuffer( buf, idx + datalen )
   err = readBytes( reader, buf[idx:] )
   if err != nil {
      return nil, err
   }

   if Debug {
      fmt.Printf( "Read: len( buf ) = %d  idx=%d datalen=%d idx+datalen=%d\n", len( buf ), idx, datalen, idx + datalen )
      for _, b := range buf {
         fmt.Printf( "%02X ", b )
      }
   }

   p := DecodePacket( buf )
   return p, nil
}

func DecodeString( data []byte ) (ret string) {
   for _, c := range data {
      ret += fmt.Sprintf( "%c", c )
	}
	return
}

func DecodeInteger( data []byte ) (ret uint64) {
   for _, i := range data {
		ret = ret * 256
		ret = ret + uint64(i)
	}
	return
}

func EncodeInteger( val uint64 ) []byte {
	var out bytes.Buffer
	found := false
	shift := uint(56)
	mask := uint64(0xFF00000000000000)
	for mask > 0 {
		if !found && ( val & mask != 0 ) {
			found = true
		}
		if found || ( shift == 0 ) {
			out.Write( []byte { byte( ( val & mask ) >> shift ) } )
		}
		shift -= 8
		mask = mask >> 8
	}
	return out.Bytes()
}

func DecodePacket( data []byte ) *Packet {
   p, _ := decodePacket( data )
   return p
}

func decodePacket( data []byte ) (*Packet, []byte) {
   if Debug {
      fmt.Printf( "decodePacket: enter %d\n", len( data ) )
   }
   p := new( Packet )
   p.ClassType = data[0] & ClassBitmask
   p.TagType = data[0] & TypeBitmask
   p.Tag = data[0] & TagBitmask

   datalen := DecodeInteger( data[1:2] )
   datapos := uint64(2)
   if datalen & 128 != 0 {
      datalen -= 128
      datapos += datalen
      datalen = DecodeInteger( data[2:2+datalen] )
   }

   p.Data = new( bytes.Buffer )
   p.Children = make( []*Packet, 0, 2 )
   p.Value = nil

   value_data := data[datapos:datapos+datalen]

   if p.TagType == TypeConstructed {
      for len( value_data ) != 0 {
         var child *Packet
         child, value_data = decodePacket( value_data )
         p.AppendChild( child )
      }
   } else if p.ClassType == ClassUniversal {
      p.Data.Write( data[datapos:datapos+datalen] )
		switch p.Tag {
			case TagEOC:
			case TagBoolean:
            val := DecodeInteger( value_data )
				p.Value = val != 0
			case TagInteger:
            p.Value = DecodeInteger( value_data )
			case TagBitString:
			case TagOctetString:
            p.Value = DecodeString( value_data )
			case TagNULL:
			case TagObjectIdentifier:
			case TagObjectDescriptor:
			case TagExternal:
			case TagRealFloat:
			case TagEnumerated:
            p.Value = DecodeInteger( value_data )
			case TagEmbeddedPDV:
			case TagUTF8String:
			case TagRelativeOID:
			case TagSequence:
			case TagSet:
			case TagNumericString:
			case TagPrintableString:
            p.Value = DecodeString( value_data )
			case TagT61String:
			case TagVideotexString:
			case TagIA5String:
			case TagUTCTime:
			case TagGeneralizedTime:
			case TagGraphicString:
			case TagVisibleString:
			case TagGeneralString:
			case TagUniversalString:
			case TagCharacterString:
			case TagBMPString:
		}
   } else {
      p.Data.Write( data[datapos:datapos+datalen] )
   }

   return p, data[ datapos + datalen: ]
}

func (p *Packet) DataLength() uint64 {
	return uint64( p.Data.Len() )
}

func (p *Packet) Bytes() []byte {
	var out bytes.Buffer
	out.Write( []byte { p.ClassType | p.TagType | p.Tag } )
	packet_length := EncodeInteger( p.DataLength() )
	if p.DataLength() > 127 || len( packet_length ) > 1 {
		out.Write( []byte { byte( len( packet_length ) | 128 ) } )
		out.Write( packet_length )
	} else {
		out.Write( packet_length )
	}
	out.Write( p.Data.Bytes() )
	return out.Bytes()
}

func (p *Packet) AppendChild( child *Packet ) {
	p.Data.Write( child.Bytes() )
   if len( p.Children ) == cap( p.Children ) {
      newChildren := make( []*Packet, cap( p.Children ) * 2 )
      copy( newChildren, p.Children )
      p.Children = newChildren[0:len(p.Children)]
   }
   p.Children = p.Children[ 0:len(p.Children) + 1 ]
   p.Children[ len( p.Children ) - 1 ] = child
}

func Encode( ClassType, TagType, Tag uint8, Value interface{}, Description string ) *Packet {
	p := new( Packet )
	p.ClassType = ClassType
	p.TagType = TagType
	p.Tag = Tag
	p.Data = new( bytes.Buffer )
   p.Children = make( []*Packet, 0, 2 )
   p.Value = Value
   p.Description = Description

	if Value != nil {
		v := reflect.NewValue(Value)

		if ( ClassType == ClassUniversal ) {
			switch Tag {
				case TagOctetString:
					sv, ok := v.Interface().(string)
					if ok {
						p.Data.Write( []byte(sv) )
					}
			}
		}
	}

	return p
}

func NewSequence( Description string) *Packet {
   return Encode( ClassUniversal, TypePrimative, TagSequence, nil, Description )
}

func NewBoolean( ClassType, TagType, Tag uint8, Value bool, Description string ) *Packet {
   intValue := 0
   if Value {
      intValue = 1
   }

   p := Encode( ClassType, TagType, Tag, nil, Description )
   p.Value = Value
   p.Data.Write( EncodeInteger( uint64(intValue) ) )
   return p
}

func NewInteger( ClassType, TagType, Tag uint8, Value uint64, Description string ) *Packet {
   p := Encode( ClassType, TagType, Tag, nil, Description )
   p.Value = Value
   p.Data.Write( EncodeInteger( Value ) )
   return p
}

func NewString( ClassType, TagType, Tag uint8, Value, Description string ) *Packet {
   p := Encode( ClassType, TagType, Tag, nil, Description )
   p.Value = Value
   p.Data.Write( []byte( Value ) )
   return p
}

<commit_msg>refactoring and fixes for go1.2<commit_after>package ber

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
)

type Packet struct {
	ClassType   uint8
	TagType     uint8
	Tag         uint8
	Value       interface{}
	Data        *bytes.Buffer
	Children    []*Packet
	Description string
}

const (
	TagEOC              = 0x00
	TagBoolean          = 0x01
	TagInteger          = 0x02
	TagBitString        = 0x03
	TagOctetString      = 0x04
	TagNULL             = 0x05
	TagObjectIdentifier = 0x06
	TagObjectDescriptor = 0x07
	TagExternal         = 0x08
	TagRealFloat        = 0x09
	TagEnumerated       = 0x0a
	TagEmbeddedPDV      = 0x0b
	TagUTF8String       = 0x0c
	TagRelativeOID      = 0x0d
	TagSequence         = 0x10
	TagSet              = 0x11
	TagNumericString    = 0x12
	TagPrintableString  = 0x13
	TagT61String        = 0x14
	TagVideotexString   = 0x15
	TagIA5String        = 0x16
	TagUTCTime          = 0x17
	TagGeneralizedTime  = 0x18
	TagGraphicString    = 0x19
	TagVisibleString    = 0x1a
	TagGeneralString    = 0x1b
	TagUniversalString  = 0x1c
	TagCharacterString  = 0x1d
	TagBMPString        = 0x1e
	TagBitmask          = 0x1f // xxx11111b
)

var TagMap = map[uint8]string{
	TagEOC:              "EOC (End-of-Content)",
	TagBoolean:          "Boolean",
	TagInteger:          "Integer",
	TagBitString:        "Bit String",
	TagOctetString:      "Octet String",
	TagNULL:             "NULL",
	TagObjectIdentifier: "Object Identifier",
	TagObjectDescriptor: "Object Descriptor",
	TagExternal:         "External",
	TagRealFloat:        "Real (float)",
	TagEnumerated:       "Enumerated",
	TagEmbeddedPDV:      "Embedded PDV",
	TagUTF8String:       "UTF8 String",
	TagRelativeOID:      "Relative-OID",
	TagSequence:         "Sequence and Sequence of",
	TagSet:              "Set and Set OF",
	TagNumericString:    "Numeric String",
	TagPrintableString:  "Printable String",
	TagT61String:        "T61 String",
	TagVideotexString:   "Videotex String",
	TagIA5String:        "IA5 String",
	TagUTCTime:          "UTC Time",
	TagGeneralizedTime:  "Generalized Time",
	TagGraphicString:    "Graphic String",
	TagVisibleString:    "Visible String",
	TagGeneralString:    "General String",
	TagUniversalString:  "Universal String",
	TagCharacterString:  "Character String",
	TagBMPString:        "BMP String",
}

const (
	ClassUniversal   = 0   // 00xxxxxxb
	ClassApplication = 64  // 01xxxxxxb
	ClassContext     = 128 // 10xxxxxxb
	ClassPrivate     = 192 // 11xxxxxxb
	ClassBitmask     = 192 // 11xxxxxxb
)

var ClassMap = map[uint8]string{
	ClassUniversal:   "Universal",
	ClassApplication: "Application",
	ClassContext:     "Context",
	ClassPrivate:     "Private",
}

const (
	TypePrimative   = 0  // xx0xxxxxb
	TypeConstructed = 32 // xx1xxxxxb
	TypeBitmask     = 32 // xx1xxxxxb
)

var TypeMap = map[uint8]string{
	TypePrimative:   "Primative",
	TypeConstructed: "Constructed",
}

var Debug bool = false

func PrintBytes(buf []byte, indent string) {
	data_lines := make([]string, (len(buf)/30)+1)
	num_lines := make([]string, (len(buf)/30)+1)

	for i, b := range buf {
		data_lines[i/30] += fmt.Sprintf("%02x ", b)
		num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
	}

	for i := 0; i < len(data_lines); i++ {
		fmt.Print(indent + data_lines[i] + "\n")
		fmt.Print(indent + num_lines[i] + "\n\n")
	}
}

func PrintPacket(p *Packet) {
	printPacket(p, 0, false)
}

func printPacket(p *Packet, indent int, printBytes bool) {
	indent_str := ""
	for len(indent_str) != indent {
		indent_str += " "
	}

	class_str := ClassMap[p.ClassType]
	tagtype_str := TypeMap[p.TagType]
	tag_str := fmt.Sprintf("0x%02X", p.Tag)

	if p.ClassType == ClassUniversal {
		tag_str = TagMap[p.Tag]
	}

	value := fmt.Sprint(p.Value)
	description := ""
	if p.Description != "" {
		description = p.Description + ": "
	}

	fmt.Printf("%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)

	if printBytes {
		PrintBytes(p.Bytes(), indent_str)
	}

	for _, child := range p.Children {
		printPacket(child, indent+1, printBytes)
	}
}

func resizeBuffer(in []byte, new_size uint64) (out []byte) {
	out = make([]byte, new_size)
	copy(out, in)
	return
}

func readBytes(reader io.Reader, buf []byte) error {
	idx := 0
	buflen := len(buf)
	for idx < buflen {
		n, err := reader.Read(buf[idx:])
		if err != nil {
			return err
		}
		idx += n
	}
	return nil
}

func ReadPacket(reader io.Reader) (*Packet, error) {
	buf := make([]byte, 2)
	err := readBytes(reader, buf)
	if err != nil {
		return nil, err
	}
	idx := uint64(2)
	datalen := uint64(buf[1])
	if Debug {
		fmt.Printf("Read: datalen = %d len(buf) = %d ", datalen, len(buf))
		for _, b := range buf {
			fmt.Printf("%02X ", b)
		}
		fmt.Printf("\n")
	}
	if datalen&128 != 0 {
		a := datalen - 128
		idx += a
		buf = resizeBuffer(buf, 2+a)
		err := readBytes(reader, buf[2:])
		if err != nil {
			return nil, err
		}
		datalen = DecodeInteger(buf[2 : 2+a])
		if Debug {
			fmt.Printf("Read: a = %d  idx = %d  datalen = %d  len(buf) = %d", a, idx, datalen, len(buf))
			for _, b := range buf {
				fmt.Printf("%02X ", b)
			}
			fmt.Printf("\n")
		}
	}

	buf = resizeBuffer(buf, idx+datalen)
	err = readBytes(reader, buf[idx:])
	if err != nil {
		return nil, err
	}

	if Debug {
		fmt.Printf("Read: len( buf ) = %d  idx=%d datalen=%d idx+datalen=%d\n", len(buf), idx, datalen, idx+datalen)
		for _, b := range buf {
			fmt.Printf("%02X ", b)
		}
	}

	p := DecodePacket(buf)
	return p, nil
}

func DecodeString(data []byte) (ret string) {
	for _, c := range data {
		ret += fmt.Sprintf("%c", c)
	}
	return
}

func DecodeInteger(data []byte) (ret uint64) {
	for _, i := range data {
		ret = ret * 256
		ret = ret + uint64(i)
	}
	return
}

func EncodeInteger(val uint64) []byte {
	var out bytes.Buffer
	found := false
	shift := uint(56)
	mask := uint64(0xFF00000000000000)
	for mask > 0 {
		if !found && (val&mask != 0) {
			found = true
		}
		if found || (shift == 0) {
			out.Write([]byte{byte((val & mask) >> shift)})
		}
		shift -= 8
		mask = mask >> 8
	}
	return out.Bytes()
}

func DecodePacket(data []byte) *Packet {
	p, _ := decodePacket(data)
	return p
}

func decodePacket(data []byte) (*Packet, []byte) {
	if Debug {
		fmt.Printf("decodePacket: enter %d\n", len(data))
	}
	p := new(Packet)
	p.ClassType = data[0] & ClassBitmask
	p.TagType = data[0] & TypeBitmask
	p.Tag = data[0] & TagBitmask

	datalen := DecodeInteger(data[1:2])
	datapos := uint64(2)
	if datalen&128 != 0 {
		datalen -= 128
		datapos += datalen
		datalen = DecodeInteger(data[2 : 2+datalen])
	}

	p.Data = new(bytes.Buffer)
	p.Children = make([]*Packet, 0, 2)
	p.Value = nil

	value_data := data[datapos : datapos+datalen]

	if p.TagType == TypeConstructed {
		for len(value_data) != 0 {
			var child *Packet
			child, value_data = decodePacket(value_data)
			p.AppendChild(child)
		}
	} else if p.ClassType == ClassUniversal {
		p.Data.Write(data[datapos : datapos+datalen])
		switch p.Tag {
		case TagEOC:
		case TagBoolean:
			val := DecodeInteger(value_data)
			p.Value = val != 0
		case TagInteger:
			p.Value = DecodeInteger(value_data)
		case TagBitString:
		case TagOctetString:
			p.Value = DecodeString(value_data)
		case TagNULL:
		case TagObjectIdentifier:
		case TagObjectDescriptor:
		case TagExternal:
		case TagRealFloat:
		case TagEnumerated:
			p.Value = DecodeInteger(value_data)
		case TagEmbeddedPDV:
		case TagUTF8String:
		case TagRelativeOID:
		case TagSequence:
		case TagSet:
		case TagNumericString:
		case TagPrintableString:
			p.Value = DecodeString(value_data)
		case TagT61String:
		case TagVideotexString:
		case TagIA5String:
		case TagUTCTime:
		case TagGeneralizedTime:
		case TagGraphicString:
		case TagVisibleString:
		case TagGeneralString:
		case TagUniversalString:
		case TagCharacterString:
		case TagBMPString:
		}
	} else {
		p.Data.Write(data[datapos : datapos+datalen])
	}

	return p, data[datapos+datalen:]
}

func (p *Packet) DataLength() uint64 {
	return uint64(p.Data.Len())
}

func (p *Packet) Bytes() []byte {
	var out bytes.Buffer
	out.Write([]byte{p.ClassType | p.TagType | p.Tag})
	packet_length := EncodeInteger(p.DataLength())
	if p.DataLength() > 127 || len(packet_length) > 1 {
		out.Write([]byte{byte(len(packet_length) | 128)})
		out.Write(packet_length)
	} else {
		out.Write(packet_length)
	}
	out.Write(p.Data.Bytes())
	return out.Bytes()
}

func (p *Packet) AppendChild(child *Packet) {
	p.Data.Write(child.Bytes())
	if len(p.Children) == cap(p.Children) {
		newChildren := make([]*Packet, cap(p.Children)*2)
		copy(newChildren, p.Children)
		p.Children = newChildren[0:len(p.Children)]
	}
	p.Children = p.Children[0 : len(p.Children)+1]
	p.Children[len(p.Children)-1] = child
}

func Encode(ClassType, TagType, Tag uint8, Value interface{}, Description string) *Packet {
	p := new(Packet)
	p.ClassType = ClassType
	p.TagType = TagType
	p.Tag = Tag
	p.Data = new(bytes.Buffer)
	p.Children = make([]*Packet, 0, 2)
	p.Value = Value
	p.Description = Description

	if Value != nil {
		v := reflect.ValueOf(Value)

		if ClassType == ClassUniversal {
			switch Tag {
			case TagOctetString:
				sv, ok := v.Interface().(string)
				if ok {
					p.Data.Write([]byte(sv))
				}
			}
		}
	}

	return p
}

func NewSequence(Description string) *Packet {
	return Encode(ClassUniversal, TypePrimative, TagSequence, nil, Description)
}

func NewBoolean(ClassType, TagType, Tag uint8, Value bool, Description string) *Packet {
	intValue := 0
	if Value {
		intValue = 1
	}

	p := Encode(ClassType, TagType, Tag, nil, Description)
	p.Value = Value
	p.Data.Write(EncodeInteger(uint64(intValue)))
	return p
}

func NewInteger(ClassType, TagType, Tag uint8, Value uint64, Description string) *Packet {
	p := Encode(ClassType, TagType, Tag, nil, Description)
	p.Value = Value
	p.Data.Write(EncodeInteger(Value))
	return p
}

func NewString(ClassType, TagType, Tag uint8, Value, Description string) *Packet {
	p := Encode(ClassType, TagType, Tag, nil, Description)
	p.Value = Value
	p.Data.Write([]byte(Value))
	return p
}
<|endoftext|>