<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/joyrexus/buckets"
	"github.com/julienschmidt/httprouter"
)

const verbose = false // if `true` you'll see log output

func main() {
	// Open a buckets database.
	bx, err := buckets.Open(tempFilePath())
	if err != nil {
		log.Fatalf("couldn't open db: %v", err)
	}

	// Delete and close the db when done.
	defer os.Remove(bx.Path())
	defer bx.Close()

	// Create a bucket for storing todos.
	bucket, err := bx.New([]byte("todos"))
	if err != nil {
		log.Fatalf("couldn't create todos bucket: %v", err)
	}

	// Initialize our controller for handling specific routes.
	control := NewController(bucket)

	// Create and setup our router.
	router := httprouter.New()
	router.POST("/day/:day", control.post)
	router.GET("/day/:day", control.getDayTasks)
	router.GET("/weekend", control.getWeekendTasks)
	router.GET("/weekdays", control.getWeekdayTasks)

	// Start our web server.
	srv := httptest.NewServer(router)
	defer srv.Close()

	// Setup daily todos for client to post.
	posts := []*Todo{
		&Todo{Day: "mon", Task: "milk cows"},
		&Todo{Day: "mon", Task: "feed cows"},
		&Todo{Day: "mon", Task: "wash cows"},
		&Todo{Day: "tue", Task: "wash laundry"},
		&Todo{Day: "tue", Task: "fold laundry"},
		&Todo{Day: "tue", Task: "iron laundry"},
		&Todo{Day: "wed", Task: "flip burgers"},
		&Todo{Day: "thu", Task: "join army"},
		&Todo{Day: "fri", Task: "kill time"},
		&Todo{Day: "sat", Task: "have beer"},
		&Todo{Day: "sat", Task: "make merry"},
		&Todo{Day: "sun", Task: "take aspirin"},
		&Todo{Day: "sun", Task: "pray quietly"},
	}

	// Create our client.
	client := new(Client)

	// Use our client to post each daily todo.
	for _, todo := range posts {
		url := srv.URL + "/day/" + todo.Day
		if err := client.post(url, todo); err != nil {
			fmt.Printf("client post error: %v", err)
		}
	}

	// Now, let's try retrieving the persisted todos.

	// Get a list of tasks for each day.
	week := []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
	fmt.Println("daily tasks ...")
	for _, day := range week {
		url := srv.URL + "/day/" + day
		tasks, err := client.get(url)
		if err != nil {
			fmt.Printf("client get error: %v", err)
		}
		fmt.Printf("  %s: %s\n", day, tasks)
	}
	// Output:
	// daily tasks ...
	//   mon: milk cows, feed cows, wash cows
	//   tue: wash laundry, fold laundry, iron laundry
	//   wed: flip burgers
	//   thu: join army
	//   fri: kill time
	//   sat: have beer, make merry
	//   sun: take aspirin, pray quietly

	// Get a list of combined tasks for weekdays.
	tasks, err := client.get(srv.URL + "/weekdays")
	if err != nil {
		fmt.Printf("client get error: %v", err)
	}
	fmt.Printf("\nweekday tasks: %s\n", tasks)
	// Output:
	// weekday tasks: milk cows, feed cows, wash cows, wash laundry,
	// fold laundry, iron laundry, flip burgers, join army, kill time

	// Get a list of combined tasks for the weekend.
	tasks, err = client.get(srv.URL + "/weekend")
	if err != nil {
		fmt.Printf("client get error: %v", err)
	}
	fmt.Printf("\nweekend tasks: %s\n", tasks)
	// Output:
	// weekend tasks: have beer, make merry, take aspirin, pray quietly
}

/* -- MODELS --*/

// A Todo models a daily task.
type Todo struct {
	Task    string    // task to be done
	Day     string    // day to do task
	Created time.Time // when created
}

// Encode marshals a Todo into a buffer.
func (todo *Todo) Encode() (*bytes.Buffer, error) {
	b, err := json.Marshal(todo)
	if err != nil {
		return &bytes.Buffer{}, err
	}
	return bytes.NewBuffer(b), nil
}

// A TaskList is a list of tasks for a particular day.
type TaskList struct {
	When  string
	Tasks []string
}

/* -- CONTROLLER -- */

// NewController initializes a new instance of our controller.
// It provides handler methods for our router.
func NewController(bk *buckets.Bucket) *Controller {
	// map of days to integers
	daynum := map[string]int{
		"mon": 1, // monday is the first day of the week
		"tue": 2,
		"wed": 3,
		"thu": 4,
		"fri": 5,
		"sat": 6,
		"sun": 7,
	}
	// map of scanners for iterating over keys subsets of keys
	scan := map[string]buckets.Scanner{
		"mon": bk.NewPrefixScanner([]byte("1")),
		"tue": bk.NewPrefixScanner([]byte("2")),
		"wed": bk.NewPrefixScanner([]byte("3")),
		"thu": bk.NewPrefixScanner([]byte("4")),
		"fri": bk.NewPrefixScanner([]byte("5")),
		"sat": bk.NewPrefixScanner([]byte("6")),
		"sun": bk.NewPrefixScanner([]byte("7")),
		// weekdays are mon to fri: 1 <= key < 6.
		"weekday": bk.NewRangeScanner([]byte("1"), []byte("6")),
		// weekends are sat to sun: 6 <= key < 8.
		"weekend": bk.NewRangeScanner([]byte("6"), []byte("8")),
	}
	return &Controller{bk, daynum, scan}
}

// This Controller handles requests for todo items.  The items are stored
// in a todos bucket.  The request URLs are used as bucket keys and the
// raw json payload as values.
//
// Note that since we're using `httprouter` (abbreviated as `mux` when
// imported) as our router, each method is a `httprouter.Handle` rather
// than a `http.HandlerFunc`.
type Controller struct {
	todos  *buckets.Bucket
	daynum map[string]int
	scan   map[string]buckets.Scanner
}

// getWeekendTasks handles get requests for `/weekend`, returning the
// combined task list for saturday and sunday.
//
// Note how we utilize the RangeItems method, which makes it easy
// to get items in our todos bucket with keys in a certain range
// (6 <= key < 8), viz., the items for sat and sun.
func (c *Controller) getWeekendTasks(w http.ResponseWriter, r *http.Request,
	_ httprouter.Params) {

	// Get todo items within the weekend range.
	items, err := c.todos.RangeItems([]byte("6"), []byte("8"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{"weekend", []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// getWeekdayTasks handles get requests for `/weekdays`, returning the
// combined task list for monday through friday.
//
// Note how we utilize the RangeItems method, which makes it easy
// to get items in our todos bucket with keys in a certain range
// (1 <= key < 6), viz., the items for mon through fri.
func (c *Controller) getWeekdayTasks(w http.ResponseWriter, r *http.Request,
	_ httprouter.Params) {

	// Get todo items within the weekday range.
	items, err := c.todos.RangeItems([]byte("1"), []byte("6"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{"weekdays", []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// getDayTasks handles get requests for `/:day`, returning a particular
// day's task list.
//
// Note how we utilize the PrefixItems method for the day requested (as
// indicated in the route's `day` parameter). This makes it easy to get
// items in our todos bucket with a certain prefix, viz. those with the
// prefix representing the requested day.
func (c *Controller) getDayTasks(w http.ResponseWriter, r *http.Request,
	p httprouter.Params) {

	// Get todo items for the day requested.
	day := p.ByName("day")
	num := c.daynum[day]
	pre := []byte(strconv.Itoa(num)) // daynum prefix to use
	items, err := c.todos.PrefixItems(pre)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{day, []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// post handles post requests to create a daily todo item.
//
//
func (c *Controller) post(w http.ResponseWriter, r *http.Request,
	p httprouter.Params) {

	// Read request body's json payload into buffer.
	b, err := ioutil.ReadAll(r.Body)
	todo, err := decode(b)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Use the day number + creation time as key.
	day := p.ByName("day")
	num := c.daynum[day] // number of day of week
	created := todo.Created.Format(time.RFC3339Nano)
	key := fmt.Sprintf("%d/%s", num, created)

	// Put key/buffer into todos bucket.
	if err := c.todos.Put([]byte(key), b); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if verbose {
		log.Printf("server: %s: %v", key, todo.Task)
	}

	w.Header().Set("Content-Type", "text/plain")
	fmt.Fprintf(w, "put todo for %s: %s\n", key, todo)
}

/* -- CLIENT -- */

// Our http client for sending requests.
type Client struct{}

// post sends a post request with a json payload.
func (c *Client) post(url string, todo *Todo) error {
	todo.Created = time.Now()
	bodyType := "application/json"
	body, err := todo.Encode()
	if err != nil {
		return err
	}
	resp, err := http.Post(url, bodyType, body)
	if err != nil {
		return err
	}
	if verbose {
		log.Printf("client: %s\n", resp.Status)
	}
	return nil
}

// get sends get requests and expects responses to be a json-encoded
// task list.
func (c *Client) get(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	taskList := new(TaskList)
	if err = json.NewDecoder(resp.Body).Decode(taskList); err != nil {
		return "", err
	}
	return strings.Join(taskList.Tasks, ", "), nil
}

/* -- UTILITY FUNCTIONS, &c. -- */

// decode unmarshals a json-encoded byteslice into a Todo.
func decode(b []byte) (*Todo, error) {
	todo := new(Todo)
	if err := json.Unmarshal(b, todo); err != nil {
		return &Todo{}, err
	}
	return todo, nil
}

// tempFilePath returns a temporary file path.
func tempFilePath() string {
	f, _ := ioutil.TempFile("", "bolt-")
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
	if err := os.Remove(f.Name()); err != nil {
		log.Fatal(err)
	}
	return f.Name()
}
<commit_msg>remove map of scanners<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/joyrexus/buckets"
	"github.com/julienschmidt/httprouter"
)

const verbose = false // if `true` you'll see log output

func main() {
	// Open a buckets database.
	bx, err := buckets.Open(tempFilePath())
	if err != nil {
		log.Fatalf("couldn't open db: %v", err)
	}

	// Delete and close the db when done.
	defer os.Remove(bx.Path())
	defer bx.Close()

	// Create a bucket for storing todos.
	bucket, err := bx.New([]byte("todos"))
	if err != nil {
		log.Fatalf("couldn't create todos bucket: %v", err)
	}

	// Initialize our controller for handling specific routes.
	control := NewController(bucket)

	// Create and setup our router.
	router := httprouter.New()
	router.POST("/day/:day", control.post)
	router.GET("/day/:day", control.getDayTasks)
	router.GET("/weekend", control.getWeekendTasks)
	router.GET("/weekdays", control.getWeekdayTasks)

	// Start our web server.
	srv := httptest.NewServer(router)
	defer srv.Close()

	// Setup daily todos for client to post.
	posts := []*Todo{
		&Todo{Day: "mon", Task: "milk cows"},
		&Todo{Day: "mon", Task: "feed cows"},
		&Todo{Day: "mon", Task: "wash cows"},
		&Todo{Day: "tue", Task: "wash laundry"},
		&Todo{Day: "tue", Task: "fold laundry"},
		&Todo{Day: "tue", Task: "iron laundry"},
		&Todo{Day: "wed", Task: "flip burgers"},
		&Todo{Day: "thu", Task: "join army"},
		&Todo{Day: "fri", Task: "kill time"},
		&Todo{Day: "sat", Task: "have beer"},
		&Todo{Day: "sat", Task: "make merry"},
		&Todo{Day: "sun", Task: "take aspirin"},
		&Todo{Day: "sun", Task: "pray quietly"},
	}

	// Create our client.
	client := new(Client)

	// Use our client to post each daily todo.
	for _, todo := range posts {
		url := srv.URL + "/day/" + todo.Day
		if err := client.post(url, todo); err != nil {
			fmt.Printf("client post error: %v", err)
		}
	}

	// Now, let's try retrieving the persisted todos.

	// Get a list of tasks for each day.
	week := []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
	fmt.Println("daily tasks ...")
	for _, day := range week {
		url := srv.URL + "/day/" + day
		tasks, err := client.get(url)
		if err != nil {
			fmt.Printf("client get error: %v", err)
		}
		fmt.Printf("  %s: %s\n", day, tasks)
	}
	// Output:
	// daily tasks ...
	//   mon: milk cows, feed cows, wash cows
	//   tue: wash laundry, fold laundry, iron laundry
	//   wed: flip burgers
	//   thu: join army
	//   fri: kill time
	//   sat: have beer, make merry
	//   sun: take aspirin, pray quietly

	// Get a list of combined tasks for weekdays.
	tasks, err := client.get(srv.URL + "/weekdays")
	if err != nil {
		fmt.Printf("client get error: %v", err)
	}
	fmt.Printf("\nweekday tasks: %s\n", tasks)
	// Output:
	// weekday tasks: milk cows, feed cows, wash cows, wash laundry,
	// fold laundry, iron laundry, flip burgers, join army, kill time

	// Get a list of combined tasks for the weekend.
	tasks, err = client.get(srv.URL + "/weekend")
	if err != nil {
		fmt.Printf("client get error: %v", err)
	}
	fmt.Printf("\nweekend tasks: %s\n", tasks)
	// Output:
	// weekend tasks: have beer, make merry, take aspirin, pray quietly
}

/* -- MODELS --*/

// A Todo models a daily task.
type Todo struct {
	Task    string    // task to be done
	Day     string    // day to do task
	Created time.Time // when created
}

// Encode marshals a Todo into a buffer.
func (todo *Todo) Encode() (*bytes.Buffer, error) {
	b, err := json.Marshal(todo)
	if err != nil {
		return &bytes.Buffer{}, err
	}
	return bytes.NewBuffer(b), nil
}

// A TaskList is a list of tasks for a particular day.
type TaskList struct {
	When  string
	Tasks []string
}

/* -- CONTROLLER -- */

// NewController initializes a new instance of our controller.
// It provides handler methods for our router.
func NewController(bk *buckets.Bucket) *Controller {
	// map of days to integers
	daynum := map[string]int{
		"mon": 1, // monday is the first day of the week
		"tue": 2,
		"wed": 3,
		"thu": 4,
		"fri": 5,
		"sat": 6,
		"sun": 7,
	}
	return &Controller{bk, daynum}
}

// This Controller handles requests for todo items.  The items are stored
// in a todos bucket.  The request URLs are used as bucket keys and the
// raw json payload as values.
//
// Note that since we're using `httprouter` (abbreviated as `mux` when
// imported) as our router, each method is a `httprouter.Handle` rather
// than a `http.HandlerFunc`.
type Controller struct {
	todos  *buckets.Bucket
	daynum map[string]int
}

// getWeekendTasks handles get requests for `/weekend`, returning the
// combined task list for saturday and sunday.
//
// Note how we utilize the RangeItems method, which makes it easy
// to get items in our todos bucket with keys in a certain range
// (6 <= key < 8), viz., the items for sat and sun.
func (c *Controller) getWeekendTasks(w http.ResponseWriter, r *http.Request,
	_ httprouter.Params) {

	// Get todo items within the weekend range.
	items, err := c.todos.RangeItems([]byte("6"), []byte("8"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{"weekend", []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// getWeekdayTasks handles get requests for `/weekdays`, returning the
// combined task list for monday through friday.
//
// Note how we utilize the RangeItems method, which makes it easy
// to get items in our todos bucket with keys in a certain range
// (1 <= key < 6), viz., the items for mon through fri.
func (c *Controller) getWeekdayTasks(w http.ResponseWriter, r *http.Request,
	_ httprouter.Params) {

	// Get todo items within the weekday range.
	items, err := c.todos.RangeItems([]byte("1"), []byte("6"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{"weekdays", []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// getDayTasks handles get requests for `/:day`, returning a particular
// day's task list.
//
// Note how we utilize the PrefixItems method for the day requested (as
// indicated in the route's `day` parameter). This makes it easy to get
// items in our todos bucket with a certain prefix, viz. those with the
// prefix representing the requested day.
func (c *Controller) getDayTasks(w http.ResponseWriter, r *http.Request,
	p httprouter.Params) {

	// Get todo items for the day requested.
	day := p.ByName("day")
	num := c.daynum[day]
	pre := []byte(strconv.Itoa(num)) // daynum prefix to use
	items, err := c.todos.PrefixItems(pre)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Generate a list of tasks based on todo items retrieved.
	taskList := &TaskList{day, []string{}}

	for _, item := range items {
		todo, err := decode(item.Value)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		taskList.Tasks = append(taskList.Tasks, todo.Task)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// post handles post requests to create a daily todo item.
//
//
func (c *Controller) post(w http.ResponseWriter, r *http.Request,
	p httprouter.Params) {

	// Read request body's json payload into buffer.
	b, err := ioutil.ReadAll(r.Body)
	todo, err := decode(b)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	// Use the day number + creation time as key.
	day := p.ByName("day")
	num := c.daynum[day] // number of day of week
	created := todo.Created.Format(time.RFC3339Nano)
	key := fmt.Sprintf("%d/%s", num, created)

	// Put key/buffer into todos bucket.
	if err := c.todos.Put([]byte(key), b); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if verbose {
		log.Printf("server: %s: %v", key, todo.Task)
	}

	w.Header().Set("Content-Type", "text/plain")
	fmt.Fprintf(w, "put todo for %s: %s\n", key, todo)
}

/* -- CLIENT -- */

// Our http client for sending requests.
type Client struct{}

// post sends a post request with a json payload.
func (c *Client) post(url string, todo *Todo) error {
	todo.Created = time.Now()
	bodyType := "application/json"
	body, err := todo.Encode()
	if err != nil {
		return err
	}
	resp, err := http.Post(url, bodyType, body)
	if err != nil {
		return err
	}
	if verbose {
		log.Printf("client: %s\n", resp.Status)
	}
	return nil
}

// get sends get requests and expects responses to be a json-encoded
// task list.
func (c *Client) get(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	taskList := new(TaskList)
	if err = json.NewDecoder(resp.Body).Decode(taskList); err != nil {
		return "", err
	}
	return strings.Join(taskList.Tasks, ", "), nil
}

/* -- UTILITY FUNCTIONS, &c. -- */

// decode unmarshals a json-encoded byteslice into a Todo.
func decode(b []byte) (*Todo, error) {
	todo := new(Todo)
	if err := json.Unmarshal(b, todo); err != nil {
		return &Todo{}, err
	}
	return todo, nil
}

// tempFilePath returns a temporary file path.
func tempFilePath() string {
	f, _ := ioutil.TempFile("", "bolt-")
	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
	if err := os.Remove(f.Name()); err != nil {
		log.Fatal(err)
	}
	return f.Name()
}
<|endoftext|>


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

package hsts

import (
	"strconv"
	"strings"

	"github.com/google/go-safeweb/safehttp"
)

// Plugin implements automatic HSTS functionality.
type Plugin struct {
	// The time, in seconds, that the browser should remember
	// that a site is only to be accessed using HTTPS.
	MaxAge uint64

	// This field controls the includeSubDomains directive.
	// When DisableIncludeSubDomains is false, all subdomains
	// of the domain where this service is hosted will also be added
	// to the browsers HSTS list.
	DisableIncludeSubDomains bool

	// This field controls the preload directive.
	// This should only be enabled if this site should be
	// added to the browser HSTS preload list, which is supported
	// by all major browsers. See https://hstspreload.org/ for
	// more info.
	Preload bool
}

// NewPlugin creates a new HSTS plugin with safe defaults.
func NewPlugin() Plugin {
	return Plugin{MaxAge: 63072000} // two years in seconds
}

// Before should be executed before the request is sent to the handler.
// The function redirects HTTP requests to HTTPS. When HTTPS traffic
// is received the Strict-Transport-Security header is applied to the
// response.
func (p *Plugin) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	if r.TLS == nil {
		r.URL.Scheme = "https"
		return w.Redirect(r, r.URL.String(), safehttp.StatusMovedPermanently)
	}

	var value strings.Builder
	value.WriteString("max-age=")
	value.WriteString(strconv.FormatUint(p.MaxAge, 10))
	if !p.DisableIncludeSubDomains {
		value.WriteString("; includeSubDomains")
	}
	if p.Preload {
		value.WriteString("; preload")
	}
	h := w.Header()
	if err := h.Set("Strict-Transport-Security", value.String()); err != nil {
		// TODO(@mattiasgrenfeldt): Replace the response with an actual saferesponse somehow.
		return w.ServerError(safehttp.StatusInternalServerError, "Internal Server Error")
	}
	// TODO: Implement header claiming.
	h.MarkImmutable("Strict-Transport-Security")
	return safehttp.Result{}
}
<commit_msg>Added BehindProxy option<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hsts

import (
	"strconv"
	"strings"

	"github.com/google/go-safeweb/safehttp"
)

// Plugin implements automatic HSTS functionality.
type Plugin struct {
	// The time, in seconds, that the browser should remember
	// that a site is only to be accessed using HTTPS.
	MaxAge uint64

	// This field controls the includeSubDomains directive.
	// When DisableIncludeSubDomains is false, all subdomains
	// of the domain where this service is hosted will also be added
	// to the browsers HSTS list.
	DisableIncludeSubDomains bool

	// This field controls the preload directive.
	// This should only be enabled if this site should be
	// added to the browser HSTS preload list, which is supported
	// by all major browsers. See https://hstspreload.org/ for
	// more info.
	Preload bool

	// If this server is behind a proxy that terminates HTTPS
	// traffic then this should be enabled. If this is enabled
	// then the plugin will always send the Strict-Transport-Security
	// header and will not redirect HTTP traffic to HTTPS traffic.
	BehindProxy bool
}

// NewPlugin creates a new HSTS plugin with safe defaults.
func NewPlugin() Plugin {
	return Plugin{MaxAge: 63072000} // two years in seconds
}

// Before should be executed before the request is sent to the handler.
// The function redirects HTTP requests to HTTPS. When HTTPS traffic
// is received the Strict-Transport-Security header is applied to the
// response.
func (p *Plugin) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	if !p.BehindProxy && r.TLS == nil {
		r.URL.Scheme = "https"
		return w.Redirect(r, r.URL.String(), safehttp.StatusMovedPermanently)
	}

	var value strings.Builder
	value.WriteString("max-age=")
	value.WriteString(strconv.FormatUint(p.MaxAge, 10))
	if !p.DisableIncludeSubDomains {
		value.WriteString("; includeSubDomains")
	}
	if p.Preload {
		value.WriteString("; preload")
	}
	h := w.Header()
	if err := h.Set("Strict-Transport-Security", value.String()); err != nil {
		// TODO(@mattiasgrenfeldt): Replace the response with an actual saferesponse somehow.
		return w.ServerError(safehttp.StatusInternalServerError, "Internal Server Error")
	}
	// TODO: Implement header claiming.
	h.MarkImmutable("Strict-Transport-Security")
	return safehttp.Result{}
}
<|endoftext|>


<commit_before>package twitter

import (
	"net/http"

	"github.com/dghubble/sling"
)

// FollowerService provides methods for accessing Twitter friendship endpoints.
type FriendshipService struct {
	sling *sling.Sling
}

// Creates a new friendship service
func newFriendshipService(sling *sling.Sling) *FriendshipService {
	return &FriendshipService{
		sling: sling.Path("friendships/"),
	}
}

// The relationship status between the authenticated user and the target
type FriendshipLookupStatus struct {
	Name        string   `json:"name"`
	ScreenName  string   `json:"screen_name"`
	ID          int64    `json:"id"`
	IDStr       string   `json:"id_str"`
	Connections []string `json:"connections"`
}

// Basic parameters for friendship requests
type FriendshipLookupParams struct {
	UserID     string `url:"user_id,omitempty"`
	ScreenName string `url:"screen_name,omitempty"`
}

// Returns the relationships of the authenticating user to target user
func (s *FriendshipService) Lookup(params *FriendshipLookupParams) (*[]FriendshipLookupStatus, *http.Response, error) {
	friendships := new([]FriendshipLookupStatus)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// Result from the Friendship show function
type FriendshipShowResult struct {
	Relationship FriendshipRelationship `json:"relationship"`
}

// The underlying relationship of the show function
type FriendshipRelationship struct {
	Target FriendshipRelationshipTarget `json:"target"`
	Source FriendshipRelationshipSource `json:"source"`
}

// The target's attributes from the show function
type FriendshipRelationshipTarget struct {
	IDStr      string `json:"id_str"`
	ID         int64  `json:"id"`
	ScreenName string `json:"screen_name"`
	Following  bool   `json:"following"`
	FollowedBy bool   `json:"followed_by"`
}

// The source's attributes from the show function
type FriendshipRelationshipSource struct {
	CanDM                bool   `json:"can_dm"`
	Blocking             bool   `json:"blocking"`
	Muting               bool   `json:"muting"`
	IDStr                string `json:"id_str"`
	AllReplies           bool   `json:"all_replies"`
	WantRetweets         bool   `json:"want_retweets"`
	ID                   int64  `json:"id"`
	MarkedSpam           bool   `json:"marked_spam"`
	ScreenName           string `json:"screen_name"`
	Following            bool   `json:"following"`
	FollowedBy           bool   `json:"followed_by"`
	NotificationsEnabled bool   `json:"notifications_enabled"`
}

// The parameters given to the show function
type FriendshipShowParams struct {
	SourceScreenName string `url:"source_screen_name,omitempty"`
	SourceID         string `url:"source_id,omitempty"`
	TargetScreenName string `url:"target_screen_name,omitempty"`
	TargetID         string `url:"target_id,omitempty"`
}

// Returns the relationship between any two specified users
func (s *FriendshipService) Show(params *FriendshipShowParams) (*FriendshipShowResult, *http.Response, error) {
	friendships := new(FriendshipShowResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// Generic return result
type FriendshipGenericResult struct {
	Name string `json:"name"`
	ID   int64  `json:"id"`
}

// Unfollow a user
func (s *FriendshipService) Destroy(params *FriendshipLookupParams) (*FriendshipGenericResult, *http.Response, error) {
	friendships := new(FriendshipGenericResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("destroy.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// Follow a user
func (s *FriendshipService) Create(params *FriendshipLookupParams) (*FriendshipGenericResult, *http.Response, error) {
	friendships := new(FriendshipGenericResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("create.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}
<commit_msg>Update comments to start with relevant name<commit_after>package twitter

import (
	"net/http"

	"github.com/dghubble/sling"
)

// FollowerService provides methods for accessing Twitter friendship endpoints.
type FriendshipService struct {
	sling *sling.Sling
}

// Creates a new friendship service
func newFriendshipService(sling *sling.Sling) *FriendshipService {
	return &FriendshipService{
		sling: sling.Path("friendships/"),
	}
}

// FriendshipLookupStatus is The relationship status between the authenticated user and the target
type FriendshipLookupStatus struct {
	Name        string   `json:"name"`
	ScreenName  string   `json:"screen_name"`
	ID          int64    `json:"id"`
	IDStr       string   `json:"id_str"`
	Connections []string `json:"connections"`
}

// FriendshipLookupParams are Basic parameters for friendship requests
type FriendshipLookupParams struct {
	UserID     string `url:"user_id,omitempty"`
	ScreenName string `url:"screen_name,omitempty"`
}

// Lookup returns the relationships of the authenticating user to target user
func (s *FriendshipService) Lookup(params *FriendshipLookupParams) (*[]FriendshipLookupStatus, *http.Response, error) {
	friendships := new([]FriendshipLookupStatus)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// FriendshipShowResult is the result from the Friendship show function
type FriendshipShowResult struct {
	Relationship FriendshipRelationship `json:"relationship"`
}

// FriendshipRelationship is the underlying relationship of the show function
type FriendshipRelationship struct {
	Target FriendshipRelationshipTarget `json:"target"`
	Source FriendshipRelationshipSource `json:"source"`
}

// FriendshipRelationshipTarget is the target's attributes from the show function
type FriendshipRelationshipTarget struct {
	IDStr      string `json:"id_str"`
	ID         int64  `json:"id"`
	ScreenName string `json:"screen_name"`
	Following  bool   `json:"following"`
	FollowedBy bool   `json:"followed_by"`
}

// FriendshipRelationshipSource is the source's attributes from the show function
type FriendshipRelationshipSource struct {
	CanDM                bool   `json:"can_dm"`
	Blocking             bool   `json:"blocking"`
	Muting               bool   `json:"muting"`
	IDStr                string `json:"id_str"`
	AllReplies           bool   `json:"all_replies"`
	WantRetweets         bool   `json:"want_retweets"`
	ID                   int64  `json:"id"`
	MarkedSpam           bool   `json:"marked_spam"`
	ScreenName           string `json:"screen_name"`
	Following            bool   `json:"following"`
	FollowedBy           bool   `json:"followed_by"`
	NotificationsEnabled bool   `json:"notifications_enabled"`
}

// FriendshipShowParams are the parameters given to the show function
type FriendshipShowParams struct {
	SourceScreenName string `url:"source_screen_name,omitempty"`
	SourceID         string `url:"source_id,omitempty"`
	TargetScreenName string `url:"target_screen_name,omitempty"`
	TargetID         string `url:"target_id,omitempty"`
}

// Show returns the relationship between any two specified users
func (s *FriendshipService) Show(params *FriendshipShowParams) (*FriendshipShowResult, *http.Response, error) {
	friendships := new(FriendshipShowResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// FriendshipGenericResult is a generic return result
type FriendshipGenericResult struct {
	Name string `json:"name"`
	ID   int64  `json:"id"`
}

// Destroy unfollows a user
func (s *FriendshipService) Destroy(params *FriendshipLookupParams) (*FriendshipGenericResult, *http.Response, error) {
	friendships := new(FriendshipGenericResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("destroy.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}

// Create follows a user
func (s *FriendshipService) Create(params *FriendshipLookupParams) (*FriendshipGenericResult, *http.Response, error) {
	friendships := new(FriendshipGenericResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("create.json").QueryStruct(params).Receive(friendships, apiError)
	return friendships, resp, relevantError(err, *apiError)
}
<|endoftext|>


<commit_before>package task

import (
	"fmt"
	"neon/build"
	"os"
	"path/filepath"
	"reflect"
)

func init() {
	build.AddTask(build.TaskDesc{
		Name: "neon",
		Func: neon,
		Args: reflect.TypeOf(neonArgs{}),
		Help: `Run a NeON build.

Arguments:

- neon: the build file to run (string).
- targets: the target(s) to run (strings, wrap, optional).

Examples:

    # run target 'foo' of build file 'bar/build.yml'
    - neon:    'bar/build.yml'
      targets: 'foo'`,
	})
}

type neonArgs struct {
	Neon    string   `neon:"file"`
	Targets []string `neon:"optional,wrap"`
}

func neon(context *build.Context, args interface{}) error {
	params := args.(neonArgs)
	path, err := filepath.Abs(params.Neon)
	if err != nil {
		return fmt.Errorf("getting build file path: %v", err)
	}
	base := filepath.Dir(path)
	newBuild, err := build.NewBuild(path, base, context.Build.Repository)
	if err != nil {
		return fmt.Errorf("instantiating build: %v", err)
	}
	dir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("getting current directory: %v", err)
	}
	defer os.Chdir(dir)
	os.Chdir(newBuild.Dir)
	buildContext := build.NewContext(newBuild)
	err = buildContext.Init()
	if err != nil {
		return fmt.Errorf("initializing build context: %v", err)
	}
	err = newBuild.Run(context, params.Targets)
	if err != nil {
		return fmt.Errorf("running build '%s': %v", params.Neon, err)
	}
	return nil
}
<commit_msg>Fixed context error with neon task<commit_after>package task

import (
	"fmt"
	"neon/build"
	"os"
	"path/filepath"
	"reflect"
)

func init() {
	build.AddTask(build.TaskDesc{
		Name: "neon",
		Func: neon,
		Args: reflect.TypeOf(neonArgs{}),
		Help: `Run a NeON build.

Arguments:

- neon: the build file to run (string).
- targets: the target(s) to run (strings, wrap, optional).

Examples:

    # run target 'foo' of build file 'bar/build.yml'
    - neon:    'bar/build.yml'
      targets: 'foo'`,
	})
}

type neonArgs struct {
	Neon    string   `neon:"file"`
	Targets []string `neon:"optional,wrap"`
}

func neon(context *build.Context, args interface{}) error {
	params := args.(neonArgs)
	// FIXME: path relative to build directory
	path, err := filepath.Abs(params.Neon)
	if err != nil {
		return fmt.Errorf("getting build file path: %v", err)
	}
	base := filepath.Dir(path)
	newBuild, err := build.NewBuild(path, base, context.Build.Repository)
	if err != nil {
		return fmt.Errorf("instantiating build: %v", err)
	}
	dir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("getting current directory: %v", err)
	}
	defer os.Chdir(dir)
	os.Chdir(newBuild.Dir)
	newContext := build.NewContext(newBuild)
	err = newContext.Init()
	if err != nil {
		return fmt.Errorf("initializing build context: %v", err)
	}
	err = newBuild.Run(newContext, params.Targets)
	if err != nil {
		return fmt.Errorf("running build '%s': %v", params.Neon, err)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 forgetfs

import "github.com/jacobsa/fuse"

// Create a file system whose sole contents are a file named "foo" and a
// directory named "bar".
//
// The file "foo" may be opened for reading and/or writing, but reads and
// writes aren't supported. Additionally, any non-existent file or directory
// name may be created within any directory, but the resulting inode will
// appear to have been unlinked immediately.
//
// The file system maintains reference counts for the inodes involved. It will
// panic if a reference count becomes negative or if an inode ID is re-used
// after we expect it to be dead. Its Check method may be used to check that
// there are no inodes with non-zero reference counts remaining, after
// unmounting.
func NewFileSystem() (fs *ForgetFS) {
	fs = &ForgetFS{}
	return
}

type ForgetFS struct {
}

func (fs *ForgetFS) ServeOps(c *fuse.Connection) {
	panic("TODO: Export dispatch function from fuseutil and use it here.")
}

// Panic if there are any inodes that have a non-zero reference count. For use
// after unmounting.
func (fs *ForgetFS) Check() {
	panic("TODO")
}
<commit_msg>Revised a plan.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 forgetfs

import (
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
)

// Create a file system whose sole contents are a file named "foo" and a
// directory named "bar".
//
// The file "foo" may be opened for reading and/or writing, but reads and
// writes aren't supported. Additionally, any non-existent file or directory
// name may be created within any directory, but the resulting inode will
// appear to have been unlinked immediately.
//
// The file system maintains reference counts for the inodes involved. It will
// panic if a reference count becomes negative or if an inode ID is re-used
// after we expect it to be dead. Its Check method may be used to check that
// there are no inodes with non-zero reference counts remaining, after
// unmounting.
func NewFileSystem() (fs *ForgetFS) {
	impl := &fsImpl{}

	fs = &ForgetFS{
		impl:   impl,
		server: fuseutil.NewFileSystemServer(impl),
	}

	return
}

////////////////////////////////////////////////////////////////////////
// ForgetFS
////////////////////////////////////////////////////////////////////////

type ForgetFS struct {
	impl   *fsImpl
	server fuse.Server
}

func (fs *ForgetFS) ServeOps(c *fuse.Connection) {
	fs.server.ServeOps(c)
}

// Panic if there are any inodes that have a non-zero reference count. For use
// after unmounting.
func (fs *ForgetFS) Check() {
	panic("TODO")
}

////////////////////////////////////////////////////////////////////////
// Actual implementation
////////////////////////////////////////////////////////////////////////

type fsImpl struct {
	fuseutil.NotImplementedFileSystem
}
<|endoftext|>


<commit_before>package store

import (
	"os"
)

var emptyDir = node{V: "", Ds: make(map[string]node), Rev: Dir}

const ErrorPath = "/ctl/err"

const Nop = "nop:"

// This structure should be kept immutable.
type node struct {
	V   string
	Rev int64
	Ds  map[string]node
}

func (n node) String() string {
	return "<node>"
}

func (n node) readdir() []string {
	names := make([]string, len(n.Ds))
	i := 0
	for name := range n.Ds {
		names[i] = name
		i++
	}
	return names
}

func (n node) at(parts []string) (node, os.Error) {
	switch len(parts) {
	case 0:
		return n, nil
	default:
		if n.Ds != nil {
			if m, ok := n.Ds[parts[0]]; ok {
				return m.at(parts[1:])
			}
		}
		return node{}, os.ENOENT
	}
	panic("unreachable")
}

func (n node) get(parts []string) ([]string, int64) {
	switch m, err := n.at(parts); err {
	case os.ENOENT:
		return []string{""}, Missing
	default:
		if len(m.Ds) > 0 {
			return m.readdir(), m.Rev
		} else {
			return []string{m.V}, m.Rev
		}
	}
	panic("unreachable")
}

func (n node) Get(path string) ([]string, int64) {
	if err := checkPath(path); err != nil {
		return []string{""}, Missing
	}

	return n.get(split(path))
}

func (n node) stat(parts []string) (int32, int64) {
	switch m, err := n.at(parts); err {
	case os.ENOENT:
		return 0, Missing
	default:
		l := len(m.Ds)
		if l > 0 {
			return int32(l), m.Rev
		} else {
			return int32(len(m.V)), m.Rev
		}
	}
	panic("unreachable")
}

func (n node) Stat(path string) (int32, int64) {
	if err := checkPath(path); err != nil {
		return 0, Missing
	}

	return n.stat(split(path))
}

func copyMap(a map[string]node) map[string]node {
	b := make(map[string]node)
	for k, v := range a {
		b[k] = v
	}
	return b
}

// Return value is replacement node
func (n node) set(parts []string, v string, rev int64, keep bool) (node, bool) {
	if len(parts) == 0 {
		return node{v, rev, n.Ds}, keep
	}

	n.Ds = copyMap(n.Ds)
	p, ok := n.Ds[parts[0]].set(parts[1:], v, rev, keep)
	n.Ds[parts[0]] = p, ok
	n.Rev = Dir
	return n, len(n.Ds) > 0
}

func (n node) setp(k, v string, rev int64, keep bool) node {
	if err := checkPath(k); err != nil {
		return n
	}

	n, _ = n.set(split(k), v, rev, keep)
	return n
}

func (n node) apply(seqn int64, mut string) (rep node, ev Event) {
	ev.Seqn, ev.Rev, ev.Mut = seqn, seqn, mut
	if mut == Nop {
		ev.Path = "/"
		ev.Rev = nop
		rep = n
		ev.Getter = rep
		return
	}

	var rev int64
	var keep bool
	ev.Path, ev.Body, rev, keep, ev.Err = decode(mut)

	if ev.Err == nil && keep {
		components := split(ev.Path)
		for i := 0; i < len(components)-1; i++ {
			_, dirRev := n.get(components[0 : i+1])
			if dirRev == Missing {
				break
			}
			if dirRev != Dir {
				ev.Err = os.ENOTDIR
				break
			}
		}
	}

	if ev.Err == nil {
		_, curRev := n.Get(ev.Path)
		if rev != Clobber && rev < curRev {
			ev.Err = ErrRevMismatch
		} else if curRev == Dir {
			ev.Err = os.EISDIR
		}
	}

	if ev.Err != nil {
		ev.Path, ev.Body, rev, keep = ErrorPath, ev.Err.String(), Clobber, true
	}

	if !keep {
		ev.Rev = Missing
	}

	rep = n.setp(ev.Path, ev.Body, ev.Rev, keep)
	ev.Getter = rep
	return
}
<commit_msg>store: remove unnecessary syntax check<commit_after>package store

import (
	"os"
)

var emptyDir = node{V: "", Ds: make(map[string]node), Rev: Dir}

const ErrorPath = "/ctl/err"

const Nop = "nop:"

// This structure should be kept immutable.
type node struct {
	V   string
	Rev int64
	Ds  map[string]node
}

func (n node) String() string {
	return "<node>"
}

func (n node) readdir() []string {
	names := make([]string, len(n.Ds))
	i := 0
	for name := range n.Ds {
		names[i] = name
		i++
	}
	return names
}

func (n node) at(parts []string) (node, os.Error) {
	switch len(parts) {
	case 0:
		return n, nil
	default:
		if n.Ds != nil {
			if m, ok := n.Ds[parts[0]]; ok {
				return m.at(parts[1:])
			}
		}
		return node{}, os.ENOENT
	}
	panic("unreachable")
}

func (n node) get(parts []string) ([]string, int64) {
	switch m, err := n.at(parts); err {
	case os.ENOENT:
		return []string{""}, Missing
	default:
		if len(m.Ds) > 0 {
			return m.readdir(), m.Rev
		} else {
			return []string{m.V}, m.Rev
		}
	}
	panic("unreachable")
}

func (n node) Get(path string) ([]string, int64) {
	return n.get(split(path))
}

func (n node) stat(parts []string) (int32, int64) {
	switch m, err := n.at(parts); err {
	case os.ENOENT:
		return 0, Missing
	default:
		l := len(m.Ds)
		if l > 0 {
			return int32(l), m.Rev
		} else {
			return int32(len(m.V)), m.Rev
		}
	}
	panic("unreachable")
}

func (n node) Stat(path string) (int32, int64) {
	if err := checkPath(path); err != nil {
		return 0, Missing
	}

	return n.stat(split(path))
}

func copyMap(a map[string]node) map[string]node {
	b := make(map[string]node)
	for k, v := range a {
		b[k] = v
	}
	return b
}

// Return value is replacement node
func (n node) set(parts []string, v string, rev int64, keep bool) (node, bool) {
	if len(parts) == 0 {
		return node{v, rev, n.Ds}, keep
	}

	n.Ds = copyMap(n.Ds)
	p, ok := n.Ds[parts[0]].set(parts[1:], v, rev, keep)
	n.Ds[parts[0]] = p, ok
	n.Rev = Dir
	return n, len(n.Ds) > 0
}

func (n node) setp(k, v string, rev int64, keep bool) node {
	if err := checkPath(k); err != nil {
		return n
	}

	n, _ = n.set(split(k), v, rev, keep)
	return n
}

func (n node) apply(seqn int64, mut string) (rep node, ev Event) {
	ev.Seqn, ev.Rev, ev.Mut = seqn, seqn, mut
	if mut == Nop {
		ev.Path = "/"
		ev.Rev = nop
		rep = n
		ev.Getter = rep
		return
	}

	var rev int64
	var keep bool
	ev.Path, ev.Body, rev, keep, ev.Err = decode(mut)

	if ev.Err == nil && keep {
		components := split(ev.Path)
		for i := 0; i < len(components)-1; i++ {
			_, dirRev := n.get(components[0 : i+1])
			if dirRev == Missing {
				break
			}
			if dirRev != Dir {
				ev.Err = os.ENOTDIR
				break
			}
		}
	}

	if ev.Err == nil {
		_, curRev := n.Get(ev.Path)
		if rev != Clobber && rev < curRev {
			ev.Err = ErrRevMismatch
		} else if curRev == Dir {
			ev.Err = os.EISDIR
		}
	}

	if ev.Err != nil {
		ev.Path, ev.Body, rev, keep = ErrorPath, ev.Err.String(), Clobber, true
	}

	if !keep {
		ev.Rev = Missing
	}

	rep = n.setp(ev.Path, ev.Body, ev.Rev, keep)
	ev.Getter = rep
	return
}
<|endoftext|>


<commit_before>package models

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"time"

	"github.com/technoweenie/grohl"
)

type IMagick struct{}

type processPipelineStep func(workingDirectoryPath string, inputFilePath string, args *ProcessArgs) (outputFilePath string, err error)

var defaultPipeline = []processPipelineStep{
	downloadRemote,
	preProcessImage,
	processImage,
}

// Process a remote asset url using graphicsmagick with the args supplied
// and write the response to w
func (p *IMagick) Process(w http.ResponseWriter, r *http.Request, args *ProcessArgs) (err error) {
	tempDir, err := createTemporaryWorkspace()
	if err != nil {
		return
	}
	// defer os.RemoveAll(tempDir)

	var filePath string

	for _, step := range defaultPipeline {
		filePath, err = step(tempDir, filePath, args)
		if err != nil {
			return
		}
	}

	// serve response
	http.ServeFile(w, r, filePath)
	return
}

func createTemporaryWorkspace() (string, error) {
	return ioutil.TempDir("", "_firesize")
}

func downloadRemote(tempDir string, _ string, args *ProcessArgs) (string, error) {
	url := args.Url
	inFile := filepath.Join(tempDir, "in")

	grohl.Log(grohl.Data{
		"processor": "imagick",
		"download":  url,
		"local":     inFile,
	})

	out, err := os.Create(inFile)
	if err != nil {
		return inFile, err
	}
	defer out.Close()

	resp, err := http.Get(url)
	if err != nil {
		return inFile, err
	}
	defer resp.Body.Close()

	_, err = io.Copy(out, resp.Body)

	return inFile, err
}

func preProcessImage(tempDir string, inFile string, args *ProcessArgs) (string, error) {
	if isAnimatedGif(inFile) {
		args.Format = "gif" // Total hack cos format is incorrectly .png on example
		return coalesceAnimatedGif(tempDir, inFile)
	} else {
		return inFile, nil
	}
}

func processImage(tempDir string, inFile string, args *ProcessArgs) (string, error) {
	outFile := filepath.Join(tempDir, "out")
	cmdArgs, outFileWithFormat := args.CommandArgs(inFile, outFile)

	grohl.Log(grohl.Data{
		"processor": "imagick",
		"args":      cmdArgs,
	})

	executable := "convert"
	cmd := exec.Command(executable, cmdArgs...)
	var outErr bytes.Buffer
	cmd.Stdout, cmd.Stderr = &outErr, &outErr
	err := runWithTimeout(cmd, 60*time.Second)
	if err != nil {
		grohl.Log(grohl.Data{
			"processor": "imagick",
			"failure":   err,
			"args":      cmdArgs,
			"output":    string(outErr.Bytes()),
		})
	}

	return outFileWithFormat, err
}

func isAnimatedGif(inFile string) bool {
	// identify -format %n updates-product-click.gif # => 105
	cmd := exec.Command("identify", "-format", "%n", inFile)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := runWithTimeout(cmd, 10*time.Second)
	if err != nil {
		output := string(stderr.Bytes())
		grohl.Log(grohl.Data{
			"identify-error": err,
			"raw-stderr":     output,
		})
	} else {
		output := string(stdout.Bytes())
		numFrames, err := strconv.Atoi(output)
		if err != nil {
			grohl.Log(grohl.Data{
				"identify-error": "non numeric identify output",
				"raw-stdout":     output,
			})
		} else {
			grohl.Log(grohl.Data{
				"processor":  "imagick",
				"num-frames": numFrames,
			})
			return numFrames > 1
		}
	}
	// if anything fucks out assume not animated
	return false
}

func coalesceAnimatedGif(tempDir string, inFile string) (string, error) {
	outFile := filepath.Join(tempDir, "temp")

	// convert do.gif -coalesce temporary.gif
	cmd := exec.Command("convert", inFile, "-coalesce", outFile)
	_ = runWithTimeout(cmd, 60*time.Second)

	return outFile, nil
}

func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
	// Start the process
	err := cmd.Start()
	if err != nil {
		return err
	}

	// Kill the process if it doesn't exit in time
	defer time.AfterFunc(timeout, func() {
		fmt.Println("command timed out")
		cmd.Process.Kill()
	}).Stop()

	// Wait for the process to finish
	return cmd.Wait()
}
<commit_msg>Make error logging more consistent.<commit_after>package models

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"time"

	"github.com/technoweenie/grohl"
)

type IMagick struct{}

type processPipelineStep func(workingDirectoryPath string, inputFilePath string, args *ProcessArgs) (outputFilePath string, err error)

var defaultPipeline = []processPipelineStep{
	downloadRemote,
	preProcessImage,
	processImage,
}

// Process a remote asset url using graphicsmagick with the args supplied
// and write the response to w
func (p *IMagick) Process(w http.ResponseWriter, r *http.Request, args *ProcessArgs) (err error) {
	tempDir, err := createTemporaryWorkspace()
	if err != nil {
		return
	}
	// defer os.RemoveAll(tempDir)

	var filePath string

	for _, step := range defaultPipeline {
		filePath, err = step(tempDir, filePath, args)
		if err != nil {
			return
		}
	}

	// serve response
	http.ServeFile(w, r, filePath)
	return
}

func createTemporaryWorkspace() (string, error) {
	return ioutil.TempDir("", "_firesize")
}

func downloadRemote(tempDir string, _ string, args *ProcessArgs) (string, error) {
	url := args.Url
	inFile := filepath.Join(tempDir, "in")

	grohl.Log(grohl.Data{
		"processor": "imagick",
		"download":  url,
		"local":     inFile,
	})

	out, err := os.Create(inFile)
	if err != nil {
		return inFile, err
	}
	defer out.Close()

	resp, err := http.Get(url)
	if err != nil {
		return inFile, err
	}
	defer resp.Body.Close()

	_, err = io.Copy(out, resp.Body)

	return inFile, err
}

func preProcessImage(tempDir string, inFile string, args *ProcessArgs) (string, error) {
	if isAnimatedGif(inFile) {
		args.Format = "gif" // Total hack cos format is incorrectly .png on example
		return coalesceAnimatedGif(tempDir, inFile)
	} else {
		return inFile, nil
	}
}

func processImage(tempDir string, inFile string, args *ProcessArgs) (string, error) {
	outFile := filepath.Join(tempDir, "out")
	cmdArgs, outFileWithFormat := args.CommandArgs(inFile, outFile)

	grohl.Log(grohl.Data{
		"processor": "imagick",
		"args":      cmdArgs,
	})

	executable := "convert"
	cmd := exec.Command(executable, cmdArgs...)
	var outErr bytes.Buffer
	cmd.Stdout, cmd.Stderr = &outErr, &outErr
	err := runWithTimeout(cmd, 60*time.Second)
	if err != nil {
		grohl.Log(grohl.Data{
			"processor": "imagick",
			"step":      "convert",
			"failure":   err,
			"args":      cmdArgs,
			"output":    string(outErr.Bytes()),
		})
	}

	return outFileWithFormat, err
}

func isAnimatedGif(inFile string) bool {
	// identify -format %n updates-product-click.gif # => 105
	cmd := exec.Command("identify", "-format", "%n", inFile)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := runWithTimeout(cmd, 10*time.Second)
	if err != nil {
		output := string(stderr.Bytes())
		grohl.Log(grohl.Data{
			"processor": "imagick",
			"step":      "identify",
			"failure":   err,
			"output":    output,
		})
	} else {
		output := string(stdout.Bytes())
		numFrames, err := strconv.Atoi(output)
		if err != nil {
			grohl.Log(grohl.Data{
				"processor": "imagick",
				"step":      "identify",
				"failure":   err,
				"output":    output,
				"message":   "non numeric identify output",
			})
		} else {
			grohl.Log(grohl.Data{
				"processor":  "imagick",
				"step":       "identify",
				"num-frames": numFrames,
			})
			return numFrames > 1
		}
	}
	// if anything fucks out assume not animated
	return false
}

func coalesceAnimatedGif(tempDir string, inFile string) (string, error) {
	outFile := filepath.Join(tempDir, "temp")

	// convert do.gif -coalesce temporary.gif
	cmd := exec.Command("convert", inFile, "-coalesce", outFile)
	_ = runWithTimeout(cmd, 60*time.Second)

	return outFile, nil
}

func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
	// Start the process
	err := cmd.Start()
	if err != nil {
		return err
	}

	// Kill the process if it doesn't exit in time
	defer time.AfterFunc(timeout, func() {
		fmt.Println("command timed out")
		cmd.Process.Kill()
	}).Stop()

	// Wait for the process to finish
	return cmd.Wait()
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 docker

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"os"
	"path"
	"strings"

	"github.com/docker/libcontainer"
	"github.com/docker/libcontainer/cgroups"
	cgroup_fs "github.com/docker/libcontainer/cgroups/fs"
	"github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"github.com/google/cadvisor/container"
	containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
	"github.com/google/cadvisor/fs"
	"github.com/google/cadvisor/info"
	"github.com/google/cadvisor/utils"
)

// Relative path from Docker root to the libcontainer per-container state.
const pathToLibcontainerState = "execdriver/native"

// Path to aufs dir where all the files exist.
// aufs/layers is ignored here since it does not hold a lot of data.
// aufs/mnt contains the mount points used to compose the rootfs. Hence it is also ignored.
var pathToAufsDir = "aufs/diff"

var fileNotFound = errors.New("file not found")

type dockerContainerHandler struct {
	client               *docker.Client
	name                 string
	id                   string
	aliases              []string
	machineInfoFactory   info.MachineInfoFactory
	libcontainerStateDir string
	cgroup               cgroups.Cgroup
	usesAufsDriver       bool
	fsInfo               fs.FsInfo
	storageDirs          []string
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
	dockerRootDir string,
	usesAufsDriver bool,
) (container.ContainerHandler, error) {
	fsInfo, err := fs.NewFsInfo()
	if err != nil {
		return nil, err
	}
	handler := &dockerContainerHandler{
		client:               client,
		name:                 name,
		machineInfoFactory:   machineInfoFactory,
		libcontainerStateDir: path.Join(dockerRootDir, pathToLibcontainerState),
		cgroup: cgroups.Cgroup{
			Parent: "/",
			Name:   name,
		},
		usesAufsDriver: usesAufsDriver,
		fsInfo:         fsInfo,
	}
	handler.storageDirs = append(handler.storageDirs, path.Join(dockerRootDir, pathToAufsDir, path.Base(name)))
	if handler.isDockerRoot() {
		return handler, nil
	}
	id := containerNameToDockerId(name)
	handler.id = id
	ctnr, err := client.InspectContainer(id)
	// We assume that if Inspect fails then the container is not known to docker.
	if err != nil {
		return nil, fmt.Errorf("failed to inspect container %s - %s\n", id, err)
	}
	handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
	return handler, nil
}

func containerNameToDockerId(name string) string {
	id := path.Base(name)

	// Turn systemd cgroup name into Docker ID.
	if useSystemd {
		const systemdDockerPrefix = "docker-"
		if strings.HasPrefix(id, systemdDockerPrefix) {
			id = id[len(systemdDockerPrefix):]
		}

		const systemdScopeSuffix = ".scope"
		if strings.HasSuffix(id, systemdScopeSuffix) {
			id = id[:len(id)-len(systemdScopeSuffix)]
		}
	}

	return id
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:    self.name,
		Aliases: self.aliases,
	}, nil
}

func (self *dockerContainerHandler) isDockerRoot() bool {
	return self.name == "/docker"
}

// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func (self *dockerContainerHandler) readLibcontainerConfig() (config *libcontainer.Config, err error) {
	configPath := path.Join(self.libcontainerStateDir, self.id, "container.json")
	if !utils.FileExists(configPath) {
		// TODO(vishh): Return file name as well once we have a better error interface.
		err = fileNotFound
		return
	}
	f, err := os.Open(configPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open %s - %s\n", configPath, err)
	}
	defer f.Close()
	d := json.NewDecoder(f)
	retConfig := new(libcontainer.Config)
	err = d.Decode(retConfig)
	if err != nil {
		return
	}
	config = retConfig

	// Replace cgroup parent and name with our own since we may be running in a different context.
	*config.Cgroups = self.cgroup

	return
}

func (self *dockerContainerHandler) readLibcontainerState() (state *libcontainer.State, err error) {
	statePath := path.Join(self.libcontainerStateDir, self.id, "state.json")
	if !utils.FileExists(statePath) {
		// TODO(vmarmol): Remove this once we can depend on a newer Docker.
		// Libcontainer changed how its state was stored, try the old way of a "pid" file
		if utils.FileExists(path.Join(self.libcontainerStateDir, self.id, "pid")) {
			// We don't need the old state, return an empty state and we'll gracefully degrade.
			state = new(libcontainer.State)
			return
		}

		// TODO(vishh): Return file name as well once we have a better error interface.
		err = fileNotFound
		return
	}
	f, err := os.Open(statePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open %s - %s\n", statePath, err)
	}
	defer f.Close()
	d := json.NewDecoder(f)
	retState := new(libcontainer.State)
	err = d.Decode(retState)
	if err != nil {
		return
	}
	state = retState

	return
}

func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) info.ContainerSpec {
	var spec info.ContainerSpec
	spec.HasMemory = true
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.HasCpu = true
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	if config.Cgroups.CpusetCpus == "" {
		// All cores are active.
		spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
	} else {
		spec.Cpu.Mask = config.Cgroups.CpusetCpus
	}

	spec.HasNetwork = true
	return spec
}

func (self *dockerContainerHandler) GetSpec() (spec info.ContainerSpec, err error) {
	if self.isDockerRoot() {
		return info.ContainerSpec{}, nil
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	libcontainerConfig, err := self.readLibcontainerConfig()
	if err != nil {
		return
	}

	spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi)

	if self.usesAufsDriver {
		spec.HasFilesystem = true
	}

	return
}

func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
	// No support for non-aufs storage drivers.
	if !self.usesAufsDriver {
		return nil
	}

	// As of now we assume that all the storage dirs are on the same device.
	// The first storage dir will be that of the image layers.
	deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
	if err != nil {
		return err
	}

	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return err
	}
	var limit uint64 = 0
	// Docker does not impose any filesystem limits for containers. So use capacity as limit.
	for _, fs := range mi.Filesystems {
		if fs.Device == deviceInfo.Device {
			limit = fs.Capacity
			break
		}
	}

	fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}

	var usage uint64 = 0
	for _, dir := range self.storageDirs {
		// TODO(Vishh): Add support for external mounts.
		dirUsage, err := self.fsInfo.GetDirUsage(dir)
		if err != nil {
			return err
		}
		usage += dirUsage
	}
	fsStat.Usage = usage
	stats.Filesystem = append(stats.Filesystem, fsStat)

	return nil
}

func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
	if self.isDockerRoot() {
		return &info.ContainerStats{}, nil
	}
	state, err := self.readLibcontainerState()
	if err != nil {
		if err == fileNotFound {
			glog.Errorf("Libcontainer state not found for container %q", self.name)
			return &info.ContainerStats{}, nil
		}
		return
	}

	stats, err = containerLibcontainer.GetStats(&self.cgroup, state)
	if err != nil {
		return
	}
	err = self.getFsStats(stats)
	if err != nil {
		return
	}

	return stats, nil
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.name != "/docker" {
		return []info.ContainerReference{}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}

	// On non-systemd systems Docker containers are under /docker.
	containerPrefix := "/docker"
	if useSystemd {
		containerPrefix = "/system.slice"
	}

	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}

		ref := info.ContainerReference{
			Name:    path.Join(containerPrefix, c.ID),
			Aliases: c.Names,
		}
		ret = append(ret, ref)
	}

	return ret, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	return nil, nil
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return cgroup_fs.GetPids(&self.cgroup)
}

func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
	return fmt.Errorf("watch is unimplemented in the Docker container driver")
}

func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
	// No-op for Docker driver.
	return nil
}
<commit_msg>Only overwrite the Name and Parent in the libcontainer Cgroup.<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 docker

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"os"
	"path"
	"strings"

	"github.com/docker/libcontainer"
	"github.com/docker/libcontainer/cgroups"
	cgroup_fs "github.com/docker/libcontainer/cgroups/fs"
	"github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"github.com/google/cadvisor/container"
	containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
	"github.com/google/cadvisor/fs"
	"github.com/google/cadvisor/info"
	"github.com/google/cadvisor/utils"
)

// Relative path from Docker root to the libcontainer per-container state.
const pathToLibcontainerState = "execdriver/native"

// Path to aufs dir where all the files exist.
// aufs/layers is ignored here since it does not hold a lot of data.
// aufs/mnt contains the mount points used to compose the rootfs. Hence it is also ignored.
var pathToAufsDir = "aufs/diff"

var fileNotFound = errors.New("file not found")

type dockerContainerHandler struct {
	client               *docker.Client
	name                 string
	id                   string
	aliases              []string
	machineInfoFactory   info.MachineInfoFactory
	libcontainerStateDir string
	cgroup               cgroups.Cgroup
	usesAufsDriver       bool
	fsInfo               fs.FsInfo
	storageDirs          []string
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
	dockerRootDir string,
	usesAufsDriver bool,
) (container.ContainerHandler, error) {
	fsInfo, err := fs.NewFsInfo()
	if err != nil {
		return nil, err
	}
	handler := &dockerContainerHandler{
		client:               client,
		name:                 name,
		machineInfoFactory:   machineInfoFactory,
		libcontainerStateDir: path.Join(dockerRootDir, pathToLibcontainerState),
		cgroup: cgroups.Cgroup{
			Parent: "/",
			Name:   name,
		},
		usesAufsDriver: usesAufsDriver,
		fsInfo:         fsInfo,
	}
	handler.storageDirs = append(handler.storageDirs, path.Join(dockerRootDir, pathToAufsDir, path.Base(name)))
	if handler.isDockerRoot() {
		return handler, nil
	}
	id := containerNameToDockerId(name)
	handler.id = id
	ctnr, err := client.InspectContainer(id)
	// We assume that if Inspect fails then the container is not known to docker.
	if err != nil {
		return nil, fmt.Errorf("failed to inspect container %s - %s\n", id, err)
	}
	handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
	return handler, nil
}

func containerNameToDockerId(name string) string {
	id := path.Base(name)

	// Turn systemd cgroup name into Docker ID.
	if useSystemd {
		const systemdDockerPrefix = "docker-"
		if strings.HasPrefix(id, systemdDockerPrefix) {
			id = id[len(systemdDockerPrefix):]
		}

		const systemdScopeSuffix = ".scope"
		if strings.HasSuffix(id, systemdScopeSuffix) {
			id = id[:len(id)-len(systemdScopeSuffix)]
		}
	}

	return id
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:    self.name,
		Aliases: self.aliases,
	}, nil
}

func (self *dockerContainerHandler) isDockerRoot() bool {
	return self.name == "/docker"
}

// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func (self *dockerContainerHandler) readLibcontainerConfig() (config *libcontainer.Config, err error) {
	configPath := path.Join(self.libcontainerStateDir, self.id, "container.json")
	if !utils.FileExists(configPath) {
		// TODO(vishh): Return file name as well once we have a better error interface.
		err = fileNotFound
		return
	}
	f, err := os.Open(configPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open %s - %s\n", configPath, err)
	}
	defer f.Close()
	d := json.NewDecoder(f)
	retConfig := new(libcontainer.Config)
	err = d.Decode(retConfig)
	if err != nil {
		return
	}
	config = retConfig

	// Replace cgroup parent and name with our own since we may be running in a different context.
	config.Cgroups.Name = self.cgroup.Name
	config.Cgroups.Parent = self.cgroup.Parent

	return
}

func (self *dockerContainerHandler) readLibcontainerState() (state *libcontainer.State, err error) {
	statePath := path.Join(self.libcontainerStateDir, self.id, "state.json")
	if !utils.FileExists(statePath) {
		// TODO(vmarmol): Remove this once we can depend on a newer Docker.
		// Libcontainer changed how its state was stored, try the old way of a "pid" file
		if utils.FileExists(path.Join(self.libcontainerStateDir, self.id, "pid")) {
			// We don't need the old state, return an empty state and we'll gracefully degrade.
			state = new(libcontainer.State)
			return
		}

		// TODO(vishh): Return file name as well once we have a better error interface.
		err = fileNotFound
		return
	}
	f, err := os.Open(statePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open %s - %s\n", statePath, err)
	}
	defer f.Close()
	d := json.NewDecoder(f)
	retState := new(libcontainer.State)
	err = d.Decode(retState)
	if err != nil {
		return
	}
	state = retState

	return
}

func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) info.ContainerSpec {
	var spec info.ContainerSpec
	spec.HasMemory = true
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.HasCpu = true
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	if config.Cgroups.CpusetCpus == "" {
		// All cores are active.
		spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
	} else {
		spec.Cpu.Mask = config.Cgroups.CpusetCpus
	}

	spec.HasNetwork = true
	return spec
}

func (self *dockerContainerHandler) GetSpec() (spec info.ContainerSpec, err error) {
	if self.isDockerRoot() {
		return info.ContainerSpec{}, nil
	}
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return
	}
	libcontainerConfig, err := self.readLibcontainerConfig()
	if err != nil {
		return
	}

	spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi)

	if self.usesAufsDriver {
		spec.HasFilesystem = true
	}

	return
}

func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
	// No support for non-aufs storage drivers.
	if !self.usesAufsDriver {
		return nil
	}

	// As of now we assume that all the storage dirs are on the same device.
	// The first storage dir will be that of the image layers.
	deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
	if err != nil {
		return err
	}

	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return err
	}
	var limit uint64 = 0
	// Docker does not impose any filesystem limits for containers. So use capacity as limit.
	for _, fs := range mi.Filesystems {
		if fs.Device == deviceInfo.Device {
			limit = fs.Capacity
			break
		}
	}

	fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}

	var usage uint64 = 0
	for _, dir := range self.storageDirs {
		// TODO(Vishh): Add support for external mounts.
		dirUsage, err := self.fsInfo.GetDirUsage(dir)
		if err != nil {
			return err
		}
		usage += dirUsage
	}
	fsStat.Usage = usage
	stats.Filesystem = append(stats.Filesystem, fsStat)

	return nil
}

func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
	if self.isDockerRoot() {
		return &info.ContainerStats{}, nil
	}
	state, err := self.readLibcontainerState()
	if err != nil {
		if err == fileNotFound {
			glog.Errorf("Libcontainer state not found for container %q", self.name)
			return &info.ContainerStats{}, nil
		}
		return
	}

	stats, err = containerLibcontainer.GetStats(&self.cgroup, state)
	if err != nil {
		return
	}
	err = self.getFsStats(stats)
	if err != nil {
		return
	}

	return stats, nil
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.name != "/docker" {
		return []info.ContainerReference{}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}

	// On non-systemd systems Docker containers are under /docker.
	containerPrefix := "/docker"
	if useSystemd {
		containerPrefix = "/system.slice"
	}

	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}

		ref := info.ContainerReference{
			Name:    path.Join(containerPrefix, c.ID),
			Aliases: c.Names,
		}
		ret = append(ret, ref)
	}

	return ret, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	return nil, nil
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return cgroup_fs.GetPids(&self.cgroup)
}

func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
	return fmt.Errorf("watch is unimplemented in the Docker container driver")
}

func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
	// No-op for Docker driver.
	return nil
}
<|endoftext|>


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

// launch.go [created: Thu, 20 Mar 2014]

/*
Launch instances

the "launch" command can be used to spin up one or more new ec2 instances.

	oti launch -h

*/
package main

import (
	"code.google.com/p/go-uuid/uuid"
	"github.com/bmatsuo/oti/otisub"
	"github.com/bmatsuo/oti/otitag"
	"github.com/crowdmob/goamz/aws"
	awsec2 "github.com/crowdmob/goamz/ec2"

	"flag"
	"fmt"
	"strconv"
	"strings"
)

var launch = otisub.Register("launch", func(args []string) {
	fs := otisub.FlagSet(flag.ExitOnError, "inspect", "imagename [directive ...] ...")
	sessionType := fs.String("s", "launch", "session type for management purposes")
	keyname := fs.String("k", "", "default key pair used to run instances")
	waitPending := fs.Bool("w", false, "wait while instances are 'pending'")
	fs.Parse(args)
	args = fs.Args()

	umfts, err := ParseUserLaunchManifest(args)
	if err != nil {
		Log.Fatal(err)
	}

	if len(umfts) == 0 {
		Log.Fatal("no manifests")
	}

	awsregion := aws.USEast
	awsauth, err := Config.AwsAuth()
	if err != nil {
		Log.Fatalln("error reading aws credentials: ", err)
	}

	ec2 := awsec2.New(awsauth, awsregion)

	// find images based on manifest names (if no image is explicitly specified)
	for _, mft := range ManifestsNeedingImageLookup(umfts) { // mft points into mfts
		images, err := LookupImages(ec2, mft.Name)
		if err != nil {
			Log.Fatal("error locating image ids: ", err)
		}
		if len(images) > 0 {
			Log.Fatal("ambigous results: %v", images)
		}
		mft.Ec2ImageId = images[0].Id
	}

	if DEBUG {
		for _, m := range umfts {
			Log.Printf("%#v", m)
		}
	}

	sessionId, err := NewSessionId(*sessionType)
	if err != nil {
		Log.Fatal(err)
	}

	Log.Println("session id: ", sessionId)
	fmt.Println(sessionId) // to stdout

	mfts, err := BuildSystemLaunchManifests(ec2, sessionId, *keyname, umfts)
	if err != nil {
		Log.Fatalln(err)
	}

	if DEBUG {
		for _, m := range mfts {
			Log.Printf("%#v", m)
		}
	}

	var haserrors bool
	for _, m := range mfts {
		runopts := &awsec2.RunInstancesOptions{
			ImageId:        m.Ec2.ImageId,
			MinCount:       m.Min,
			MaxCount:       m.Max,
			KeyName:        m.Ec2.KeyName,
			InstanceType:   m.Ec2.InstanceType,
			SecurityGroups: m.Ec2.SecurityGroups,
		}
		resp, err := ec2.RunInstances(runopts)
		if err != nil {
			haserrors = true
			Log.Printf("error running %q: %v", m.Name, err)
			continue
		}

		var instanceIds []string
		for _, inst := range resp.Instances {
			instanceIds = append(instanceIds, inst.InstanceId)
		}

		if DEBUG {
			Log.Printf("started instances: %v", instanceIds)
		}

		_, err = ec2.CreateTags(instanceIds, []awsec2.Tag{
			{Config.Ec2Tag(otitag.SessionId), string(m.SessionId)},
		})
		if err != nil {
			haserrors = true
			Log.Printf("error tagging instances: %v", err)
		}
	}
	if haserrors {
		Log.Fatal()
	}

	// wait for instances to boot
	if *waitPending {
		Log.Fatal("waiting not implemented")
	}
})

// create LaunchManifests from the given ULMs. the manifests are given the
// provided session id and, if the ULM does not specify a ec2 key name, the
// provided keyname as well.
func BuildSystemLaunchManifests(ec2 *awsec2.EC2, sessionId SessionId, keyname string, umfts []ULM) ([]LaunchManifest, error) {
	mfts := make([]LaunchManifest, len(umfts))

	// get real security groups.
	secgroups, err := LookupSecurityGroups(ec2, umfts)
	if err != nil {
		return nil, fmt.Errorf("error locating up security groups: %v", err)
	}

	// get key pairs TODO

	// build each LaunchManifest
	for i := range umfts {
		m := &mfts[i]
		um := &umfts[i]
		m.SessionId = sessionId
		m.Name = um.Name
		m.Min = um.Min
		m.Max = um.Max
		m.Ec2.InstanceType = um.Ec2InstanceType
		m.Ec2.ImageId = um.Ec2ImageId
		m.Ec2.KeyName = um.Ec2KeyName
		if m.Ec2.KeyName == "" {
			m.Ec2.KeyName = keyname
		}
		for _, group := range um.Ec2SecGroups {
			found := false
			for _, info := range secgroups {
				if info.Id == group {
					m.Ec2.SecurityGroups = append(m.Ec2.SecurityGroups, info.SecurityGroup)
					found = true
				} else if info.Name == group {
					m.Ec2.SecurityGroups = append(m.Ec2.SecurityGroups, info.SecurityGroup)
					found = true
				}
			}
			if !found {
				return nil, fmt.Errorf("unknown security group: %q", group)
			}
		}
	}

	return mfts, nil
}

func LookupSecurityGroups(ec2 *awsec2.EC2, mfts []ULM) ([]awsec2.SecurityGroupInfo, error) {
	var groups []awsec2.SecurityGroup
	for i := range mfts {
		for _, group := range mfts[i].Ec2SecGroups {
			if strings.HasPrefix(group, "sg-") {
				groups = append(groups, awsec2.SecurityGroup{Id: group})
			} else {
				groups = append(groups, awsec2.SecurityGroup{Name: group})
			}
		}
	}

	resp, err := ec2.SecurityGroups(groups, nil)
	if err != nil {
		return nil, err
	}

	return resp.Groups, nil
}

func ManifestsNeedingImageLookup(ulms []ULM) []*ULM {
	var _ulms []*ULM
	for i := range ulms {
		if ulms[i].Ec2ImageId == "" {
			_ulms = append(_ulms, &ulms[i])
		}
	}
	return _ulms
}

// use the packer config to locate images on ec2.
func LookupImages(ec2 *awsec2.EC2, name string) ([]awsec2.Image, error) {
	return nil, fmt.Errorf("unimplemented")
}

// User Launch Manifest -- information read from the command line
type ULM struct {
	Name            string   // OTI name that can be used to filter images
	Ec2ImageId      string   // AWS EC2 image id.
	Ec2InstanceType string   // AWS EC2 instance type.
	Ec2KeyName      string   // AWS EC2 key pair name
	Ec2SecGroups    []string // Security groups to assign the instances
	Min, Max        int      // may not be empty
}

type ArgumentError struct {
	i   int
	err error
}

func (err ArgumentError) Error() string {
	return fmt.Sprintf("argument %d: %v", err.i, err.err)
}

var ErrEndOfArgs = ArgumentError{-1, fmt.Errorf("no more arguments")}

// parses a set launch manifest. manifests have the form
//	name [ flag[=val] ... ] -- ...
// for reference use the following list of flags and the default values
//	flag      alias  default
//	min              1
//	max              1
//	ec2type          "t1.micro"
//	ami              ""
//	keyname          ""
//	secgroup         ""
func ParseUserLaunchManifest(args []string) ([]ULM, error) {
	ulms := make([]ULM, 0, len(args))
	sepseq := "--"

	parseUlm := func(args []string) (ulm ULM, rest []string, err error) {
		rest = args

		if len(rest) == 0 {
			return ULM{}, nil, ErrEndOfArgs
		}

		if rest[0] == sepseq {
			err := fmt.Errorf("unexpected separator sequence %v", sepseq)
			return ULM{}, nil, err
		}

		ulm.Name, rest = args[0], rest[1:]

		// set defaults
		ulm.Min, ulm.Max = 1, 1
		ulm.Ec2InstanceType = "t1.micro"

		retErr := func(err error) (ULM, []string, error) {
			return ULM{}, nil, err
		}
		ulmErr := func(err error) error { return fmt.Errorf("%v %v", ulm.Name, err) }
		ulmFlagErr := func(key string, err error) error {
			return ulmErr(fmt.Errorf("invalid flag %q: %v", key, err))
		}

		flags := make(map[string][]string)
		for len(rest) > 0 && rest[0] != sepseq {
			var head string
			head, rest = rest[0], rest[1:]
			key, value := head, ""

			pair := strings.SplitN(key, "=", 2)
			if len(pair) == 2 {
				key, value = pair[0], pair[1]
			}

			switch key {
			case "min", "max", "secgroup", "ami", "keyname", "ec2type":
			default:
				err := fmt.Errorf("unexpected flag %v", key)
				return retErr(ulmErr(err))
			}

			flags[key] = append(flags[key], value)
		}

		for k, vs := range flags {
			var err error
			numvs := len(vs)
			switch k {
			case "min":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Min, err = strconv.Atoi(vs[0])
				}
			case "max":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Max, err = strconv.Atoi(vs[0])
				}
			case "secgroup":
				ulm.Ec2SecGroups = vs
			case "ec2type":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2InstanceType = vs[0]
				}
			case "ami":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2ImageId = vs[0]
				}
			case "keyname":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2KeyName = vs[0]
				}
			}
			if err != nil {
				return retErr(ulmFlagErr(k, err))
			}
		}

		if len(ulm.Ec2SecGroups) == 0 {
			return retErr(ulmFlagErr("secgroup", fmt.Errorf("required for now")))
		}

		if ulm.Ec2ImageId == "" {
			return retErr(ulmFlagErr("ami", fmt.Errorf("required for now")))
		}

		if ulm.Min > ulm.Max {
			return retErr(ulmErr(err))
		}

		if len(rest) > 0 && rest[0] == sepseq {
			return ulm, rest[1:], nil
		} else {
			return ulm, nil, nil
		}
	}

	for len(args) > 0 {
		var ulm ULM
		var err error
		ulm, args, err = parseUlm(args)
		if err != nil {
			return nil, err
		}
		ulms = append(ulms, ulm)
	}

	return ulms, nil
}

type LaunchManifest struct {
	Name      string    // configured by the user
	Min, Max  int       // configured by the user
	SessionId SessionId // generated at runtime
	Ec2       struct {
		ImageId        string                 // located AWS image id
		InstanceType   string                 // configured by the user
		KeyName        string                 // configured by the user or generated at run-time
		SecurityGroups []awsec2.SecurityGroup // configured by the user or created at runtime
	}
}

type SessionId string

func NewSessionId(sessiontype string) (SessionId, error) {
	if strings.Contains(sessiontype, ":") {
		return "", fmt.Errorf("session type cannot contain ':'")
	}
	if sessiontype == "" {
		sessiontype = "session"
	}
	sid := SessionId(fmt.Sprintf("%s:%v", sessiontype, uuid.New()))
	return sid, nil
}

func (sid SessionId) Type() string {
	return strings.SplitN(string(sid), ":", 2)[0]
}
<commit_msg>add region flag -r to `oti launch`<commit_after>// Copyright 2014, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// launch.go [created: Thu, 20 Mar 2014]

/*
Launch instances

the "launch" command can be used to spin up one or more new ec2 instances.

	oti launch -h

*/
package main

import (
	"code.google.com/p/go-uuid/uuid"
	"github.com/bmatsuo/oti/otisub"
	"github.com/bmatsuo/oti/otitag"
	"github.com/crowdmob/goamz/aws"
	awsec2 "github.com/crowdmob/goamz/ec2"

	"flag"
	"fmt"
	"strconv"
	"strings"
)

var launch = otisub.Register("launch", func(args []string) {
	fs := otisub.FlagSet(flag.ExitOnError, "inspect", "imagename [directive ...] ...")
	sessionType := fs.String("s", "launch", "session type for management purposes")
	keyname := fs.String("k", "", "default key pair used to run instances")
	region := fs.String("r", "us-east-1", "region to run instances in")
	waitPending := fs.Bool("w", false, "wait while instances are 'pending'")
	fs.Parse(args)
	args = fs.Args()

	umfts, err := ParseUserLaunchManifest(args)
	if err != nil {
		Log.Fatal(err)
	}

	if len(umfts) == 0 {
		Log.Fatal("no manifests")
	}

	if *region != "us-east-1" {
		Log.Fatal("unsupported region %q", *region)
	}
	awsregion := aws.USEast
	awsauth, err := Config.AwsAuth()
	if err != nil {
		Log.Fatalln("error reading aws credentials: ", err)
	}

	ec2 := awsec2.New(awsauth, awsregion)

	// find images based on manifest names (if no image is explicitly specified)
	for _, mft := range ManifestsNeedingImageLookup(umfts) { // mft points into mfts
		images, err := LookupImages(ec2, mft.Name)
		if err != nil {
			Log.Fatal("error locating image ids: ", err)
		}
		if len(images) > 0 {
			Log.Fatal("ambigous results: %v", images)
		}
		mft.Ec2ImageId = images[0].Id
	}

	if DEBUG {
		for _, m := range umfts {
			Log.Printf("%#v", m)
		}
	}

	sessionId, err := NewSessionId(*sessionType)
	if err != nil {
		Log.Fatal(err)
	}

	Log.Println("session id: ", sessionId)
	fmt.Println(sessionId) // to stdout

	mfts, err := BuildSystemLaunchManifests(ec2, sessionId, *keyname, umfts)
	if err != nil {
		Log.Fatalln(err)
	}

	if DEBUG {
		for _, m := range mfts {
			Log.Printf("%#v", m)
		}
	}

	var haserrors bool
	for _, m := range mfts {
		runopts := &awsec2.RunInstancesOptions{
			ImageId:        m.Ec2.ImageId,
			MinCount:       m.Min,
			MaxCount:       m.Max,
			KeyName:        m.Ec2.KeyName,
			InstanceType:   m.Ec2.InstanceType,
			SecurityGroups: m.Ec2.SecurityGroups,
		}
		resp, err := ec2.RunInstances(runopts)
		if err != nil {
			haserrors = true
			Log.Printf("error running %q: %v", m.Name, err)
			continue
		}

		var instanceIds []string
		for _, inst := range resp.Instances {
			instanceIds = append(instanceIds, inst.InstanceId)
		}

		if DEBUG {
			Log.Printf("started instances: %v", instanceIds)
		}

		_, err = ec2.CreateTags(instanceIds, []awsec2.Tag{
			{Config.Ec2Tag(otitag.SessionId), string(m.SessionId)},
		})
		if err != nil {
			haserrors = true
			Log.Printf("error tagging instances: %v", err)
		}
	}
	if haserrors {
		Log.Fatal()
	}

	// wait for instances to boot
	if *waitPending {
		Log.Fatal("waiting not implemented")
	}
})

// create LaunchManifests from the given ULMs. the manifests are given the
// provided session id and, if the ULM does not specify a ec2 key name, the
// provided keyname as well.
func BuildSystemLaunchManifests(ec2 *awsec2.EC2, sessionId SessionId, keyname string, umfts []ULM) ([]LaunchManifest, error) {
	mfts := make([]LaunchManifest, len(umfts))

	// get real security groups.
	secgroups, err := LookupSecurityGroups(ec2, umfts)
	if err != nil {
		return nil, fmt.Errorf("error locating up security groups: %v", err)
	}

	// get key pairs TODO

	// build each LaunchManifest
	for i := range umfts {
		m := &mfts[i]
		um := &umfts[i]
		m.SessionId = sessionId
		m.Name = um.Name
		m.Min = um.Min
		m.Max = um.Max
		m.Ec2.InstanceType = um.Ec2InstanceType
		m.Ec2.ImageId = um.Ec2ImageId
		m.Ec2.KeyName = um.Ec2KeyName
		if m.Ec2.KeyName == "" {
			m.Ec2.KeyName = keyname
		}
		for _, group := range um.Ec2SecGroups {
			found := false
			for _, info := range secgroups {
				if info.Id == group {
					m.Ec2.SecurityGroups = append(m.Ec2.SecurityGroups, info.SecurityGroup)
					found = true
				} else if info.Name == group {
					m.Ec2.SecurityGroups = append(m.Ec2.SecurityGroups, info.SecurityGroup)
					found = true
				}
			}
			if !found {
				return nil, fmt.Errorf("unknown security group: %q", group)
			}
		}
	}

	return mfts, nil
}

func LookupSecurityGroups(ec2 *awsec2.EC2, mfts []ULM) ([]awsec2.SecurityGroupInfo, error) {
	var groups []awsec2.SecurityGroup
	for i := range mfts {
		for _, group := range mfts[i].Ec2SecGroups {
			if strings.HasPrefix(group, "sg-") {
				groups = append(groups, awsec2.SecurityGroup{Id: group})
			} else {
				groups = append(groups, awsec2.SecurityGroup{Name: group})
			}
		}
	}

	resp, err := ec2.SecurityGroups(groups, nil)
	if err != nil {
		return nil, err
	}

	return resp.Groups, nil
}

func ManifestsNeedingImageLookup(ulms []ULM) []*ULM {
	var _ulms []*ULM
	for i := range ulms {
		if ulms[i].Ec2ImageId == "" {
			_ulms = append(_ulms, &ulms[i])
		}
	}
	return _ulms
}

// use the packer config to locate images on ec2.
func LookupImages(ec2 *awsec2.EC2, name string) ([]awsec2.Image, error) {
	return nil, fmt.Errorf("unimplemented")
}

// User Launch Manifest -- information read from the command line
type ULM struct {
	Name            string   // OTI name that can be used to filter images
	Ec2ImageId      string   // AWS EC2 image id.
	Ec2InstanceType string   // AWS EC2 instance type.
	Ec2KeyName      string   // AWS EC2 key pair name
	Ec2SecGroups    []string // Security groups to assign the instances
	Min, Max        int      // may not be empty
}

type ArgumentError struct {
	i   int
	err error
}

func (err ArgumentError) Error() string {
	return fmt.Sprintf("argument %d: %v", err.i, err.err)
}

var ErrEndOfArgs = ArgumentError{-1, fmt.Errorf("no more arguments")}

// parses a set launch manifest. manifests have the form
//	name [ flag[=val] ... ] -- ...
// for reference use the following list of flags and the default values
//	flag      alias  default
//	min              1
//	max              1
//	ec2type          "t1.micro"
//	ami              ""
//	keyname          ""
//	secgroup         ""
func ParseUserLaunchManifest(args []string) ([]ULM, error) {
	ulms := make([]ULM, 0, len(args))
	sepseq := "--"

	parseUlm := func(args []string) (ulm ULM, rest []string, err error) {
		rest = args

		if len(rest) == 0 {
			return ULM{}, nil, ErrEndOfArgs
		}

		if rest[0] == sepseq {
			err := fmt.Errorf("unexpected separator sequence %v", sepseq)
			return ULM{}, nil, err
		}

		ulm.Name, rest = args[0], rest[1:]

		// set defaults
		ulm.Min, ulm.Max = 1, 1
		ulm.Ec2InstanceType = "t1.micro"

		retErr := func(err error) (ULM, []string, error) {
			return ULM{}, nil, err
		}
		ulmErr := func(err error) error { return fmt.Errorf("%v %v", ulm.Name, err) }
		ulmFlagErr := func(key string, err error) error {
			return ulmErr(fmt.Errorf("invalid flag %q: %v", key, err))
		}

		flags := make(map[string][]string)
		for len(rest) > 0 && rest[0] != sepseq {
			var head string
			head, rest = rest[0], rest[1:]
			key, value := head, ""

			pair := strings.SplitN(key, "=", 2)
			if len(pair) == 2 {
				key, value = pair[0], pair[1]
			}

			switch key {
			case "min", "max", "secgroup", "ami", "keyname", "ec2type":
			default:
				err := fmt.Errorf("unexpected flag %v", key)
				return retErr(ulmErr(err))
			}

			flags[key] = append(flags[key], value)
		}

		for k, vs := range flags {
			var err error
			numvs := len(vs)
			switch k {
			case "min":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Min, err = strconv.Atoi(vs[0])
				}
			case "max":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Max, err = strconv.Atoi(vs[0])
				}
			case "secgroup":
				ulm.Ec2SecGroups = vs
			case "ec2type":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2InstanceType = vs[0]
				}
			case "ami":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2ImageId = vs[0]
				}
			case "keyname":
				if numvs > 1 {
					err = fmt.Errorf("specified multiple times")
				} else {
					ulm.Ec2KeyName = vs[0]
				}
			}
			if err != nil {
				return retErr(ulmFlagErr(k, err))
			}
		}

		if len(ulm.Ec2SecGroups) == 0 {
			return retErr(ulmFlagErr("secgroup", fmt.Errorf("required for now")))
		}

		if ulm.Ec2ImageId == "" {
			return retErr(ulmFlagErr("ami", fmt.Errorf("required for now")))
		}

		if ulm.Min > ulm.Max {
			return retErr(ulmErr(err))
		}

		if len(rest) > 0 && rest[0] == sepseq {
			return ulm, rest[1:], nil
		} else {
			return ulm, nil, nil
		}
	}

	for len(args) > 0 {
		var ulm ULM
		var err error
		ulm, args, err = parseUlm(args)
		if err != nil {
			return nil, err
		}
		ulms = append(ulms, ulm)
	}

	return ulms, nil
}

type LaunchManifest struct {
	Name      string    // configured by the user
	Min, Max  int       // configured by the user
	SessionId SessionId // generated at runtime
	Ec2       struct {
		ImageId        string                 // located AWS image id
		InstanceType   string                 // configured by the user
		KeyName        string                 // configured by the user or generated at run-time
		SecurityGroups []awsec2.SecurityGroup // configured by the user or created at runtime
	}
}

type SessionId string

func NewSessionId(sessiontype string) (SessionId, error) {
	if strings.Contains(sessiontype, ":") {
		return "", fmt.Errorf("session type cannot contain ':'")
	}
	if sessiontype == "" {
		sessiontype = "session"
	}
	sid := SessionId(fmt.Sprintf("%s:%v", sessiontype, uuid.New()))
	return sid, nil
}

func (sid SessionId) Type() string {
	return strings.SplitN(string(sid), ":", 2)[0]
}
<|endoftext|>