<commit_before>package worker

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

	gocontext "context"

	"github.com/bitly/go-simplejson"
	"github.com/jtacoma/uritemplates"
	"github.com/pkg/errors"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/context"
	"github.com/travis-ci/worker/metrics"
)

type httpJob struct {
	payload         *httpJobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time

	jobBoardURL *url.URL
	site        string
	workerID    string
}

type jobScriptPayload struct {
	Name     string `json:"name"`
	Encoding string `json:"encoding"`
	Content  string `json:"content"`
}

type httpJobPayload struct {
	Data        *JobPayload      `json:"data"`
	JobScript   jobScriptPayload `json:"job_script"`
	JobStateURL string           `json:"job_state_url"`
	JobPartsURL string           `json:"log_parts_url"`
	JWT         string           `json:"jwt"`
	ImageName   string           `json:"image_name"`
}

type httpJobStateUpdate struct {
	CurrentState string    `json:"cur"`
	NewState     string    `json:"new"`
	ReceivedAt   time.Time `json:"received,omitempty"`
	StartedAt    time.Time `json:"started,omitempty"`
}

func (j *httpJob) GoString() string {
	return fmt.Sprintf("&httpJob{payload: %#v, startAttributes: %#v}",
		j.payload, j.startAttributes)
}

func (j *httpJob) Payload() *JobPayload {
	return j.payload.Data
}

func (j *httpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *httpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *httpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx, time.Minute)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(ctx, FinishStateErrored)
}

func (j *httpJob) Requeue(ctx gocontext.Context) error {
	context.LoggerFromContext(ctx).Info("requeueing job")

	metrics.Mark("worker.job.requeue")

	currentState := j.currentState()

	j.received = time.Time{}
	j.started = time.Time{}

	err := j.sendStateUpdate(currentState, "created")

	if err != nil {
		return err
	}

	return nil
}

func (j *httpJob) Received() error {
	j.received = time.Now()
	return j.sendStateUpdate("queued", "received")
}

func (j *httpJob) Started() error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate("received", "started")
}

func (j *httpJob) currentState() string {

	currentState := "queued"

	if !j.received.IsZero() {
		currentState = "received"
	}

	if !j.started.IsZero() {
		currentState = "started"
	}

	return currentState
}

func (j *httpJob) Finish(ctx gocontext.Context, state FinishState) error {
	context.LoggerFromContext(ctx).WithField("state", state).Info("finishing job")

	u := *j.jobBoardURL
	u.Path = fmt.Sprintf("/jobs/%d", j.Payload().Job.ID)
	u.User = nil

	req, err := http.NewRequest("DELETE", u.String(), nil)
	if err != nil {
		return err
	}

	req.Header.Add("Travis-Site", j.site)
	req.Header.Add("Authorization", "Bearer "+j.payload.JWT)
	req.Header.Add("From", j.workerID)

	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNoContent {
		var errorResp jobBoardErrorResponse
		err := json.Unmarshal(body, &errorResp)
		if err != nil {
			return errors.Wrapf(err, "job board job delete request errored with status %d and didn't send an error response", resp.StatusCode)
		}

		return errors.Errorf("job board job delete request errored with status %d: %s", resp.StatusCode, errorResp.Error)
	}

	finishedAt := time.Now()
	receivedAt := j.received
	if receivedAt.IsZero() {
		receivedAt = finishedAt
	}

	startedAt := j.started
	if startedAt.IsZero() {
		startedAt = finishedAt
	}

	return j.sendStateUpdate(j.currentState(), string(state))
}

func (j *httpJob) LogWriter(ctx gocontext.Context, defaultLogTimeout time.Duration) (LogWriter, error) {
	logTimeout := time.Duration(j.payload.Data.Timeouts.LogSilence) * time.Second
	if logTimeout == 0 {
		logTimeout = defaultLogTimeout
	}

	return newHTTPLogWriter(ctx, j.payload.JobPartsURL, j.payload.JWT, j.payload.Data.Job.ID, logTimeout)
}

func (j *httpJob) Generate(ctx gocontext.Context, job Job) ([]byte, error) {
	if j.payload.JobScript.Encoding != "base64" {
		return nil, errors.Errorf("unknown job script encoding: %s", j.payload.JobScript.Encoding)
	}

	script, err := base64.StdEncoding.DecodeString(j.payload.JobScript.Content)
	if err != nil {
		return nil, errors.Wrap(err, "couldn't base64 decode job script")
	}

	return script, nil
}

func (j *httpJob) sendStateUpdate(currentState, newState string) error {
	payload := &httpJobStateUpdate{
		CurrentState: currentState,
		NewState:     newState,
		ReceivedAt:   j.received,
		StartedAt:    j.started,
	}

	encodedPayload, err := json.Marshal(payload)
	if err != nil {
		return errors.Wrap(err, "error encoding json")
	}

	template, err := uritemplates.Parse(j.payload.JobStateURL)
	if err != nil {
		return errors.Wrap(err, "couldn't parse base URL template")
	}

	u, err := template.Expand(map[string]interface{}{
		"job_id": j.payload.Data.Job.ID,
	})
	if err != nil {
		return errors.Wrap(err, "couldn't expand base URL template")
	}

	req, err := http.NewRequest("PATCH", u, bytes.NewReader(encodedPayload))
	if err != nil {
		return errors.Wrap(err, "couldn't create request")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", j.payload.JWT))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return errors.Wrap(err, "error making state update request")
	}

	if resp.StatusCode != http.StatusOK {
		return errors.Errorf("expected %d, but got %d", http.StatusOK, resp.StatusCode)
	}

	return nil
}
<commit_msg>Change if err != nil { return err } return nil to return err<commit_after>package worker

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

	gocontext "context"

	"github.com/bitly/go-simplejson"
	"github.com/jtacoma/uritemplates"
	"github.com/pkg/errors"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/context"
	"github.com/travis-ci/worker/metrics"
)

type httpJob struct {
	payload         *httpJobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time

	jobBoardURL *url.URL
	site        string
	workerID    string
}

type jobScriptPayload struct {
	Name     string `json:"name"`
	Encoding string `json:"encoding"`
	Content  string `json:"content"`
}

type httpJobPayload struct {
	Data        *JobPayload      `json:"data"`
	JobScript   jobScriptPayload `json:"job_script"`
	JobStateURL string           `json:"job_state_url"`
	JobPartsURL string           `json:"log_parts_url"`
	JWT         string           `json:"jwt"`
	ImageName   string           `json:"image_name"`
}

type httpJobStateUpdate struct {
	CurrentState string    `json:"cur"`
	NewState     string    `json:"new"`
	ReceivedAt   time.Time `json:"received,omitempty"`
	StartedAt    time.Time `json:"started,omitempty"`
}

func (j *httpJob) GoString() string {
	return fmt.Sprintf("&httpJob{payload: %#v, startAttributes: %#v}",
		j.payload, j.startAttributes)
}

func (j *httpJob) Payload() *JobPayload {
	return j.payload.Data
}

func (j *httpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *httpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *httpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx, time.Minute)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(ctx, FinishStateErrored)
}

func (j *httpJob) Requeue(ctx gocontext.Context) error {
	context.LoggerFromContext(ctx).Info("requeueing job")

	metrics.Mark("worker.job.requeue")

	currentState := j.currentState()

	j.received = time.Time{}
	j.started = time.Time{}

	return j.sendStateUpdate(currentState, "created")
}

func (j *httpJob) Received() error {
	j.received = time.Now()
	return j.sendStateUpdate("queued", "received")
}

func (j *httpJob) Started() error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate("received", "started")
}

func (j *httpJob) currentState() string {

	currentState := "queued"

	if !j.received.IsZero() {
		currentState = "received"
	}

	if !j.started.IsZero() {
		currentState = "started"
	}

	return currentState
}

func (j *httpJob) Finish(ctx gocontext.Context, state FinishState) error {
	context.LoggerFromContext(ctx).WithField("state", state).Info("finishing job")

	u := *j.jobBoardURL
	u.Path = fmt.Sprintf("/jobs/%d", j.Payload().Job.ID)
	u.User = nil

	req, err := http.NewRequest("DELETE", u.String(), nil)
	if err != nil {
		return err
	}

	req.Header.Add("Travis-Site", j.site)
	req.Header.Add("Authorization", "Bearer "+j.payload.JWT)
	req.Header.Add("From", j.workerID)

	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNoContent {
		var errorResp jobBoardErrorResponse
		err := json.Unmarshal(body, &errorResp)
		if err != nil {
			return errors.Wrapf(err, "job board job delete request errored with status %d and didn't send an error response", resp.StatusCode)
		}

		return errors.Errorf("job board job delete request errored with status %d: %s", resp.StatusCode, errorResp.Error)
	}

	finishedAt := time.Now()
	receivedAt := j.received
	if receivedAt.IsZero() {
		receivedAt = finishedAt
	}

	startedAt := j.started
	if startedAt.IsZero() {
		startedAt = finishedAt
	}

	return j.sendStateUpdate(j.currentState(), string(state))
}

func (j *httpJob) LogWriter(ctx gocontext.Context, defaultLogTimeout time.Duration) (LogWriter, error) {
	logTimeout := time.Duration(j.payload.Data.Timeouts.LogSilence) * time.Second
	if logTimeout == 0 {
		logTimeout = defaultLogTimeout
	}

	return newHTTPLogWriter(ctx, j.payload.JobPartsURL, j.payload.JWT, j.payload.Data.Job.ID, logTimeout)
}

func (j *httpJob) Generate(ctx gocontext.Context, job Job) ([]byte, error) {
	if j.payload.JobScript.Encoding != "base64" {
		return nil, errors.Errorf("unknown job script encoding: %s", j.payload.JobScript.Encoding)
	}

	script, err := base64.StdEncoding.DecodeString(j.payload.JobScript.Content)
	if err != nil {
		return nil, errors.Wrap(err, "couldn't base64 decode job script")
	}

	return script, nil
}

func (j *httpJob) sendStateUpdate(currentState, newState string) error {
	payload := &httpJobStateUpdate{
		CurrentState: currentState,
		NewState:     newState,
		ReceivedAt:   j.received,
		StartedAt:    j.started,
	}

	encodedPayload, err := json.Marshal(payload)
	if err != nil {
		return errors.Wrap(err, "error encoding json")
	}

	template, err := uritemplates.Parse(j.payload.JobStateURL)
	if err != nil {
		return errors.Wrap(err, "couldn't parse base URL template")
	}

	u, err := template.Expand(map[string]interface{}{
		"job_id": j.payload.Data.Job.ID,
	})
	if err != nil {
		return errors.Wrap(err, "couldn't expand base URL template")
	}

	req, err := http.NewRequest("PATCH", u, bytes.NewReader(encodedPayload))
	if err != nil {
		return errors.Wrap(err, "couldn't create request")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", j.payload.JWT))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return errors.Wrap(err, "error making state update request")
	}

	if resp.StatusCode != http.StatusOK {
		return errors.Errorf("expected %d, but got %d", http.StatusOK, resp.StatusCode)
	}

	return nil
}
<|endoftext|>


<commit_before>package eventhub

import (
	"testing"
	"time"
)

type DummyFeed struct {
	events chan *Event
}

func (d DummyFeed) Updates() <-chan *Event {
	return d.events
}

func (d DummyFeed) Close() error {
	close(d.events)
	return nil
}

type FakeBroadCaster struct {
	events chan *Event
}

func (f FakeBroadCaster) Broadcast(e *Event) {
	f.events <- e
}

func TestHub(t *testing.T) {

	d := NewLocalMemoryStore()
	h := NewHub("Application", d)

	f1 := DummyFeed{make(chan *Event)}
	f2 := DummyFeed{make(chan *Event)}

	h.AddFeeds(f1, f2)

	b := FakeBroadCaster{make(chan *Event)}
	h.AddBroadcasters(b)

	count := 5
	ticker := time.NewTicker(1 * time.Millisecond)
	quit := make(chan struct{})
	go func() {
		for {
			select {
			case <-ticker.C:
				e := NewEvent(
					"myapp.user.login",
					nil,
					nil,
					"User foobar logged in",
					3,
					"myapp",
					[]string{"ns/foo", "ns/moo"},
					nil,
					nil,
					nil)

				t.Logf("Sending %+v to feed, count: %d", e, count)

				e.Description = "from feed 1"
				f1.events <- e
				e.Description = "from feed 2"
				f2.events <- e
				count--

				if count < 0 {
					close(quit)
				}
			case <-quit:
				t.Logf("Closing ticker")
				ticker.Stop()
				return
			}
		}
	}()

	go h.Run()

	for i := 0; i < 12; i++ {
		t.Logf("Broadcast: %+v", <-b.events)
	}
}
<commit_msg>Added fake data service<commit_after>package eventhub

import (
	"fmt"
	"testing"
	"time"
)

type DummyFeed struct {
	events chan *Event
}

func (d DummyFeed) Updates() <-chan *Event {
	return d.events
}

func (d DummyFeed) Close() error {
	close(d.events)
	return nil
}

type FakeBroadCaster struct {
	events chan *Event
}

func (f FakeBroadCaster) Broadcast(e *Event) {
	f.events <- e
}

type FakeDataService struct {
	d *DataBackend
}

func (f FakeDataService) Run(d *DataBackend, ec chan error) {
	f.d = d
	if f.d == nil {
		ec <- fmt.Errorf("FakeDataService started with nil DataBackend")
	}
}

func TestHub(t *testing.T) {

	d := NewLocalMemoryStore()
	h := NewHub("Application", d)

	f1 := DummyFeed{make(chan *Event)}
	f2 := DummyFeed{make(chan *Event)}

	h.AddFeeds(f1, f2)

	b := FakeBroadCaster{make(chan *Event)}
	h.AddBroadcasters(b)

	fds := FakeDataService{}
	h.AddDataServices(fds)

	count := 5
	ticker := time.NewTicker(1 * time.Millisecond)
	quit := make(chan struct{})
	go func() {
		for {
			select {
			case <-ticker.C:
				e := NewEvent(
					"myapp.user.login",
					nil,
					nil,
					"User foobar logged in",
					3,
					"myapp",
					[]string{"ns/foo", "ns/moo"},
					nil,
					nil,
					nil)

				t.Logf("Sending %+v to feed, count: %d", e, count)

				e.Description = "from feed 1"
				f1.events <- e
				e.Description = "from feed 2"
				f2.events <- e
				count--

				if count < 0 {
					close(quit)
				}
			case <-quit:
				t.Logf("Closing ticker")
				ticker.Stop()
				return
			}
		}
	}()

	go h.Run()

	for i := 0; i < 12; i++ {
		t.Logf("Broadcast: %+v", <-b.events)
	}

}
<|endoftext|>


<commit_before>package create

const eventTmpl = `name: "{{ .Year}}-{{ .City}}" # The name of the event. Four digit year with the city name in lower-case, with no spaces.
year: "{{ .Year}}" # The year of the event. Make sure it is in quotes.
city: "{{ .City}}" # The displayed city name of the event. Capitalize it.
event_twitter: "{{ .Twitter}}" # Change this to the twitter handle for your event such as devopsdayschi or devopsdaysmsp
description: "Devopsdays is coming to {{ .City}}!" # Edit this to suit your preferences
ga_tracking_id: "{{ .GoogleAnalytics}}" # If you have your own Google Analytics tracking ID, enter it here. Example: "UA-74738648-1"

# All dates are in unquoted YYYY-MM-DD, like this:   variable: 2016-01-05
startdate: {{ .StartDate}} # The start date of your event. Leave blank if you don't have a venue reserved yet.
enddate: {{ .StartDate}} # The end date of your event. Leave blank if you don't have a venue reserved yet.

# Leave CFP dates blank if you don't know yet, or set all three at once.
cfp_date_start: {{ .CFPDateStart}} # start accepting talk proposals.
cfp_date_end: {{ .CFPDateEnd}} # close your call for proposals.
cfp_date_announce: {{ .CFPDateAnnounce}} # inform proposers of status

cfp_open: "{{ .CFPOpen}}"
cfp_link: "{{ .CFPLink}}" #if you have a custom link for submitting proposals, add it here. This will control the Propose menu item as well as the "Propose" button.

registration_date_start: {{ .RegistrationDateStart}} # start accepting registration. Leave blank if registration is not open yet
registration_date_end: {{ .RegistrationDateEnd}} # close registration. Leave blank if registration is not open yet.

registration_closed: "{{ .RegistrationClosed }}" # set this to true if you need to manually close registration before your registration end date
registration_link: "{{ .RegistrationLink }}" # If you have a custom registration link, enter it here. This will control the Registration menu item as well as the "Register" button.

masthead_background: "{{ .MastheadBackground }}"

# Location
#
coordinates: "{{ .Coordinates }}" # The coordinates of your city. Get Latitude and Longitude of a Point: http://itouchmap.com/latlong.html
location: "{{ .Location }}" # Defaults to city, but you can make it the venue name.
#
location_address: "{{ .LocationAddress }}" #Optional - use the street address of your venue. This will show up on the welcome page if set.

nav_elements: # List of pages you want to show up in the navigation of your page.
 # - name: propose
 # - name: location
 # - name: registration
 # - name: program
 # - name: speakers
  - name: sponsor
  - name: contact
  - name: conduct
# - name: example
#   icon: "map-o" # This is a font-awesome icon that will display on small screens. Choose at http://fontawesome.io/icons/
#   url: http://mycfp.com # The url setting is optional, and only if you want the navigation to link off-site

# These are the same people you have on the mailing list and Slack channel.
team_members: # Name is the only required field for team members.
  - name: "John Doe"
  - name: "Jane Smith"
    twitter: "devopsdays"
  - name: "Sally Fields"
    employer: "Acme Anvil Co."
    github: "devopsdays"
    facebook: "https://www.facebook.com/sally.fields"
    linkedin: "https://www.linkedin.com/in/sallyfields"
    website: "https://mattstratton.com"
    image: "sally-fields.jpg"
organizer_email: "{{ .OrganizerEmail }}" # Put your organizer email address here
proposal_email: "{{ .ProposalEmail }}" # Put your proposal email address here

# List all of your sponsors here along with what level of sponsorship they have.
# Check data/sponsors/ to use sponsors already added by others.
sponsors:
  - id: samplesponsorname
    level: gold
 #  url: http://mysponsor.com/?campaign=me # Use this if you need to over-ride a sponsor URL.
  - id: arresteddevops
    level: community

sponsors_accepted : "{{ .SponsorsAccepted }}" # Whether you want "Become a XXX Sponsor!" link

# In this section, list the level of sponsorships and the label to use.
# You may optionally include a "max" attribute to limit the number of sponsors per level. For
# unlimited sponsors, omit the max attribute or set it to 0. If you want to prevent all
# sponsorship for a specific level, it is best to remove the level.
sponsor_levels:
  - id: gold
    label: Gold
#    max: 10
  - id: silver
    label: Silver
    max: 0 # This is the same as omitting the max limit.
  - id: bronze
    label: Bronze
  - id: community
    label: Community
    `

const speakerTmpl = `+++
    Title = "{{ .Title }}"
    type = "speaker"
    {{ with .Website }}website = "{{ . }}"{{ end }}
    {{ with .Twitter }}twitter = "{{ . }}"{{ end }}
    {{ with .Facebook }}facebook = "{{ . }}"{{ end }}
    {{ with .Linkedin }}linkedin = "{{ . }}"{{ end }}
    {{ with .Github }}github = "{{ . }}"{{ end }}
    {{ with .Gitlab }}gitlab = "{{ . }}"{{ end }}
    {{ with .ImagePath }}image = "{{ . }}"{{ end }}
    +++
    {{ with .Bio }}{{.}}{{ end }}
    `
<commit_msg>Fix spacing error in speaker template<commit_after>package create

const eventTmpl = `name: "{{ .Year}}-{{ .City}}" # The name of the event. Four digit year with the city name in lower-case, with no spaces.
year: "{{ .Year}}" # The year of the event. Make sure it is in quotes.
city: "{{ .City}}" # The displayed city name of the event. Capitalize it.
event_twitter: "{{ .Twitter}}" # Change this to the twitter handle for your event such as devopsdayschi or devopsdaysmsp
description: "Devopsdays is coming to {{ .City}}!" # Edit this to suit your preferences
ga_tracking_id: "{{ .GoogleAnalytics}}" # If you have your own Google Analytics tracking ID, enter it here. Example: "UA-74738648-1"

# All dates are in unquoted YYYY-MM-DD, like this:   variable: 2016-01-05
startdate: {{ .StartDate}} # The start date of your event. Leave blank if you don't have a venue reserved yet.
enddate: {{ .StartDate}} # The end date of your event. Leave blank if you don't have a venue reserved yet.

# Leave CFP dates blank if you don't know yet, or set all three at once.
cfp_date_start: {{ .CFPDateStart}} # start accepting talk proposals.
cfp_date_end: {{ .CFPDateEnd}} # close your call for proposals.
cfp_date_announce: {{ .CFPDateAnnounce}} # inform proposers of status

cfp_open: "{{ .CFPOpen}}"
cfp_link: "{{ .CFPLink}}" #if you have a custom link for submitting proposals, add it here. This will control the Propose menu item as well as the "Propose" button.

registration_date_start: {{ .RegistrationDateStart}} # start accepting registration. Leave blank if registration is not open yet
registration_date_end: {{ .RegistrationDateEnd}} # close registration. Leave blank if registration is not open yet.

registration_closed: "{{ .RegistrationClosed }}" # set this to true if you need to manually close registration before your registration end date
registration_link: "{{ .RegistrationLink }}" # If you have a custom registration link, enter it here. This will control the Registration menu item as well as the "Register" button.

masthead_background: "{{ .MastheadBackground }}"

# Location
#
coordinates: "{{ .Coordinates }}" # The coordinates of your city. Get Latitude and Longitude of a Point: http://itouchmap.com/latlong.html
location: "{{ .Location }}" # Defaults to city, but you can make it the venue name.
#
location_address: "{{ .LocationAddress }}" #Optional - use the street address of your venue. This will show up on the welcome page if set.

nav_elements: # List of pages you want to show up in the navigation of your page.
 # - name: propose
 # - name: location
 # - name: registration
 # - name: program
 # - name: speakers
  - name: sponsor
  - name: contact
  - name: conduct
# - name: example
#   icon: "map-o" # This is a font-awesome icon that will display on small screens. Choose at http://fontawesome.io/icons/
#   url: http://mycfp.com # The url setting is optional, and only if you want the navigation to link off-site

# These are the same people you have on the mailing list and Slack channel.
team_members: # Name is the only required field for team members.
  - name: "John Doe"
  - name: "Jane Smith"
    twitter: "devopsdays"
  - name: "Sally Fields"
    employer: "Acme Anvil Co."
    github: "devopsdays"
    facebook: "https://www.facebook.com/sally.fields"
    linkedin: "https://www.linkedin.com/in/sallyfields"
    website: "https://mattstratton.com"
    image: "sally-fields.jpg"
organizer_email: "{{ .OrganizerEmail }}" # Put your organizer email address here
proposal_email: "{{ .ProposalEmail }}" # Put your proposal email address here

# List all of your sponsors here along with what level of sponsorship they have.
# Check data/sponsors/ to use sponsors already added by others.
sponsors:
  - id: samplesponsorname
    level: gold
 #  url: http://mysponsor.com/?campaign=me # Use this if you need to over-ride a sponsor URL.
  - id: arresteddevops
    level: community

sponsors_accepted : "{{ .SponsorsAccepted }}" # Whether you want "Become a XXX Sponsor!" link

# In this section, list the level of sponsorships and the label to use.
# You may optionally include a "max" attribute to limit the number of sponsors per level. For
# unlimited sponsors, omit the max attribute or set it to 0. If you want to prevent all
# sponsorship for a specific level, it is best to remove the level.
sponsor_levels:
  - id: gold
    label: Gold
#    max: 10
  - id: silver
    label: Silver
    max: 0 # This is the same as omitting the max limit.
  - id: bronze
    label: Bronze
  - id: community
    label: Community
    `

const speakerTmpl = `+++
Title = "{{ .Title }}"
type = "speaker"
{{ with .Website }}website = "{{ . }}"{{ end }}
{{ with .Twitter }}twitter = "{{ . }}"{{ end }}
{{ with .Facebook }}facebook = "{{ . }}"{{ end }}
{{ with .Linkedin }}linkedin = "{{ . }}"{{ end }}
{{ with .Github }}github = "{{ . }}"{{ end }}
{{ with .Gitlab }}gitlab = "{{ . }}"{{ end }}
{{ with .ImagePath }}image = "{{ . }}"{{ end }}
+++
{{ with .Bio }}{{.}}{{ end }}
`
<|endoftext|>


<commit_before>// Package prerender provides a Prerender.io handler implementation and a
// Negroni middleware.
package prerender

import (
	"compress/gzip"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"

	e "github.com/jqatampa/gadget-arm/errors"
)

// Options provides you with the ability to specify a custom Prerender.io URL
// as well as a Prerender.io Token to include as an X-Prerender-Token header
// to the upstream server.
type Options struct {
	PrerenderURL *url.URL
	Token        string
	BlackList    []regexp.Regexp
	WhiteList    []regexp.Regexp
}

// NewOptions generates a default Options struct pointing to the Prerender.io
// service, obtaining a Token from the environment variable PRERENDER_TOKEN.
// No blacklist/whitelist is created.
func NewOptions() *Options {
	url, _ := url.Parse("https://service.prerender.io/")
	return &Options{
		PrerenderURL: url,
		Token:        os.Getenv("PRERENDER_TOKEN"),
		BlackList:    nil,
		WhiteList:    nil,
	}
}

// Prerender exposes methods to validate and serve content from a Prerender.io
// upstream server.
type Prerender struct {
	Options *Options
}

// NewPrerender generates a new Prerender instance.
func (o *Options) NewPrerender() *Prerender {
	return &Prerender{Options: o}
}

// ServeHTTP allows Prerender to act as a Negroni middleware.
func (p *Prerender) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	fmt.Println("Prerender")
	if p.ShouldPrerender(r) {
		p.PreRenderHandler(rw, r)
	} else if next != nil {
		next(rw, r)
	}
}

// ShouldPrerender analyzes the request to determine whether it should be routed
// to a Prerender.io upstream server.
func (p *Prerender) ShouldPrerender(or *http.Request) bool {
	fmt.Println(or)
	userAgent := strings.ToLower(or.Header.Get("User-Agent"))
	bufferAgent := or.Header.Get("X-Bufferbot")
	isRequestingPrerenderedPage := false
	reqURL := strings.ToLower(or.URL.String())

	// No user agent, don't prerender
	if userAgent == "" {
		return false
	}

	// Not a GET or HEAD request, don't prerender
	if or.Method != "GET" && or.Method != "HEAD" {
		return false
	}

	// Static resource, don't prerender
	for _, extension := range skippedTypes {
		if strings.HasSuffix(reqURL, strings.ToLower(extension)) {
			return false
		}
	}

	// Buffer Agent or requesting an excaped fragment, request prerender
	if bufferAgent != "" || or.URL.Query().Get("_escaped_fragment_") != "" {
		isRequestingPrerenderedPage = true
	}

	// Cralwer, request prerender
	for _, crawlerAgent := range crawlerUserAgents {
		if strings.Contains(crawlerAgent, strings.ToLower(userAgent)) {
			isRequestingPrerenderedPage = true
			break
		}
	}

	// If it's a bot/crawler/escaped fragment request apply Blacklist/Whitelist logic
	if isRequestingPrerenderedPage {
		if p.Options.WhiteList != nil {
			matchFound := false
			for _, val := range p.Options.WhiteList {
				if val.MatchString(reqURL) {
					matchFound = true
					break
				}
			}
			if !matchFound {
				return false
			}
		}

		if p.Options.BlackList != nil {
			matchFound := false
			for _, val := range p.Options.BlackList {
				if val.MatchString(reqURL) {
					matchFound = true
					break
				}
			}
			if matchFound {
				return false
			}
		}
	}

	return isRequestingPrerenderedPage
}

func (p *Prerender) buildURL(or *http.Request) string {
	url := p.Options.PrerenderURL

	if !strings.HasSuffix(url.String(), "/") {
		url.Path = url.Path + "/"
	}

	var protocol = or.URL.Scheme

	if cf := or.Header.Get("CF-Visitor"); cf != "" {
		match := cfSchemeRegex.FindStringSubmatch(cf)
		if len(match) > 1 {
			protocol = match[1]
		}
	}

	if len(protocol) == 0 {
		protocol = "http"
	}

	if fp := or.Header.Get("X-Forwarded-Proto"); fp != "" {
		protocol = strings.Split(fp, ",")[0]
	}

	apiURL := url.String() + protocol + "://" + or.Host + or.URL.Path + "?" +
		or.URL.RawQuery
	return apiURL
}

// PreRenderHandler is a net/http compatible handler that proxies a request to
// the configured Prerender.io URL.  All upstream requests are made with an
// Accept-Encoding=gzip header.  Responses are provided either uncompressed or
// gzip compressed based on the downstream requests Accept-Encoding header
func (p *Prerender) PreRenderHandler(rw http.ResponseWriter, or *http.Request) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", p.buildURL(or), nil)
	e.Check(err)

	if p.Options.Token != "" {
		req.Header.Set("X-Prerender-Token", p.Options.Token)
	}
	req.Header.Set("User-Agent", or.Header.Get("User-Agent"))
	req.Header.Set("Content-Type", or.Header.Get("Content-Type"))
	req.Header.Set("Accept-Encoding", "gzip")

	res, err := client.Do(req)

	fmt.Println(res)
	e.Check(err)

	rw.Header().Set("Content-Type", res.Header.Get("Content-Type"))

	defer res.Body.Close()

	//Figure out whether the client accepts gzip responses
	doGzip := strings.Contains(or.Header.Get("Accept-Encoding"), "gzip")
	isGzip := strings.Contains(res.Header.Get("Content-Encoding"), "gzip")

	if doGzip && !isGzip {
		// gzip raw response
		rw.Header().Set("Content-Encoding", "gzip")
		gz := gzip.NewWriter(rw)
		defer gz.Close()
		io.Copy(gz, res.Body)
		gz.Flush()

	} else if !doGzip && isGzip {
		// gunzip response
		gz, err := gzip.NewReader(res.Body)
		e.Check(err)
		defer gz.Close()
		io.Copy(rw, gz)
	} else {
		// Pass through, gzip/gzip or raw/raw
		rw.Header().Set("Content-Encoding", res.Header.Get("Content-Encoding"))
		io.Copy(rw, res.Body)

	}
}
<commit_msg>adding appengine support<commit_after>// Package prerender provides a Prerender.io handler implementation and a
// Negroni middleware.
package prerender

import (
	"compress/gzip"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"

	e "github.com/jqatampa/gadget-arm/errors"
	"google.golang.org/appengine"
	"google.golang.org/appengine/urlfetch"
)

// Options provides you with the ability to specify a custom Prerender.io URL
// as well as a Prerender.io Token to include as an X-Prerender-Token header
// to the upstream server.
type Options struct {
	PrerenderURL   *url.URL
	Token          string
	BlackList      []regexp.Regexp
	WhiteList      []regexp.Regexp
	UsingAppEngine bool
}

// NewOptions generates a default Options struct pointing to the Prerender.io
// service, obtaining a Token from the environment variable PRERENDER_TOKEN.
// No blacklist/whitelist is created.
func NewOptions() *Options {
	url, _ := url.Parse("https://service.prerender.io/")
	return &Options{
		PrerenderURL: url,
		Token:        os.Getenv("PRERENDER_TOKEN"),
		BlackList:    nil,
		WhiteList:    nil,
		UsingAppEngine: false,
	}
}

// Prerender exposes methods to validate and serve content from a Prerender.io
// upstream server.
type Prerender struct {
	Options *Options
}

// NewPrerender generates a new Prerender instance.
func (o *Options) NewPrerender() *Prerender {
	return &Prerender{Options: o}
}

// ServeHTTP allows Prerender to act as a Negroni middleware.
func (p *Prerender) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	fmt.Println("Prerender")
	if p.ShouldPrerender(r) {
		p.PreRenderHandler(rw, r)
	} else if next != nil {
		next(rw, r)
	}
}

// ShouldPrerender analyzes the request to determine whether it should be routed
// to a Prerender.io upstream server.
func (p *Prerender) ShouldPrerender(or *http.Request) bool {
	fmt.Println(or)
	userAgent := strings.ToLower(or.Header.Get("User-Agent"))
	bufferAgent := or.Header.Get("X-Bufferbot")
	isRequestingPrerenderedPage := false
	reqURL := strings.ToLower(or.URL.String())

	// No user agent, don't prerender
	if userAgent == "" {
		return false
	}

	// Not a GET or HEAD request, don't prerender
	if or.Method != "GET" && or.Method != "HEAD" {
		return false
	}

	// Static resource, don't prerender
	for _, extension := range skippedTypes {
		if strings.HasSuffix(reqURL, strings.ToLower(extension)) {
			return false
		}
	}

	// Buffer Agent or requesting an excaped fragment, request prerender
	if bufferAgent != "" || or.URL.Query().Get("_escaped_fragment_") != "" {
		isRequestingPrerenderedPage = true
	}

	// Cralwer, request prerender
	for _, crawlerAgent := range crawlerUserAgents {
		if strings.Contains(crawlerAgent, strings.ToLower(userAgent)) {
			isRequestingPrerenderedPage = true
			break
		}
	}

	// If it's a bot/crawler/escaped fragment request apply Blacklist/Whitelist logic
	if isRequestingPrerenderedPage {
		if p.Options.WhiteList != nil {
			matchFound := false
			for _, val := range p.Options.WhiteList {
				if val.MatchString(reqURL) {
					matchFound = true
					break
				}
			}
			if !matchFound {
				return false
			}
		}

		if p.Options.BlackList != nil {
			matchFound := false
			for _, val := range p.Options.BlackList {
				if val.MatchString(reqURL) {
					matchFound = true
					break
				}
			}
			if matchFound {
				return false
			}
		}
	}

	return isRequestingPrerenderedPage
}

func (p *Prerender) buildURL(or *http.Request) string {
	url := p.Options.PrerenderURL

	if !strings.HasSuffix(url.String(), "/") {
		url.Path = url.Path + "/"
	}

	var protocol = or.URL.Scheme

	if cf := or.Header.Get("CF-Visitor"); cf != "" {
		match := cfSchemeRegex.FindStringSubmatch(cf)
		if len(match) > 1 {
			protocol = match[1]
		}
	}

	if len(protocol) == 0 {
		protocol = "http"
	}

	if fp := or.Header.Get("X-Forwarded-Proto"); fp != "" {
		protocol = strings.Split(fp, ",")[0]
	}

	apiURL := url.String() + protocol + "://" + or.Host + or.URL.Path + "?" +
		or.URL.RawQuery
	return apiURL
}

// PreRenderHandler is a net/http compatible handler that proxies a request to
// the configured Prerender.io URL.  All upstream requests are made with an
// Accept-Encoding=gzip header.  Responses are provided either uncompressed or
// gzip compressed based on the downstream requests Accept-Encoding header
func (p *Prerender) PreRenderHandler(rw http.ResponseWriter, or *http.Request) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", p.buildURL(or), nil)
	e.Check(err)

	if p.Options.Token != "" {
		req.Header.Set("X-Prerender-Token", p.Options.Token)
	}
	req.Header.Set("User-Agent", or.Header.Get("User-Agent"))
	req.Header.Set("Content-Type", or.Header.Get("Content-Type"))
	req.Header.Set("Accept-Encoding", "gzip")

	if p.Options.UsingAppEngine {
		ctx := appengine.NewContext(or)
		client = urlfetch.Client(ctx)
	}

	res, err := client.Do(req)

	fmt.Println(res)
	e.Check(err)

	rw.Header().Set("Content-Type", res.Header.Get("Content-Type"))

	defer res.Body.Close()

	//Figure out whether the client accepts gzip responses
	doGzip := strings.Contains(or.Header.Get("Accept-Encoding"), "gzip")
	isGzip := strings.Contains(res.Header.Get("Content-Encoding"), "gzip")

	if doGzip && !isGzip {
		// gzip raw response
		rw.Header().Set("Content-Encoding", "gzip")
		gz := gzip.NewWriter(rw)
		defer gz.Close()
		io.Copy(gz, res.Body)
		gz.Flush()

	} else if !doGzip && isGzip {
		// gunzip response
		gz, err := gzip.NewReader(res.Body)
		e.Check(err)
		defer gz.Close()
		io.Copy(rw, gz)
	} else {
		// Pass through, gzip/gzip or raw/raw
		rw.Header().Set("Content-Encoding", res.Header.Get("Content-Encoding"))
		io.Copy(rw, res.Body)

	}
}
<|endoftext|>


<commit_before>/*
Hugocs or Hugo Comment Script is a script that processes POST requests
in a specific format and converts them into JSON files that are saved
to the filesystem. These files can then be processed using Hugo's
readDir and getJSON functions and displayed as comments.

Copyright 2016 Juha Auvinen.

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

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

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

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

type Comment struct {
	Name       string `json:"name"`
	Email      string `json:"email"`
	Website    string `json:"website"`
	GravatarID string `json:"gravatarId"`
	IPAddress  string `json:"ipv4Address"`
	PageID     string `json:"pageId"`
	Body       string `json:"body"`
	Timestamp  string `json:"timestamp"`
}

type Config struct {
	BaseDir     string
	CommentsDir string
	ContentDir  string
	TouchFile   string
}

type Response struct {
	Message string `json:"message"`
	IsError bool   `json:"isError"`
}

var config = Config{
	BaseDir:     ".",
	CommentsDir: "comments",
	ContentDir:  "content",
	TouchFile:   ".comment"}

// Used for testing
func newComment(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("new.html")
	t.Execute(w, "")
}

func saveComment(w http.ResponseWriter, r *http.Request) {
	// TODO: Allow other content-types for javascript-disabled clients.
	// Maybe render a HTML template?
	w.Header().Set("Content-Type", "application/json")
	response := Response{
		Message: "",
		IsError: true}

	if r.Method == "POST" {
		r.ParseForm()
		_, err := validateComment(r, config.ContentDir)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			response = Response{
				Message: err.Error(),
				IsError: true}
		} else {
			now := time.Now()
			comment := Comment{
				Name:       r.Form.Get("name"),
				Email:      r.Form.Get("email"),
				Website:    r.Form.Get("website"),
				GravatarID: getGravatarId(r.Form.Get("email")),
				IPAddress:  getIPAddress(r),
				PageID:     r.Form.Get("page_id"),
				Body:       processBody(r.Form.Get("body")),
				Timestamp:  now.Format(time.RFC3339)}

			// FIXME: Needs error handling
			jsonData, _ := json.Marshal(comment)
			filename := buildFilename(comment.Email, buildTimestamp(now))
			writePath := filepath.Join(config.CommentsDir, comment.PageID)
			writeCommentToDisk(jsonData, writePath, filename)

			//str := fmt.Sprintf( "%#v", r )

			w.WriteHeader(http.StatusOK)
			response = Response{
				Message: "Thank you for the comment",
				IsError: false}
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		response = Response{
			Message: "Must be POST",
			IsError: true}
	}
	// FIXME: Needs error handling
	responseJSON, _ := json.Marshal(response)
	w.Write(responseJSON)
}

// TODO: Check field lengths to prevent too large comment files
// TODO: Error messages should be read from a config file
func validateComment(r *http.Request, contentDir string) (string, error) {
	form := r.Form
	if form.Get("last_name") != "" {
		return "last_name", errors.New("You appear to be a spammer, or your browser auto-fills this form.")
	}
	if regexp.MustCompile(`(?i)[\w\s\d]+`).MatchString(form.Get("name")) != true {
		return "name", errors.New("Name is not valid")
	}
	if regexp.MustCompile(`^[a-z0-9_.\-\+]+@[a-z0-9_.\-\+]+$`).MatchString(form.Get("email")) != true {
		return "email", errors.New("Email address is not valid")
	}
	if form.Get("website") != "" && regexp.MustCompile(`(https?:\/\/)?[a-z0-9\-\.]+`).MatchString(form.Get("website")) != true {
		return "website", errors.New("Website is not valid")
	}
	if regexp.MustCompile(`[a-z0-9\-]+(\/[a-z0-9\-]+)*`).MatchString(form.Get("page_id")) != true {
		return "page_id", errors.New("page_id is not valid")
	}
	if regexp.MustCompile(`[a-z]+`).MatchString(form.Get("content_type")) != true {
		return "content_type", errors.New("Content type is not valid")
	}
	if form.Get("body") == "" {
		return "body", errors.New("You forgot to write the actual comment!")
	}
	if !postExists(form.Get("page_id"), form.Get("content_type"), contentDir) {
		return "page_id", errors.New("Specified post does not exist")
	}
	return "", nil
}

func isValidPath(path string) bool {
	if _, err := os.Stat(path); err == nil {
		return true
	}
	//panic(path)
	return false
}

func postExists(pageID string, extension string, contentDir string) bool {
	return isValidPath(filepath.Join(contentDir, pageID+"."+extension))
}

func processBody(body string) string {
	body = strings.Replace(body, `"""`, "%quote%", -1)
	body = template.HTMLEscapeString(body)
	body = strings.Replace(body, "%quote%", ">", -1)
	return body
}

func getGravatarId(email string) string {
	hash := md5.Sum([]byte(email))
	return hex.EncodeToString(hash[:])
}

func getIPAddress(r *http.Request) string {
	if r.RemoteAddr != "" {
		return r.RemoteAddr
	}
	// FIXME: This should probably look at other fields instead
	return "Unknown"
}

func debugJSON(comment Comment) string {
	str := fmt.Sprintf("%#v", comment)
	return str
}

func buildFilename(email string, timestamp string) string {
	return timestamp + "-" + email + ".json"
}

/*
BuildTimestamp builds a timestamp string to be used as part of the
filename for JSON comment files.
*/
func buildTimestamp(now time.Time) string {
	dateString := fmt.Sprintf("%d-%02d-%02d-%02d%02d%02d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
	return dateString
}

func writeCommentToDisk(JSON []byte, path string, filename string) error {
	dirCreated := false
	// If path doesn't exist, create it
	if !isValidPath(path) {
		os.MkdirAll(path, os.ModePerm)
		dirCreated = true
	}
	fn := filepath.Join(path, filename)
	// Write file to disk
	// FIXME: Needs error handling
	ioutil.WriteFile(fn, JSON, 0600)
	if dirCreated == true && config.TouchFile != "" {
		updateChangeFile(filepath.Join(config.TouchFile))
	}
	return nil
}

func updateChangeFile(touchFile string) {
	// FIXME: Needs error handling
	ioutil.WriteFile(touchFile, []byte("."), 0600)
}

func main() {
	sourceFlag := flag.String("src", ".", "Full path to the sources dir.")
	contentFlag := flag.String("content", "content", "The content directory. Relative to source dir.")
	commentsFlag := flag.String("comments", "comments", "The directory to save comments. Relative to source dir.")
	touchFlag := flag.String("touch", "", "File to update when a new comment directory is created. Relative to source dir. Some watch scripts may require this.")
	addressFlag := flag.String("address", "", "IP address to use for incoming requests. Defaults to any address.")
	portFlag := flag.String("port", "8080", "Port to listen to for incoming requests.")
	pathFlag := flag.String("path", "/comment", "The url path that is used for comment processing.")

	flag.Parse()

	config.BaseDir = *sourceFlag
	config.ContentDir = filepath.Join(*sourceFlag, *contentFlag)
	config.CommentsDir = filepath.Join(*sourceFlag, *commentsFlag)
	if *touchFlag != "" {
		config.TouchFile = filepath.Join(*sourceFlag, *touchFlag)
	} else {
		config.TouchFile = ""
	}

	// FIXME: Add
	serverAddress := *addressFlag + ":" + *portFlag

	http.HandleFunc(*pathFlag, saveComment)
	//http.HandleFunc("/new", newComment );
	http.ListenAndServe(serverAddress, nil)
}
<commit_msg>Add option for avatar type<commit_after>/*
Hugocs or Hugo Comment Script is a script that processes POST requests
in a specific format and converts them into JSON files that are saved
to the filesystem. These files can then be processed using Hugo's
readDir and getJSON functions and displayed as comments.

Copyright 2016 Juha Auvinen.

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

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

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

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

type Comment struct {
	Name       string `json:"name"`
	Email      string `json:"email"`
	EmailMd5   string `json:"emailMd5"`
	Website    string `json:"website"`
	AvatarType string `json:"avatarType"`
	IPAddress  string `json:"ipv4Address"`
	PageID     string `json:"pageId"`
	Body       string `json:"body"`
	Timestamp  string `json:"timestamp"`
}

type Config struct {
	BaseDir     string
	CommentsDir string
	ContentDir  string
	TouchFile   string
}

type Response struct {
	Message string `json:"message"`
	IsError bool   `json:"isError"`
}

var config = Config{
	BaseDir:     ".",
	CommentsDir: "comments",
	ContentDir:  "content",
	TouchFile:   ".comment"}

// Used for testing
func newComment(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("new.html")
	t.Execute(w, "")
}

func saveComment(w http.ResponseWriter, r *http.Request) {
	// TODO: Allow other content-types for javascript-disabled clients.
	// Maybe render a HTML template?
	w.Header().Set("Content-Type", "application/json")
	response := Response{
		Message: "",
		IsError: true}

	if r.Method == "POST" {
		r.ParseForm()
		_, err := validateComment(r, config.ContentDir)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			response = Response{
				Message: err.Error(),
				IsError: true}
		} else {
			now := time.Now()
			comment := Comment{
				Name:       r.Form.Get("name"),
				Email:      r.Form.Get("email"),
				EmailMd5:   getEmailHash(r.Form.Get("email")),
				Website:    r.Form.Get("website"),
				AvatarType: r.Form.Get("avatar_type"),
				IPAddress:  getIPAddress(r),
				PageID:     r.Form.Get("page_id"),
				Body:       processBody(r.Form.Get("body")),
				Timestamp:  now.Format(time.RFC3339)}

			// FIXME: Needs error handling
			jsonData, _ := json.Marshal(comment)
			filename := buildFilename(comment.Email, buildTimestamp(now))
			writePath := filepath.Join(config.CommentsDir, comment.PageID)
			writeCommentToDisk(jsonData, writePath, filename)

			//str := fmt.Sprintf( "%#v", r )

			w.WriteHeader(http.StatusOK)
			response = Response{
				Message: "Thank you for the comment",
				IsError: false}
		}
	} else {
		w.WriteHeader(http.StatusBadRequest)
		response = Response{
			Message: "Must be POST",
			IsError: true}
	}
	// FIXME: Needs error handling
	responseJSON, _ := json.Marshal(response)
	w.Write(responseJSON)
}

// TODO: Check field lengths to prevent too large comment files
// TODO: Error messages should be read from a config file
func validateComment(r *http.Request, contentDir string) (string, error) {
	form := r.Form
	if form.Get("last_name") != "" {
		return "last_name", errors.New("You appear to be a spammer, or your browser auto-fills this form.")
	}
	if regexp.MustCompile(`(?i)[\w\s\d]+`).MatchString(form.Get("name")) != true {
		return "name", errors.New("Name is not valid")
	}
	if regexp.MustCompile(`^[a-z0-9_.\-\+]+@[a-z0-9_.\-\+]+$`).MatchString(form.Get("email")) != true {
		return "email", errors.New("Email address is not valid")
	}
	if form.Get("website") != "" && regexp.MustCompile(`(https?:\/\/)?[a-z0-9\-\.]+`).MatchString(form.Get("website")) != true {
		return "website", errors.New("Website is not valid")
	}
	if regexp.MustCompile(`[a-z]+`).MatchString(form.Get("avatar_type")) != true {
		return "avatar_type", errors.New("Avatar type is not valid")
	}
	if regexp.MustCompile(`[a-z0-9\-]+(\/[a-z0-9\-]+)*`).MatchString(form.Get("page_id")) != true {
		return "page_id", errors.New("page_id is not valid")
	}
	if regexp.MustCompile(`[a-z]+`).MatchString(form.Get("content_type")) != true {
		return "content_type", errors.New("Content type is not valid")
	}
	if form.Get("body") == "" {
		return "body", errors.New("You forgot to write the actual comment!")
	}
	if !postExists(form.Get("page_id"), form.Get("content_type"), contentDir) {
		return "page_id", errors.New("Specified post does not exist")
	}
	return "", nil
}

func isValidPath(path string) bool {
	if _, err := os.Stat(path); err == nil {
		return true
	}
	//panic(path)
	return false
}

func postExists(pageID string, extension string, contentDir string) bool {
	return isValidPath(filepath.Join(contentDir, pageID+"."+extension))
}

func processBody(body string) string {
	body = strings.Replace(body, `"""`, "%quote%", -1)
	body = template.HTMLEscapeString(body)
	body = strings.Replace(body, "%quote%", ">", -1)
	return body
}

func getEmailHash(email string) string {
	hash := md5.Sum([]byte(email))
	return hex.EncodeToString(hash[:])
}

func getIPAddress(r *http.Request) string {
	if r.RemoteAddr != "" {
		return r.RemoteAddr
	}
	// FIXME: This should probably look at other fields instead
	return "Unknown"
}

func debugJSON(comment Comment) string {
	str := fmt.Sprintf("%#v", comment)
	return str
}

func buildFilename(email string, timestamp string) string {
	return timestamp + "-" + email + ".json"
}

/*
BuildTimestamp builds a timestamp string to be used as part of the
filename for JSON comment files.
*/
func buildTimestamp(now time.Time) string {
	dateString := fmt.Sprintf("%d-%02d-%02d-%02d%02d%02d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
	return dateString
}

func writeCommentToDisk(JSON []byte, path string, filename string) error {
	dirCreated := false
	// If path doesn't exist, create it
	if !isValidPath(path) {
		os.MkdirAll(path, os.ModePerm)
		dirCreated = true
	}
	fn := filepath.Join(path, filename)
	// Write file to disk
	// FIXME: Needs error handling
	ioutil.WriteFile(fn, JSON, 0600)
	if dirCreated == true && config.TouchFile != "" {
		updateChangeFile(filepath.Join(config.TouchFile))
	}
	return nil
}

func updateChangeFile(touchFile string) {
	// FIXME: Needs error handling
	ioutil.WriteFile(touchFile, []byte("."), 0600)
}

func main() {
	sourceFlag := flag.String("src", ".", "Full path to the sources dir.")
	contentFlag := flag.String("content", "content", "The content directory. Relative to source dir.")
	commentsFlag := flag.String("comments", "comments", "The directory to save comments. Relative to source dir.")
	touchFlag := flag.String("touch", "", "File to update when a new comment directory is created. Relative to source dir. Some watch scripts may require this.")
	addressFlag := flag.String("address", "", "IP address to use for incoming requests. Defaults to any address.")
	portFlag := flag.String("port", "8080", "Port to listen to for incoming requests.")
	pathFlag := flag.String("path", "/comment", "The url path that is used for comment processing.")

	flag.Parse()

	config.BaseDir = *sourceFlag
	config.ContentDir = filepath.Join(*sourceFlag, *contentFlag)
	config.CommentsDir = filepath.Join(*sourceFlag, *commentsFlag)
	if *touchFlag != "" {
		config.TouchFile = filepath.Join(*sourceFlag, *touchFlag)
	} else {
		config.TouchFile = ""
	}

	// FIXME: Add
	serverAddress := *addressFlag + ":" + *portFlag

	http.HandleFunc(*pathFlag, saveComment)
	//http.HandleFunc("/new", newComment );
	http.ListenAndServe(serverAddress, 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 gcs

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

	"github.com/jacobsa/gcloud/httputil"
	"google.golang.org/api/googleapi"
	storagev1 "google.golang.org/api/storage/v1"

	"golang.org/x/net/context"
)

func (b *bucket) makeComposeObjectsBody(
	req *ComposeObjectsRequest) (rc io.ReadCloser, err error) {
	// Create a request in the form expected by the API.
	r := storagev1.ComposeRequest{
		Destination: &storagev1.Object{
			Name: req.DstName,
		},
	}

	for _, src := range req.Sources {
		s := &storagev1.ComposeRequestSourceObjects{
			Name:       src.Name,
			Generation: src.Generation,
		}

		r.SourceObjects = append(r.SourceObjects, s)
	}

	// Serialize it.
	j, err := json.Marshal(&r)
	if err != nil {
		err = fmt.Errorf("json.Marshal: %v", err)
		return
	}

	// Create a ReadCloser.
	rc = ioutil.NopCloser(bytes.NewReader(j))

	return
}

func (b *bucket) ComposeObjects(
	ctx context.Context,
	req *ComposeObjectsRequest) (o *Object, err error) {
	// Construct an appropriate URL.
	bucketSegment := httputil.EncodePathSegment(b.Name())
	objectSegment := httputil.EncodePathSegment(req.DstName)

	opaque := fmt.Sprintf(
		"//www.googleapis.com/storage/v1/b/%s/o/%s/compose",
		bucketSegment,
		objectSegment)

	query := make(url.Values)
	if req.DstGenerationPrecondition != nil {
		query.Set("ifGenerationMatch", fmt.Sprint(*req.DstGenerationPrecondition))
	}

	url := &url.URL{
		Scheme:   "https",
		Host:     "www.googleapis.com",
		Opaque:   opaque,
		RawQuery: query.Encode(),
	}

	// Set up the request body.
	body, err := b.makeComposeObjectsBody(req)
	if err != nil {
		err = fmt.Errorf("makeComposeObjectsBody: %v", err)
		return
	}

	// Create the HTTP request.
	httpReq, err := httputil.NewRequest(
		"POST",
		url,
		body,
		b.userAgent)

	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set up HTTP request headers.
	httpReq.Header.Set("Content-Type", "application/json")

	// Execute the HTTP request.
	httpRes, err := httputil.Do(ctx, b.client, httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		// Special case: handle precondition errors.
		if typed, ok := err.(*googleapi.Error); ok {
			if typed.Code == http.StatusPreconditionFailed {
				err = &PreconditionError{Err: typed}
			}
		}

		return
	}

	// Parse the response.
	var rawObject *storagev1.Object
	if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil {
		return
	}

	// Convert the response.
	if o, err = toObject(rawObject); err != nil {
		err = fmt.Errorf("toObject: %v", err)
		return
	}

	return
}
<commit_msg>Set a content type, to make GCS happy.<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 gcs

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

	"github.com/jacobsa/gcloud/httputil"
	"google.golang.org/api/googleapi"
	storagev1 "google.golang.org/api/storage/v1"

	"golang.org/x/net/context"
)

func (b *bucket) makeComposeObjectsBody(
	req *ComposeObjectsRequest) (rc io.ReadCloser, err error) {
	// Create a request in the form expected by the API.
	r := storagev1.ComposeRequest{
		Destination: &storagev1.Object{
			Name: req.DstName,

			// We get an HTTP 400 if we don't set this.
			// Cf. Google-internal bug 21588058.
			ContentType: "application/octet-stream",
		},
	}

	for _, src := range req.Sources {
		s := &storagev1.ComposeRequestSourceObjects{
			Name:       src.Name,
			Generation: src.Generation,
		}

		r.SourceObjects = append(r.SourceObjects, s)
	}

	// Serialize it.
	j, err := json.Marshal(&r)
	if err != nil {
		err = fmt.Errorf("json.Marshal: %v", err)
		return
	}

	// Create a ReadCloser.
	rc = ioutil.NopCloser(bytes.NewReader(j))

	return
}

func (b *bucket) ComposeObjects(
	ctx context.Context,
	req *ComposeObjectsRequest) (o *Object, err error) {
	// Construct an appropriate URL.
	bucketSegment := httputil.EncodePathSegment(b.Name())
	objectSegment := httputil.EncodePathSegment(req.DstName)

	opaque := fmt.Sprintf(
		"//www.googleapis.com/storage/v1/b/%s/o/%s/compose",
		bucketSegment,
		objectSegment)

	query := make(url.Values)
	if req.DstGenerationPrecondition != nil {
		query.Set("ifGenerationMatch", fmt.Sprint(*req.DstGenerationPrecondition))
	}

	url := &url.URL{
		Scheme:   "https",
		Host:     "www.googleapis.com",
		Opaque:   opaque,
		RawQuery: query.Encode(),
	}

	// Set up the request body.
	body, err := b.makeComposeObjectsBody(req)
	if err != nil {
		err = fmt.Errorf("makeComposeObjectsBody: %v", err)
		return
	}

	// Create the HTTP request.
	httpReq, err := httputil.NewRequest(
		"POST",
		url,
		body,
		b.userAgent)

	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set up HTTP request headers.
	httpReq.Header.Set("Content-Type", "application/json")

	// Execute the HTTP request.
	httpRes, err := httputil.Do(ctx, b.client, httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		// Special case: handle precondition errors.
		if typed, ok := err.(*googleapi.Error); ok {
			if typed.Code == http.StatusPreconditionFailed {
				err = &PreconditionError{Err: typed}
			}
		}

		return
	}

	// Parse the response.
	var rawObject *storagev1.Object
	if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil {
		return
	}

	// Convert the response.
	if o, err = toObject(rawObject); err != nil {
		err = fmt.Errorf("toObject: %v", err)
		return
	}

	return
}
<|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 gcs

import (
	"fmt"
	"io"

	"github.com/jacobsa/gcloud/reqtrace"

	"golang.org/x/net/context"
)

// A bucket that uses reqtrace.Trace to annotate calls.
type reqtraceBucket struct {
	Wrapped Bucket
}

////////////////////////////////////////////////////////////////////////
// Bucket interface
////////////////////////////////////////////////////////////////////////

func (b *reqtraceBucket) Name() string {
	return b.Wrapped.Name()
}

func (b *reqtraceBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// TODO(jacobsa): Do something useful for this method. Probably a bespoke
	// ReadCloser whose close method reports any errors seen while reading. What
	// to do if it's never closed? Maybe watch ctx.Done()? That's still not
	// guaranteed to be non-nil. I guess we could just fail to trace in that
	// case.
	rc, err = b.Wrapped.NewReader(ctx, req)
	return
}

func (b *reqtraceBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("CreateObject: %s", sanitizeObjectName(req.Name))
	defer traceWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.CreateObject(ctx, req)
	return
}

func (b *reqtraceBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("StatObject: %s", sanitizeObjectName(req.Name))
	defer traceWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.StatObject(ctx, req)
	return
}

func (b *reqtraceBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	desc := fmt.Sprintf("ListObjects")
	defer traceWithError(&ctx, &err, desc)()

	listing, err = b.Wrapped.ListObjects(ctx, req)
	return
}

func (b *reqtraceBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("UpdateObject: %s", sanitizeObjectName(req.Name))
	defer traceWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.UpdateObject(ctx, req)
	return
}

func (b *reqtraceBucket) DeleteObject(
	ctx context.Context,
	name string) (err error) {
	desc := fmt.Sprintf("DeleteObject: %s", sanitizeObjectName(name))
	defer traceWithError(&ctx, &err, desc)()

	err = b.Wrapped.DeleteObject(ctx, name)
	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func sanitizeObjectName(
	name string) (sanitized string) {
	sanitized = fmt.Sprintf("%q", name)
	return
}

func traceWithError(
	ctx *context.Context,
	err *error,
	desc string) (f func()) {
	var report reqtrace.ReportFunc
	*ctx, report = reqtrace.Trace(*ctx, desc)
	f = func() { report(*err) }
	return
}
<commit_msg>reqtraceBucket shouldn't unilaterally start traces.<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 gcs

import (
	"fmt"
	"io"

	"github.com/jacobsa/gcloud/reqtrace"

	"golang.org/x/net/context"
)

// A bucket that uses reqtrace.Trace to annotate calls.
type reqtraceBucket struct {
	Wrapped Bucket
}

////////////////////////////////////////////////////////////////////////
// Bucket interface
////////////////////////////////////////////////////////////////////////

func (b *reqtraceBucket) Name() string {
	return b.Wrapped.Name()
}

func (b *reqtraceBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// TODO(jacobsa): Do something useful for this method. Probably a bespoke
	// ReadCloser whose close method reports any errors seen while reading. What
	// to do if it's never closed? Maybe watch ctx.Done()? That's still not
	// guaranteed to be non-nil. I guess we could just fail to trace in that
	// case.
	rc, err = b.Wrapped.NewReader(ctx, req)
	return
}

func (b *reqtraceBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("CreateObject: %s", sanitizeObjectName(req.Name))
	defer reqtrace.StartSpanWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.CreateObject(ctx, req)
	return
}

func (b *reqtraceBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("StatObject: %s", sanitizeObjectName(req.Name))
	defer reqtrace.StartSpanWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.StatObject(ctx, req)
	return
}

func (b *reqtraceBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	desc := fmt.Sprintf("ListObjects")
	defer reqtrace.StartSpanWithError(&ctx, &err, desc)()

	listing, err = b.Wrapped.ListObjects(ctx, req)
	return
}

func (b *reqtraceBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	desc := fmt.Sprintf("UpdateObject: %s", sanitizeObjectName(req.Name))
	defer reqtrace.StartSpanWithError(&ctx, &err, desc)()

	o, err = b.Wrapped.UpdateObject(ctx, req)
	return
}

func (b *reqtraceBucket) DeleteObject(
	ctx context.Context,
	name string) (err error) {
	desc := fmt.Sprintf("DeleteObject: %s", sanitizeObjectName(name))
	defer reqtrace.StartSpanWithError(&ctx, &err, desc)()

	err = b.Wrapped.DeleteObject(ctx, name)
	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func sanitizeObjectName(
	name string) (sanitized string) {
	sanitized = fmt.Sprintf("%q", name)
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Holt and The Caddy Authors
//
// 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 fileserver

import (
	"bytes"
	_ "embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"text/template"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
	"go.uber.org/zap"
)

//go:embed browse.html
var defaultBrowseTemplate string

// Browse configures directory browsing.
type Browse struct {
	// Use this template file instead of the default browse template.
	TemplateFile string `json:"template_file,omitempty"`
}

func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	fsrv.logger.Debug("browse enabled; listing directory contents",
		zap.String("path", dirPath),
		zap.String("root", root))

	// Navigation on the client-side gets messed up if the
	// URL doesn't end in a trailing slash because hrefs to
	// "b/c" at path "/a" end up going to "/b/c" instead
	// of "/a/b/c" - so we have to redirect in this case
	// so that the path is "/a/" and the client constructs
	// relative hrefs "b/c" to be "/a/b/c".
	//
	// Only redirect if the last element of the path (the filename) was not
	// rewritten; if the admin wanted to rewrite to the canonical path, they
	// would have, and we have to be very careful not to introduce unwanted
	// redirects and especially redirect loops! (Redirecting using the
	// original URI is necessary because that's the URI the browser knows,
	// we don't want to redirect from internally-rewritten URIs.)
	// See https://github.com/caddyserver/caddy/issues/4205.
	// We also redirect if the path is empty, because this implies the path
	// prefix was fully stripped away by a `handle_path` handler for example.
	// See https://github.com/caddyserver/caddy/issues/4466.
	origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
	if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
		if !strings.HasSuffix(origReq.URL.Path, "/") {
			fsrv.logger.Debug("redirecting to trailing slash to preserve hrefs", zap.String("request_path", r.URL.Path))
			return redirect(w, r, origReq.URL.Path+"/")
		}
	}

	dir, err := fsrv.openFile(dirPath, w)
	if err != nil {
		return err
	}
	defer dir.Close()

	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	// calling path.Clean here prevents weird breadcrumbs when URL paths are sketchy like /%2e%2e%2f
	listing, err := fsrv.loadDirectoryContents(dir.(fs.ReadDirFile), root, path.Clean(r.URL.Path), repl)
	switch {
	case os.IsPermission(err):
		return caddyhttp.Error(http.StatusForbidden, err)
	case os.IsNotExist(err):
		return fsrv.notFound(w, r, next)
	case err != nil:
		return caddyhttp.Error(http.StatusInternalServerError, err)
	}

	fsrv.browseApplyQueryParams(w, r, &listing)

	buf := bufPool.Get().(*bytes.Buffer)
	defer bufPool.Put(buf)

	acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))

	// write response as either JSON or HTML
	if strings.Contains(acceptHeader, "application/json") {
		if err := json.NewEncoder(buf).Encode(listing.Items); err != nil {
			return caddyhttp.Error(http.StatusInternalServerError, err)
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	} else {
		var fs http.FileSystem
		if fsrv.Root != "" {
			fs = http.Dir(repl.ReplaceAll(fsrv.Root, "."))
		}

		var tplCtx = &templateContext{
			TemplateContext: templates.TemplateContext{
				Root:       fs,
				Req:        r,
				RespHeader: templates.WrappedHeader{Header: w.Header()},
			},
			browseTemplateContext: listing,
		}

		tpl, err := fsrv.makeBrowseTemplate(tplCtx)
		if err != nil {
			return fmt.Errorf("parsing browse template: %v", err)
		}
		if err := tpl.Execute(buf, tplCtx); err != nil {
			return caddyhttp.Error(http.StatusInternalServerError, err)
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
	}

	_, _ = buf.WriteTo(w)

	return nil
}

func (fsrv *FileServer) loadDirectoryContents(dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (browseTemplateContext, error) {
	files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable
	if err != nil {
		return browseTemplateContext{}, err
	}

	// user can presumably browse "up" to parent folder if path is longer than "/"
	canGoUp := len(urlPath) > 1

	return fsrv.directoryListing(files, canGoUp, root, urlPath, repl), nil
}

// browseApplyQueryParams applies query parameters to the listing.
// It mutates the listing and may set cookies.
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
	sortParam := r.URL.Query().Get("sort")
	orderParam := r.URL.Query().Get("order")
	limitParam := r.URL.Query().Get("limit")
	offsetParam := r.URL.Query().Get("offset")

	// first figure out what to sort by
	switch sortParam {
	case "":
		sortParam = sortByNameDirFirst
		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
			sortParam = sortCookie.Value
		}
	case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
		http.SetCookie(w, &http.Cookie{Name: "sort", Value: sortParam, Secure: r.TLS != nil})
	}

	// then figure out the order
	switch orderParam {
	case "":
		orderParam = "asc"
		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
			orderParam = orderCookie.Value
		}
	case "asc", "desc":
		http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
	}

	// finally, apply the sorting and limiting
	listing.applySortAndLimit(sortParam, orderParam, limitParam, offsetParam)
}

// makeBrowseTemplate creates the template to be used for directory listings.
func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*template.Template, error) {
	var tpl *template.Template
	var err error

	if fsrv.Browse.TemplateFile != "" {
		tpl = tplCtx.NewTemplate(path.Base(fsrv.Browse.TemplateFile))
		tpl, err = tpl.ParseFiles(fsrv.Browse.TemplateFile)
		if err != nil {
			return nil, fmt.Errorf("parsing browse template file: %v", err)
		}
	} else {
		tpl = tplCtx.NewTemplate("default_listing")
		tpl, err = tpl.Parse(defaultBrowseTemplate)
		if err != nil {
			return nil, fmt.Errorf("parsing default browse template: %v", err)
		}
	}

	return tpl, nil
}

// isSymlinkTargetDir returns true if f's symbolic link target
// is a directory.
func (fsrv *FileServer) isSymlinkTargetDir(f fs.FileInfo, root, urlPath string) bool {
	if !isSymlink(f) {
		return false
	}
	target := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, f.Name()))
	targetInfo, err := fsrv.fileSystem.Stat(target)
	if err != nil {
		return false
	}
	return targetInfo.IsDir()
}

// isSymlink return true if f is a symbolic link.
func isSymlink(f fs.FileInfo) bool {
	return f.Mode()&os.ModeSymlink != 0
}

// templateContext powers the context used when evaluating the browse template.
// It combines browse-specific features with the standard templates handler
// features.
type templateContext struct {
	templates.TemplateContext
	browseTemplateContext
}

// bufPool is used to increase the efficiency of file listings.
var bufPool = sync.Pool{
	New: func() any {
		return new(bytes.Buffer)
	},
}
<commit_msg>fileserver: reset buffer before using it (#4962) (#4963)<commit_after>// Copyright 2015 Matthew Holt and The Caddy Authors
//
// 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 fileserver

import (
	"bytes"
	_ "embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"text/template"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
	"go.uber.org/zap"
)

//go:embed browse.html
var defaultBrowseTemplate string

// Browse configures directory browsing.
type Browse struct {
	// Use this template file instead of the default browse template.
	TemplateFile string `json:"template_file,omitempty"`
}

func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	fsrv.logger.Debug("browse enabled; listing directory contents",
		zap.String("path", dirPath),
		zap.String("root", root))

	// Navigation on the client-side gets messed up if the
	// URL doesn't end in a trailing slash because hrefs to
	// "b/c" at path "/a" end up going to "/b/c" instead
	// of "/a/b/c" - so we have to redirect in this case
	// so that the path is "/a/" and the client constructs
	// relative hrefs "b/c" to be "/a/b/c".
	//
	// Only redirect if the last element of the path (the filename) was not
	// rewritten; if the admin wanted to rewrite to the canonical path, they
	// would have, and we have to be very careful not to introduce unwanted
	// redirects and especially redirect loops! (Redirecting using the
	// original URI is necessary because that's the URI the browser knows,
	// we don't want to redirect from internally-rewritten URIs.)
	// See https://github.com/caddyserver/caddy/issues/4205.
	// We also redirect if the path is empty, because this implies the path
	// prefix was fully stripped away by a `handle_path` handler for example.
	// See https://github.com/caddyserver/caddy/issues/4466.
	origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
	if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
		if !strings.HasSuffix(origReq.URL.Path, "/") {
			fsrv.logger.Debug("redirecting to trailing slash to preserve hrefs", zap.String("request_path", r.URL.Path))
			return redirect(w, r, origReq.URL.Path+"/")
		}
	}

	dir, err := fsrv.openFile(dirPath, w)
	if err != nil {
		return err
	}
	defer dir.Close()

	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	// calling path.Clean here prevents weird breadcrumbs when URL paths are sketchy like /%2e%2e%2f
	listing, err := fsrv.loadDirectoryContents(dir.(fs.ReadDirFile), root, path.Clean(r.URL.Path), repl)
	switch {
	case os.IsPermission(err):
		return caddyhttp.Error(http.StatusForbidden, err)
	case os.IsNotExist(err):
		return fsrv.notFound(w, r, next)
	case err != nil:
		return caddyhttp.Error(http.StatusInternalServerError, err)
	}

	fsrv.browseApplyQueryParams(w, r, &listing)

	buf := bufPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer bufPool.Put(buf)

	acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))

	// write response as either JSON or HTML
	if strings.Contains(acceptHeader, "application/json") {
		if err := json.NewEncoder(buf).Encode(listing.Items); err != nil {
			return caddyhttp.Error(http.StatusInternalServerError, err)
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	} else {
		var fs http.FileSystem
		if fsrv.Root != "" {
			fs = http.Dir(repl.ReplaceAll(fsrv.Root, "."))
		}

		var tplCtx = &templateContext{
			TemplateContext: templates.TemplateContext{
				Root:       fs,
				Req:        r,
				RespHeader: templates.WrappedHeader{Header: w.Header()},
			},
			browseTemplateContext: listing,
		}

		tpl, err := fsrv.makeBrowseTemplate(tplCtx)
		if err != nil {
			return fmt.Errorf("parsing browse template: %v", err)
		}
		if err := tpl.Execute(buf, tplCtx); err != nil {
			return caddyhttp.Error(http.StatusInternalServerError, err)
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
	}

	_, _ = buf.WriteTo(w)

	return nil
}

func (fsrv *FileServer) loadDirectoryContents(dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (browseTemplateContext, error) {
	files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable
	if err != nil {
		return browseTemplateContext{}, err
	}

	// user can presumably browse "up" to parent folder if path is longer than "/"
	canGoUp := len(urlPath) > 1

	return fsrv.directoryListing(files, canGoUp, root, urlPath, repl), nil
}

// browseApplyQueryParams applies query parameters to the listing.
// It mutates the listing and may set cookies.
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
	sortParam := r.URL.Query().Get("sort")
	orderParam := r.URL.Query().Get("order")
	limitParam := r.URL.Query().Get("limit")
	offsetParam := r.URL.Query().Get("offset")

	// first figure out what to sort by
	switch sortParam {
	case "":
		sortParam = sortByNameDirFirst
		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
			sortParam = sortCookie.Value
		}
	case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
		http.SetCookie(w, &http.Cookie{Name: "sort", Value: sortParam, Secure: r.TLS != nil})
	}

	// then figure out the order
	switch orderParam {
	case "":
		orderParam = "asc"
		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
			orderParam = orderCookie.Value
		}
	case "asc", "desc":
		http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
	}

	// finally, apply the sorting and limiting
	listing.applySortAndLimit(sortParam, orderParam, limitParam, offsetParam)
}

// makeBrowseTemplate creates the template to be used for directory listings.
func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*template.Template, error) {
	var tpl *template.Template
	var err error

	if fsrv.Browse.TemplateFile != "" {
		tpl = tplCtx.NewTemplate(path.Base(fsrv.Browse.TemplateFile))
		tpl, err = tpl.ParseFiles(fsrv.Browse.TemplateFile)
		if err != nil {
			return nil, fmt.Errorf("parsing browse template file: %v", err)
		}
	} else {
		tpl = tplCtx.NewTemplate("default_listing")
		tpl, err = tpl.Parse(defaultBrowseTemplate)
		if err != nil {
			return nil, fmt.Errorf("parsing default browse template: %v", err)
		}
	}

	return tpl, nil
}

// isSymlinkTargetDir returns true if f's symbolic link target
// is a directory.
func (fsrv *FileServer) isSymlinkTargetDir(f fs.FileInfo, root, urlPath string) bool {
	if !isSymlink(f) {
		return false
	}
	target := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, f.Name()))
	targetInfo, err := fsrv.fileSystem.Stat(target)
	if err != nil {
		return false
	}
	return targetInfo.IsDir()
}

// isSymlink return true if f is a symbolic link.
func isSymlink(f fs.FileInfo) bool {
	return f.Mode()&os.ModeSymlink != 0
}

// templateContext powers the context used when evaluating the browse template.
// It combines browse-specific features with the standard templates handler
// features.
type templateContext struct {
	templates.TemplateContext
	browseTemplateContext
}

// bufPool is used to increase the efficiency of file listings.
var bufPool = sync.Pool{
	New: func() any {
		return new(bytes.Buffer)
	},
}
<|endoftext|>


<commit_before>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE file.

package gohg

import (
	"fmt"
)

type identifyOpts struct {
	Cwd
	Hidden
	NonInteractive
	Quiet
	Repository
	Verbose

	Insecure
	// Mq
	RemoteCmd
	Rev
	Bookmarks
	Branch
	Id
	Num
	Tags
	Ssh

	Debug
	Profile
	Time
	Traceback
}

func (cmd *identifyOpts) String() string {
	return fmt.Sprintf(
		"identifyOpts = {\n    bookmarks: (%T) %t\n    branch: (%T) %t\n    id: (%T) %t\n"+
			// "    mq: (%T) %t\n    num: (%T) %t\n    rev: (%T) %t\n    tags: (%T) %t\n"+
			"    num: (%T) %t\n    rev: (%T) %t\n    tags: (%T) %t\n"+
			"    debug: (%T) %t\n    traceback: (%T) %t\n    profile: (%T) %t\n}\n",
		cmd.Bookmarks, cmd.Bookmarks, cmd.Branch, cmd.Branch,
		cmd.Id, cmd.Id,
		// cmd.Mq, cmd.Mq,
		cmd.Num, cmd.Num,
		cmd.Rev, cmd.Rev, cmd.Tags, cmd.Tags,
		cmd.Debug, cmd.Debug, cmd.Traceback, cmd.Traceback, cmd.Profile, cmd.Profile)
}

// Identify provides the 'hg identify' command.
func (hgcl *HgClient) Identify(source string, opts ...optionAdder) ([]byte, error) {
	cmdOpts := new(identifyOpts)
	// apply gohg defaults (that differ from type default)
	cmdOpts.Bookmarks = true
	cmdOpts.Branch = true
	cmdOpts.Id = true
	cmdOpts.Num = true
	cmdOpts.Tags = true
	params := []string{source}
	hgcmd, err := hgcl.buildCommand("identify", cmdOpts, opts, params)
	if err != nil {
		return nil, err
	}
	return hgcl.runcommand(hgcmd)
}
<commit_msg>identify: removed unnecessary option defaults<commit_after>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE file.

package gohg

import (
	"fmt"
)

type identifyOpts struct {
	Cwd
	Hidden
	NonInteractive
	Quiet
	Repository
	Verbose

	Insecure
	// Mq
	RemoteCmd
	Rev
	Bookmarks
	Branch
	Id
	Num
	Tags
	Ssh

	Debug
	Profile
	Time
	Traceback
}

func (cmd *identifyOpts) String() string {
	return fmt.Sprintf(
		"identifyOpts = {\n    bookmarks: (%T) %t\n    branch: (%T) %t\n    id: (%T) %t\n"+
			// "    mq: (%T) %t\n    num: (%T) %t\n    rev: (%T) %t\n    tags: (%T) %t\n"+
			"    num: (%T) %t\n    rev: (%T) %t\n    tags: (%T) %t\n"+
			"    debug: (%T) %t\n    traceback: (%T) %t\n    profile: (%T) %t\n}\n",
		cmd.Bookmarks, cmd.Bookmarks, cmd.Branch, cmd.Branch,
		cmd.Id, cmd.Id,
		// cmd.Mq, cmd.Mq,
		cmd.Num, cmd.Num,
		cmd.Rev, cmd.Rev, cmd.Tags, cmd.Tags,
		cmd.Debug, cmd.Debug, cmd.Traceback, cmd.Traceback, cmd.Profile, cmd.Profile)
}

// Identify provides the 'hg identify' command.
func (hgcl *HgClient) Identify(source string, opts ...optionAdder) ([]byte, error) {
	hgcmd, err := hgcl.buildCommand("identify", new(identifyOpts), opts, []string{source})
	if err != nil {
		return nil, err
	}
	return hgcl.runcommand(hgcmd)
}
<|endoftext|>


<commit_before>package empire

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"fmt"

	"github.com/jinzhu/gorm"
	"github.com/lib/pq/hstore"
	. "github.com/remind101/empire/pkg/bytesize"
	"github.com/remind101/empire/pkg/constraints"
	"github.com/remind101/empire/procfile"
)

var (
	Constraints1X = Constraints{constraints.CPUShare(256), constraints.Memory(512 * MB), constraints.Nproc(256)}
	Constraints2X = Constraints{constraints.CPUShare(512), constraints.Memory(1 * GB), constraints.Nproc(512)}
	ConstraintsPX = Constraints{constraints.CPUShare(1024), constraints.Memory(6 * GB), 0}

	// NamedConstraints maps a heroku dynos size to a Constraints.
	NamedConstraints = map[string]Constraints{
		"1X": Constraints1X,
		"2X": Constraints2X,
		"PX": ConstraintsPX,
	}

	// DefaultConstraints defaults to 1X process size.
	DefaultConstraints = Constraints1X
)

// ProcessQuantityMap represents a map of process types to quantities.
type ProcessQuantityMap map[ProcessType]int

// DefaultQuantities maps a process type to the default number of instances to
// run.
var DefaultQuantities = ProcessQuantityMap{
	"web": 1,
}

// ProcessType represents the type of a given process/command.
type ProcessType string

// Scan implements the sql.Scanner interface.
func (p *ProcessType) Scan(src interface{}) error {
	if src, ok := src.([]byte); ok {
		*p = ProcessType(src)
	}

	return nil
}

// Value implements the driver.Value interface.
func (p ProcessType) Value() (driver.Value, error) {
	return driver.Value(string(p)), nil
}

// Command represents the actual shell command that gets executed for a given
// ProcessType.
type Command string

// Scan implements the sql.Scanner interface.
func (c *Command) Scan(src interface{}) error {
	if src, ok := src.([]byte); ok {
		*c = Command(src)
	}

	return nil
}

// Value implements the driver.Value interface.
func (c Command) Value() (driver.Value, error) {
	return driver.Value(string(c)), nil
}

// Process holds configuration information about a Process Type.
type Process struct {
	ReleaseID string
	ID        string
	Type      ProcessType
	Quantity  int
	Command   Command
	Port      int `sql:"-"`
	Constraints
}

// NewProcess returns a new Process instance.
func NewProcess(t ProcessType, cmd Command) *Process {
	return &Process{
		Type:        t,
		Quantity:    DefaultQuantities[t],
		Command:     cmd,
		Constraints: DefaultConstraints,
	}
}

// CommandMap maps a process ProcessType to a Command.
type CommandMap map[ProcessType]Command

func commandMapFromProcfile(p procfile.Procfile) CommandMap {
	cm := make(CommandMap)
	for n, c := range p {
		cm[ProcessType(n)] = Command(c)
	}
	return cm
}

// Scan implements the sql.Scanner interface.
func (cm *CommandMap) Scan(src interface{}) error {
	h := hstore.Hstore{}
	if err := h.Scan(src); err != nil {
		return err
	}

	m := make(CommandMap)

	for k, v := range h.Map {
		m[ProcessType(k)] = Command(v.String)
	}

	*cm = m

	return nil
}

// Value implements the driver.Value interface.
func (cm CommandMap) Value() (driver.Value, error) {
	m := make(map[string]sql.NullString)

	for k, v := range cm {
		m[string(k)] = sql.NullString{
			Valid:  true,
			String: string(v),
		}
	}

	h := hstore.Hstore{
		Map: m,
	}

	return h.Value()
}

// Constraints aliases constraints.Constraints to implement the
// sql.Scanner interface.
type Constraints constraints.Constraints

func parseConstraints(con string) (*Constraints, error) {
	if con == "" {
		return nil, nil
	}

	if n, ok := NamedConstraints[con]; ok {
		return &n, nil
	}

	c, err := constraints.Parse(con)
	if err != nil {
		return nil, err
	}

	r := Constraints(c)
	return &r, nil
}

func (c *Constraints) UnmarshalJSON(b []byte) error {
	var s string

	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}

	cc, err := parseConstraints(s)
	if err != nil {
		return err
	}

	if cc != nil {
		*c = *cc
	}

	return nil
}

func (c Constraints) String() string {
	for n, constraint := range NamedConstraints {
		if c == constraint {
			return n
		}
	}

	if c.Nproc == 0 {
		return fmt.Sprintf("%d:%s", c.CPUShare, c.Memory)
	} else {
		return fmt.Sprintf("%d:%s:nproc=%d", c.CPUShare, c.Memory, c.Nproc)
	}
}

// Formation maps a process ProcessType to a Process.
type Formation map[ProcessType]*Process

// NewFormation creates a new Formation based on an existing Formation and
// the available processes from a CommandMap.
func NewFormation(f Formation, cm CommandMap) Formation {
	processes := make(Formation)

	// Iterate through all of the available process types in the CommandMap.
	for t, cmd := range cm {
		p := NewProcess(t, cmd)

		if existing, found := f[t]; found {
			// If the existing Formation already had a process
			// configuration for this process type, copy over the
			// instance count.
			p.Quantity = existing.Quantity
			p.Constraints = existing.Constraints
		}

		processes[t] = p
	}

	return processes
}

// newFormation takes a slice of processes and returns a Formation.
func newFormation(p []*Process) Formation {
	f := make(Formation)

	for _, pp := range p {
		f[pp.Type] = pp
	}

	return f
}

// Processes takes a Formation and returns a slice of the processes.
func (f Formation) Processes() []*Process {
	var processes []*Process

	for _, p := range f {
		processes = append(processes, p)
	}

	return processes
}

// processesUpdate updates an existing process into the database.
func processesUpdate(db *gorm.DB, process *Process) error {
	return db.Save(process).Error
}
<commit_msg>Remove unused port field.<commit_after>package empire

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"fmt"

	"github.com/jinzhu/gorm"
	"github.com/lib/pq/hstore"
	. "github.com/remind101/empire/pkg/bytesize"
	"github.com/remind101/empire/pkg/constraints"
	"github.com/remind101/empire/procfile"
)

var (
	Constraints1X = Constraints{constraints.CPUShare(256), constraints.Memory(512 * MB), constraints.Nproc(256)}
	Constraints2X = Constraints{constraints.CPUShare(512), constraints.Memory(1 * GB), constraints.Nproc(512)}
	ConstraintsPX = Constraints{constraints.CPUShare(1024), constraints.Memory(6 * GB), 0}

	// NamedConstraints maps a heroku dynos size to a Constraints.
	NamedConstraints = map[string]Constraints{
		"1X": Constraints1X,
		"2X": Constraints2X,
		"PX": ConstraintsPX,
	}

	// DefaultConstraints defaults to 1X process size.
	DefaultConstraints = Constraints1X
)

// ProcessQuantityMap represents a map of process types to quantities.
type ProcessQuantityMap map[ProcessType]int

// DefaultQuantities maps a process type to the default number of instances to
// run.
var DefaultQuantities = ProcessQuantityMap{
	"web": 1,
}

// ProcessType represents the type of a given process/command.
type ProcessType string

// Scan implements the sql.Scanner interface.
func (p *ProcessType) Scan(src interface{}) error {
	if src, ok := src.([]byte); ok {
		*p = ProcessType(src)
	}

	return nil
}

// Value implements the driver.Value interface.
func (p ProcessType) Value() (driver.Value, error) {
	return driver.Value(string(p)), nil
}

// Command represents the actual shell command that gets executed for a given
// ProcessType.
type Command string

// Scan implements the sql.Scanner interface.
func (c *Command) Scan(src interface{}) error {
	if src, ok := src.([]byte); ok {
		*c = Command(src)
	}

	return nil
}

// Value implements the driver.Value interface.
func (c Command) Value() (driver.Value, error) {
	return driver.Value(string(c)), nil
}

// Process holds configuration information about a Process Type.
type Process struct {
	ReleaseID string
	ID        string
	Type      ProcessType
	Quantity  int
	Command   Command
	Constraints
}

// NewProcess returns a new Process instance.
func NewProcess(t ProcessType, cmd Command) *Process {
	return &Process{
		Type:        t,
		Quantity:    DefaultQuantities[t],
		Command:     cmd,
		Constraints: DefaultConstraints,
	}
}

// CommandMap maps a process ProcessType to a Command.
type CommandMap map[ProcessType]Command

func commandMapFromProcfile(p procfile.Procfile) CommandMap {
	cm := make(CommandMap)
	for n, c := range p {
		cm[ProcessType(n)] = Command(c)
	}
	return cm
}

// Scan implements the sql.Scanner interface.
func (cm *CommandMap) Scan(src interface{}) error {
	h := hstore.Hstore{}
	if err := h.Scan(src); err != nil {
		return err
	}

	m := make(CommandMap)

	for k, v := range h.Map {
		m[ProcessType(k)] = Command(v.String)
	}

	*cm = m

	return nil
}

// Value implements the driver.Value interface.
func (cm CommandMap) Value() (driver.Value, error) {
	m := make(map[string]sql.NullString)

	for k, v := range cm {
		m[string(k)] = sql.NullString{
			Valid:  true,
			String: string(v),
		}
	}

	h := hstore.Hstore{
		Map: m,
	}

	return h.Value()
}

// Constraints aliases constraints.Constraints to implement the
// sql.Scanner interface.
type Constraints constraints.Constraints

func parseConstraints(con string) (*Constraints, error) {
	if con == "" {
		return nil, nil
	}

	if n, ok := NamedConstraints[con]; ok {
		return &n, nil
	}

	c, err := constraints.Parse(con)
	if err != nil {
		return nil, err
	}

	r := Constraints(c)
	return &r, nil
}

func (c *Constraints) UnmarshalJSON(b []byte) error {
	var s string

	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}

	cc, err := parseConstraints(s)
	if err != nil {
		return err
	}

	if cc != nil {
		*c = *cc
	}

	return nil
}

func (c Constraints) String() string {
	for n, constraint := range NamedConstraints {
		if c == constraint {
			return n
		}
	}

	if c.Nproc == 0 {
		return fmt.Sprintf("%d:%s", c.CPUShare, c.Memory)
	} else {
		return fmt.Sprintf("%d:%s:nproc=%d", c.CPUShare, c.Memory, c.Nproc)
	}
}

// Formation maps a process ProcessType to a Process.
type Formation map[ProcessType]*Process

// NewFormation creates a new Formation based on an existing Formation and
// the available processes from a CommandMap.
func NewFormation(f Formation, cm CommandMap) Formation {
	processes := make(Formation)

	// Iterate through all of the available process types in the CommandMap.
	for t, cmd := range cm {
		p := NewProcess(t, cmd)

		if existing, found := f[t]; found {
			// If the existing Formation already had a process
			// configuration for this process type, copy over the
			// instance count.
			p.Quantity = existing.Quantity
			p.Constraints = existing.Constraints
		}

		processes[t] = p
	}

	return processes
}

// newFormation takes a slice of processes and returns a Formation.
func newFormation(p []*Process) Formation {
	f := make(Formation)

	for _, pp := range p {
		f[pp.Type] = pp
	}

	return f
}

// Processes takes a Formation and returns a slice of the processes.
func (f Formation) Processes() []*Process {
	var processes []*Process

	for _, p := range f {
		processes = append(processes, p)
	}

	return processes
}

// processesUpdate updates an existing process into the database.
func processesUpdate(db *gorm.DB, process *Process) error {
	return db.Save(process).Error
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net"

	"github.com/mmitton/asn1-ber"
	"github.com/mmitton/ldap"
)

func handleRequest(conn net.Conn) {
	defer conn.Close()

	buf := make([]byte, 1024)
	_, err := conn.Read(buf)
	if err != nil {
		log.Println("Error reading:", err.Error())
	}

	packet := ber.DecodePacket(buf)

	if len(packet.Children) == 0 {
		log.Println("Error decoding asn1-ber packet: wrong port?")
		return
	}

	parsePacket(conn, packet)
}

func parsePacket(conn net.Conn, packet *ber.Packet) {
	messageID := packet.Children[0].Value.(uint64)
	response := packet.Children[1]

	if response.ClassType == ber.ClassApplication &&
		response.TagType == ber.TypeConstructed {
		if response.Tag == ldap.ApplicationBindRequest {
			handleBindRequest(messageID, response)
		}
	}
}

func handleBindRequest(messageID uint64, response *ber.Packet) {
	version := response.Children[0].Value.(uint64)
	name := response.Children[1].Value.(string)
	auth := response.Children[2]
	pass := auth.Data.String()
	log.Println("ApplicationBindRequest:",
		"messageID:", messageID,

		"LDAP version:", version,
		"username:", name,

		"password:", pass)
}
<commit_msg>Handle unimplemented LDAPv3 app codes<commit_after>package main

import (
	"log"
	"net"

	"github.com/mmitton/asn1-ber"
	"github.com/mmitton/ldap"
)

func handleRequest(conn net.Conn) {
	defer conn.Close()

	buf := make([]byte, 1024)
	_, err := conn.Read(buf)
	if err != nil {
		log.Println("Error reading:", err.Error())
	}

	packet := ber.DecodePacket(buf)

	if len(packet.Children) == 0 {
		log.Println("Error decoding asn1-ber packet: wrong port?")
		return
	}

	parsePacket(conn, packet)
}

func parsePacket(conn net.Conn, packet *ber.Packet) {
	messageID := packet.Children[0].Value.(uint64)
	response := packet.Children[1]

	if response.ClassType == ber.ClassApplication &&
		response.TagType == ber.TypeConstructed {
		switch response.Tag {
		case ldap.ApplicationBindRequest:
			handleBindRequest(messageID, response)
		default:
			log.Println("LDAPv3 app code not implemented:", response.Tag)
		}
	}
}

func handleBindRequest(messageID uint64, response *ber.Packet) {
	version := response.Children[0].Value.(uint64)
	name := response.Children[1].Value.(string)
	auth := response.Children[2]
	pass := auth.Data.String()
	log.Println("ApplicationBindRequest:",
		"messageID:", messageID,

		"LDAP version:", version,
		"username:", name,

		"password:", pass)
}
<|endoftext|>


<commit_before>package service

import (
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/db"
	"labix.org/v2/mgo/bson"
)

type ServiceInstance struct {
	Name        string   `bson:"_id"`
	ServiceName string   `bson:"service_name"`
	Apps        []string `bson:"apps"`
}

func (si *ServiceInstance) Create() error {
	err := db.Session.ServiceInstances().Insert(si)
	return err
}

func (si *ServiceInstance) Delete() error {
	doc := bson.M{"_id": si.Name, "apps": si.Apps}
	err := db.Session.ServiceInstances().Remove(doc)
	return err
}

func (si *ServiceInstance) Service() *Service {
	s := &Service{}
	db.Session.Services().Find(bson.M{"_id": si.ServiceName}).One(&s)
	return s
}

func (si *ServiceInstance) AllApps() []app.App {
	var apps []app.App
	q := bson.M{"name": bson.M{"$in": si.Apps}}
	db.Session.Apps().Find(q).All(&apps)
	return apps
}
<commit_msg>api/service: removed unnecessary indirect<commit_after>package service

import (
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/db"
	"labix.org/v2/mgo/bson"
)

type ServiceInstance struct {
	Name        string   `bson:"_id"`
	ServiceName string   `bson:"service_name"`
	Apps        []string `bson:"apps"`
}

func (si *ServiceInstance) Create() error {
	err := db.Session.ServiceInstances().Insert(si)
	return err
}

func (si *ServiceInstance) Delete() error {
	doc := bson.M{"_id": si.Name, "apps": si.Apps}
	err := db.Session.ServiceInstances().Remove(doc)
	return err
}

func (si *ServiceInstance) Service() *Service {
	s := &Service{}
	db.Session.Services().Find(bson.M{"_id": si.ServiceName}).One(s)
	return s
}

func (si *ServiceInstance) AllApps() []app.App {
	var apps []app.App
	q := bson.M{"name": bson.M{"$in": si.Apps}}
	db.Session.Apps().Find(q).All(&apps)
	return apps
}
<|endoftext|>