<commit_before>package main

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

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

func containerSnapshotsGet(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	cname := mux.Vars(r)["name"]

	// Handle requests targeted to a container on a different node
	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	recursion := util.IsRecursionRequest(r)
	resultString := []string{}
	resultMap := []*api.InstanceSnapshot{}

	if !recursion {
		snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
		if err != nil {
			return response.SmartError(err)
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap)
			if project == "default" {
				url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", version.APIVersion, cname, snapName)
				resultString = append(resultString, url)
			} else {
				url := fmt.Sprintf("/%s/containers/%s/snapshots/%s?project=%s", version.APIVersion, cname, snapName, project)
				resultString = append(resultString, url)
			}
		}
	} else {
		c, err := instanceLoadByProjectAndName(d.State(), project, cname)
		if err != nil {
			return response.SmartError(err)
		}

		snaps, err := c.Snapshots()
		if err != nil {
			return response.SmartError(err)
		}

		for _, snap := range snaps {
			render, _, err := snap.Render()
			if err != nil {
				continue
			}

			resultMap = append(resultMap, render.(*api.InstanceSnapshot))
		}
	}

	if !recursion {
		return response.SyncResponse(true, resultString)
	}

	return response.SyncResponse(true, resultMap)
}

func containerSnapshotsPost(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	name := mux.Vars(r)["name"]

	// Handle requests targeted to a container on a different node
	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	/*
	 * snapshot is a three step operation:
	 * 1. choose a new name
	 * 2. copy the database info over
	 * 3. copy over the rootfs
	 */
	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
	if err != nil {
		return response.SmartError(err)
	}

	req := api.InstanceSnapshotsPost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return response.BadRequest(err)
	}

	if req.Name == "" {
		req.Name, err = containerDetermineNextSnapshotName(d, inst, "snap%d")
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Validate the name
	if strings.Contains(req.Name, "/") {
		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
	}

	fullName := name +
		shared.SnapshotDelimiter +
		req.Name

	var expiry time.Time
	if req.ExpiresAt != nil {
		expiry = *req.ExpiresAt
	} else {
		expiry, err = shared.GetSnapshotExpiry(time.Now(), inst.LocalConfig()["snapshots.expiry"])
		if err != nil {
			return response.BadRequest(err)
		}
	}

	snapshot := func(op *operations.Operation) error {
		args := db.InstanceArgs{
			Project:      inst.Project(),
			Architecture: inst.Architecture(),
			Config:       inst.LocalConfig(),
			Type:         inst.Type(),
			Snapshot:     true,
			Devices:      inst.LocalDevices(),
			Ephemeral:    inst.IsEphemeral(),
			Name:         fullName,
			Profiles:     inst.Profiles(),
			Stateful:     req.Stateful,
			ExpiryDate:   expiry,
		}

		if inst.Type() != instancetype.Container {
			return fmt.Errorf("Instance is not container type")
		}

		_, err := instanceCreateAsSnapshot(d.State(), args, inst)
		if err != nil {
			return err
		}

		return nil
	}

	resources := map[string][]string{}
	resources["instances"] = []string{name}
	resources["containers"] = resources["instances"]

	op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func containerSnapshotHandler(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	containerName := mux.Vars(r)["name"]
	snapshotName := mux.Vars(r)["snapshotName"]

	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	snapshotName, err = url.QueryUnescape(snapshotName)
	if err != nil {
		return response.SmartError(err)
	}
	inst, err := instanceLoadByProjectAndName(
		d.State(),
		project, containerName+
			shared.SnapshotDelimiter+
			snapshotName)
	if err != nil {
		return response.SmartError(err)
	}

	if inst.Type() != instancetype.Container {
		return response.SmartError(fmt.Errorf("Instance is not container type"))
	}

	switch r.Method {
	case "GET":
		return snapshotGet(inst, snapshotName)
	case "POST":
		return snapshotPost(d, r, inst, containerName)
	case "DELETE":
		return snapshotDelete(inst, snapshotName)
	case "PUT":
		return snapshotPut(d, r, inst, snapshotName)
	default:
		return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
	}
}

func snapshotPut(d *Daemon, r *http.Request, sc instance.Instance, name string) response.Response {
	// Validate the ETag
	etag := []interface{}{sc.ExpiryDate()}
	err := util.EtagCheck(r, etag)
	if err != nil {
		return response.PreconditionFailed(err)
	}

	rj := shared.Jmap{}

	err = json.NewDecoder(r.Body).Decode(&rj)
	if err != nil {
		return response.InternalError(err)
	}

	var do func(op *operations.Operation) error

	_, err = rj.GetString("expires_at")
	if err != nil {
		// Skip updating the snapshot since the requested key wasn't provided
		do = func(op *operations.Operation) error {
			return nil
		}
	} else {
		body, err := json.Marshal(rj)
		if err != nil {
			return response.InternalError(err)
		}

		configRaw := api.InstanceSnapshotPut{}

		err = json.Unmarshal(body, &configRaw)
		if err != nil {
			return response.BadRequest(err)
		}

		// Update container configuration
		do = func(op *operations.Operation) error {
			args := db.InstanceArgs{
				Architecture: sc.Architecture(),
				Config:       sc.LocalConfig(),
				Description:  sc.Description(),
				Devices:      sc.LocalDevices(),
				Ephemeral:    sc.IsEphemeral(),
				Profiles:     sc.Profiles(),
				Project:      sc.Project(),
				ExpiryDate:   configRaw.ExpiresAt,
				Type:         sc.Type(),
				Snapshot:     sc.IsSnapshot(),
			}

			err = sc.Update(args, false)
			if err != nil {
				return err
			}

			return nil
		}
	}

	opType := db.OperationSnapshotUpdate

	resources := map[string][]string{}
	resources["containers"] = []string{name}

	op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, opType, resources, nil,
		do, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func snapshotGet(sc instance.Instance, name string) response.Response {
	render, _, err := sc.Render()
	if err != nil {
		return response.SmartError(err)
	}

	return response.SyncResponse(true, render.(*api.InstanceSnapshot))
}

func snapshotPost(d *Daemon, r *http.Request, sc instance.Instance, containerName string) response.Response {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return response.InternalError(err)
	}

	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))

	raw := shared.Jmap{}
	if err := json.NewDecoder(rdr1).Decode(&raw); err != nil {
		return response.BadRequest(err)
	}

	migration, err := raw.GetBool("migration")
	if err == nil && migration {
		rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))
		rdr3 := ioutil.NopCloser(bytes.NewBuffer(body))

		req := api.InstancePost{}
		err = json.NewDecoder(rdr2).Decode(&req)
		if err != nil {
			return response.BadRequest(err)
		}

		reqNew := api.InstanceSnapshotPost{}
		err = json.NewDecoder(rdr3).Decode(&reqNew)
		if err != nil {
			return response.BadRequest(err)
		}

		if reqNew.Name == "" {
			return response.BadRequest(fmt.Errorf(`A new name for the ` +
				`container must be provided`))
		}

		if reqNew.Live {
			sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
			if sourceName != reqNew.Name {
				return response.BadRequest(fmt.Errorf(`Copying `+
					`stateful containers requires that `+
					`source "%s" and `+`target "%s" name `+
					`be identical`, sourceName, reqNew.Name))
			}
		}

		ws, err := NewMigrationSource(sc, reqNew.Live, true)
		if err != nil {
			return response.SmartError(err)
		}

		resources := map[string][]string{}
		resources["containers"] = []string{containerName}

		if req.Target != nil {
			// Push mode
			err := ws.ConnectContainerTarget(*req.Target)
			if err != nil {
				return response.InternalError(err)
			}

			op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
			if err != nil {
				return response.InternalError(err)
			}

			return operations.OperationResponse(op)
		}

		// Pull mode
		op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
		if err != nil {
			return response.InternalError(err)
		}

		return operations.OperationResponse(op)
	}

	newName, err := raw.GetString("name")
	if err != nil {
		return response.BadRequest(err)
	}

	// Validate the name
	if strings.Contains(newName, "/") {
		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
	}

	fullName := containerName + shared.SnapshotDelimiter + newName

	// Check that the name isn't already in use
	id, _ := d.cluster.InstanceSnapshotID(sc.Project(), containerName, newName)
	if id > 0 {
		return response.Conflict(fmt.Errorf("Name '%s' already in use", fullName))
	}

	rename := func(op *operations.Operation) error {
		return sc.Rename(fullName)
	}

	resources := map[string][]string{}
	resources["containers"] = []string{containerName}

	op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func snapshotDelete(sc instance.Instance, name string) response.Response {
	remove := func(op *operations.Operation) error {
		return sc.Delete()
	}

	resources := map[string][]string{}
	resources["containers"] = []string{sc.Name()}

	op, err := operations.OperationCreate(sc.DaemonState(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}
<commit_msg>lxd/container/snapshot: Removes duplicated instance type check<commit_after>package main

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

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

func containerSnapshotsGet(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	cname := mux.Vars(r)["name"]

	// Handle requests targeted to a container on a different node
	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	recursion := util.IsRecursionRequest(r)
	resultString := []string{}
	resultMap := []*api.InstanceSnapshot{}

	if !recursion {
		snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
		if err != nil {
			return response.SmartError(err)
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap)
			if project == "default" {
				url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", version.APIVersion, cname, snapName)
				resultString = append(resultString, url)
			} else {
				url := fmt.Sprintf("/%s/containers/%s/snapshots/%s?project=%s", version.APIVersion, cname, snapName, project)
				resultString = append(resultString, url)
			}
		}
	} else {
		c, err := instanceLoadByProjectAndName(d.State(), project, cname)
		if err != nil {
			return response.SmartError(err)
		}

		snaps, err := c.Snapshots()
		if err != nil {
			return response.SmartError(err)
		}

		for _, snap := range snaps {
			render, _, err := snap.Render()
			if err != nil {
				continue
			}

			resultMap = append(resultMap, render.(*api.InstanceSnapshot))
		}
	}

	if !recursion {
		return response.SyncResponse(true, resultString)
	}

	return response.SyncResponse(true, resultMap)
}

func containerSnapshotsPost(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	name := mux.Vars(r)["name"]

	// Handle requests targeted to a container on a different node
	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	/*
	 * snapshot is a three step operation:
	 * 1. choose a new name
	 * 2. copy the database info over
	 * 3. copy over the rootfs
	 */
	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
	if err != nil {
		return response.SmartError(err)
	}

	req := api.InstanceSnapshotsPost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return response.BadRequest(err)
	}

	if req.Name == "" {
		req.Name, err = containerDetermineNextSnapshotName(d, inst, "snap%d")
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Validate the name
	if strings.Contains(req.Name, "/") {
		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
	}

	fullName := name +
		shared.SnapshotDelimiter +
		req.Name

	var expiry time.Time
	if req.ExpiresAt != nil {
		expiry = *req.ExpiresAt
	} else {
		expiry, err = shared.GetSnapshotExpiry(time.Now(), inst.LocalConfig()["snapshots.expiry"])
		if err != nil {
			return response.BadRequest(err)
		}
	}

	snapshot := func(op *operations.Operation) error {
		args := db.InstanceArgs{
			Project:      inst.Project(),
			Architecture: inst.Architecture(),
			Config:       inst.LocalConfig(),
			Type:         inst.Type(),
			Snapshot:     true,
			Devices:      inst.LocalDevices(),
			Ephemeral:    inst.IsEphemeral(),
			Name:         fullName,
			Profiles:     inst.Profiles(),
			Stateful:     req.Stateful,
			ExpiryDate:   expiry,
		}

		_, err := instanceCreateAsSnapshot(d.State(), args, inst)
		if err != nil {
			return err
		}

		return nil
	}

	resources := map[string][]string{}
	resources["instances"] = []string{name}
	resources["containers"] = resources["instances"]

	op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func containerSnapshotHandler(d *Daemon, r *http.Request) response.Response {
	instanceType, err := urlInstanceTypeDetect(r)
	if err != nil {
		return response.SmartError(err)
	}

	project := projectParam(r)
	containerName := mux.Vars(r)["name"]
	snapshotName := mux.Vars(r)["snapshotName"]

	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
	if err != nil {
		return response.SmartError(err)
	}
	if resp != nil {
		return resp
	}

	snapshotName, err = url.QueryUnescape(snapshotName)
	if err != nil {
		return response.SmartError(err)
	}
	inst, err := instanceLoadByProjectAndName(
		d.State(),
		project, containerName+
			shared.SnapshotDelimiter+
			snapshotName)
	if err != nil {
		return response.SmartError(err)
	}

	if inst.Type() != instancetype.Container {
		return response.SmartError(fmt.Errorf("Instance is not container type"))
	}

	switch r.Method {
	case "GET":
		return snapshotGet(inst, snapshotName)
	case "POST":
		return snapshotPost(d, r, inst, containerName)
	case "DELETE":
		return snapshotDelete(inst, snapshotName)
	case "PUT":
		return snapshotPut(d, r, inst, snapshotName)
	default:
		return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
	}
}

func snapshotPut(d *Daemon, r *http.Request, sc instance.Instance, name string) response.Response {
	// Validate the ETag
	etag := []interface{}{sc.ExpiryDate()}
	err := util.EtagCheck(r, etag)
	if err != nil {
		return response.PreconditionFailed(err)
	}

	rj := shared.Jmap{}

	err = json.NewDecoder(r.Body).Decode(&rj)
	if err != nil {
		return response.InternalError(err)
	}

	var do func(op *operations.Operation) error

	_, err = rj.GetString("expires_at")
	if err != nil {
		// Skip updating the snapshot since the requested key wasn't provided
		do = func(op *operations.Operation) error {
			return nil
		}
	} else {
		body, err := json.Marshal(rj)
		if err != nil {
			return response.InternalError(err)
		}

		configRaw := api.InstanceSnapshotPut{}

		err = json.Unmarshal(body, &configRaw)
		if err != nil {
			return response.BadRequest(err)
		}

		// Update container configuration
		do = func(op *operations.Operation) error {
			args := db.InstanceArgs{
				Architecture: sc.Architecture(),
				Config:       sc.LocalConfig(),
				Description:  sc.Description(),
				Devices:      sc.LocalDevices(),
				Ephemeral:    sc.IsEphemeral(),
				Profiles:     sc.Profiles(),
				Project:      sc.Project(),
				ExpiryDate:   configRaw.ExpiresAt,
				Type:         sc.Type(),
				Snapshot:     sc.IsSnapshot(),
			}

			err = sc.Update(args, false)
			if err != nil {
				return err
			}

			return nil
		}
	}

	opType := db.OperationSnapshotUpdate

	resources := map[string][]string{}
	resources["containers"] = []string{name}

	op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, opType, resources, nil,
		do, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func snapshotGet(sc instance.Instance, name string) response.Response {
	render, _, err := sc.Render()
	if err != nil {
		return response.SmartError(err)
	}

	return response.SyncResponse(true, render.(*api.InstanceSnapshot))
}

func snapshotPost(d *Daemon, r *http.Request, sc instance.Instance, containerName string) response.Response {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return response.InternalError(err)
	}

	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))

	raw := shared.Jmap{}
	if err := json.NewDecoder(rdr1).Decode(&raw); err != nil {
		return response.BadRequest(err)
	}

	migration, err := raw.GetBool("migration")
	if err == nil && migration {
		rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))
		rdr3 := ioutil.NopCloser(bytes.NewBuffer(body))

		req := api.InstancePost{}
		err = json.NewDecoder(rdr2).Decode(&req)
		if err != nil {
			return response.BadRequest(err)
		}

		reqNew := api.InstanceSnapshotPost{}
		err = json.NewDecoder(rdr3).Decode(&reqNew)
		if err != nil {
			return response.BadRequest(err)
		}

		if reqNew.Name == "" {
			return response.BadRequest(fmt.Errorf(`A new name for the ` +
				`container must be provided`))
		}

		if reqNew.Live {
			sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
			if sourceName != reqNew.Name {
				return response.BadRequest(fmt.Errorf(`Copying `+
					`stateful containers requires that `+
					`source "%s" and `+`target "%s" name `+
					`be identical`, sourceName, reqNew.Name))
			}
		}

		ws, err := NewMigrationSource(sc, reqNew.Live, true)
		if err != nil {
			return response.SmartError(err)
		}

		resources := map[string][]string{}
		resources["containers"] = []string{containerName}

		if req.Target != nil {
			// Push mode
			err := ws.ConnectContainerTarget(*req.Target)
			if err != nil {
				return response.InternalError(err)
			}

			op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
			if err != nil {
				return response.InternalError(err)
			}

			return operations.OperationResponse(op)
		}

		// Pull mode
		op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
		if err != nil {
			return response.InternalError(err)
		}

		return operations.OperationResponse(op)
	}

	newName, err := raw.GetString("name")
	if err != nil {
		return response.BadRequest(err)
	}

	// Validate the name
	if strings.Contains(newName, "/") {
		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
	}

	fullName := containerName + shared.SnapshotDelimiter + newName

	// Check that the name isn't already in use
	id, _ := d.cluster.InstanceSnapshotID(sc.Project(), containerName, newName)
	if id > 0 {
		return response.Conflict(fmt.Errorf("Name '%s' already in use", fullName))
	}

	rename := func(op *operations.Operation) error {
		return sc.Rename(fullName)
	}

	resources := map[string][]string{}
	resources["containers"] = []string{containerName}

	op, err := operations.OperationCreate(d.State(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}

func snapshotDelete(sc instance.Instance, name string) response.Response {
	remove := func(op *operations.Operation) error {
		return sc.Delete()
	}

	resources := map[string][]string{}
	resources["containers"] = []string{sc.Name()}

	op, err := operations.OperationCreate(sc.DaemonState(), sc.Project(), operations.OperationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
	if err != nil {
		return response.InternalError(err)
	}

	return operations.OperationResponse(op)
}
<|endoftext|>


<commit_before>package cvmfs

import (
	"fmt"
	"strings"

	exec "github.com/cvmfs/ducc/exec"
	log "github.com/sirupsen/logrus"
)

type TransactionOption interface {
	ToString() string
}

func OpenTransaction(CVMFSRepo string, options ...TransactionOption) error {
	cmd := []string{"cvmfs_server", "transaction"}
	for _, opt := range options {
		cmd = append(cmd, opt.ToString())
	}
	cmd = append(cmd, CVMFSRepo)
	err := exec.ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in opening the transaction")
		abort(CVMFSRepo)
	}
	return err
}

func Publish(CVMFSRepo string) error {
	err := exec.ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in publishing the repository")
		abort(CVMFSRepo)
	}
	return err
}

func Abort(CVMFSRepo string) error {
	err := abort(CVMFSRepo)
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in abort the transaction")
	}
	return err
}

func abort(CVMFSRepo string) error {
	return exec.ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
}

func RepositoryExists(CVMFSRepo string) bool {
	cmd := exec.ExecCommand("cvmfs_server", "list")
	err, stdout, _ := cmd.StartWithOutput()
	if err != nil {
		LogE(fmt.Errorf("Error in listing the repository")).
			Error("Repo not present")
		return false
	}
	stdoutString := string(stdout.Bytes())

	if strings.Contains(stdoutString, CVMFSRepo) {
		return true
	} else {
		return false
	}
}
<commit_msg>add TemplateTransaction option<commit_after>package cvmfs

import (
	"fmt"
	"strings"

	exec "github.com/cvmfs/ducc/exec"
	log "github.com/sirupsen/logrus"
)

type TransactionOption interface {
	ToString() string
}

type TemplateTransaction struct {
	source      string
	destination string
}

func (t TemplateTransaction) ToString() string {
	return fmt.Sprintf("-T %s=%s", t.source, t.destination)
}

func OpenTransaction(CVMFSRepo string, options ...TransactionOption) error {
	cmd := []string{"cvmfs_server", "transaction"}
	for _, opt := range options {
		cmd = append(cmd, opt.ToString())
	}
	cmd = append(cmd, CVMFSRepo)
	err := exec.ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in opening the transaction")
		abort(CVMFSRepo)
	}
	return err
}

func Publish(CVMFSRepo string) error {
	err := exec.ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in publishing the repository")
		abort(CVMFSRepo)
	}
	return err
}

func Abort(CVMFSRepo string) error {
	err := abort(CVMFSRepo)
	if err != nil {
		LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in abort the transaction")
	}
	return err
}

func abort(CVMFSRepo string) error {
	return exec.ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
}

func RepositoryExists(CVMFSRepo string) bool {
	cmd := exec.ExecCommand("cvmfs_server", "list")
	err, stdout, _ := cmd.StartWithOutput()
	if err != nil {
		LogE(fmt.Errorf("Error in listing the repository")).
			Error("Repo not present")
		return false
	}
	stdoutString := string(stdout.Bytes())

	if strings.Contains(stdoutString, CVMFSRepo) {
		return true
	} else {
		return false
	}
}
<|endoftext|>


<commit_before>package siri

import (
	"bytes"
	"text/template"
	"time"

	"github.com/jbowtie/gokogiri"
	"github.com/jbowtie/gokogiri/xml"
)

type XMLNotifyGeneralMessage struct {
	ResponseXMLStructure

	deliveries []*XMLGeneralMessageDelivery
}

type XMLGeneralMessageDelivery struct {
	ResponseXMLStructure

	subscriptionRef string
	subscriberRef   string

	xmlGeneralMessages              []*XMLGeneralMessage
	xmlGeneralMessagesCancellations []*XMLGeneralMessageCancellation
}

type SIRINotifyGeneralMessage struct {
	Address                   string
	ProducerRef               string
	RequestMessageRef         string
	ResponseMessageIdentifier string
	SubscriberRef             string
	SubscriptionIdentifier    string

	ResponseTimestamp time.Time

	Status      bool
	ErrorType   string
	ErrorNumber int
	ErrorText   string

	GeneralMessages []*SIRIGeneralMessage
}

const generalMessageNotifyTemplate = `<sw:NotifyGeneralMessage xmlns:sw="http://wsdl.siri.org.uk" xmlns:siri="http://www.siri.org.uk/siri">
	<ServiceDeliveryInfo>
		<siri:ResponseTimestamp>{{ .ResponseTimestamp.Format "2006-01-02T15:04:05.000Z07:00" }}</siri:ResponseTimestamp>
		<siri:ProducerRef>{{ .ProducerRef }}</siri:ProducerRef>{{ if .Address }}
		<siri:Address>{{ .Address }}</siri:Address>{{ end }}
		<siri:ResponseMessageIdentifier>{{ .ResponseMessageIdentifier }}</siri:ResponseMessageIdentifier>
		<siri:RequestMessageRef>{{ .RequestMessageRef }}</siri:RequestMessageRef>
	</ServiceDeliveryInfo>
	<Notification xmlns:ns2="http://www.ifopt.org.uk/acsb" xmlns:siri="http://www.ifopt.org.uk/ifopt" xmlns:ns4="http://datex2.eu/schema/2_0RC1/2_0" xmlns:siri="http://www.siri.org.uk/siri" xmlns:ns6="http://wsdl.siri.org.uk/siri">
		<siri:GeneralMessageDelivery version="2.0:FR-IDF-2.4" xmlns:stif="http://wsdl.siri.org.uk/siri">
			<siri:ResponseTimestamp>{{ .ResponseTimestamp.Format "2006-01-02T15:04:05.000Z07:00" }}</siri:ResponseTimestamp>
			<siri:RequestMessageRef>{{ .RequestMessageRef }}</siri:RequestMessageRef>
			<siri:SubscriberRef>{{ .SubscriberRef }}</siri:SubscriberRef>
			<siri:SubscriptionRef>{{ .SubscriptionIdentifier }}</siri:SubscriptionRef>
			<siri:Status>{{ .Status }}</siri:Status>{{ if not .Status }}
			<siri:ErrorCondition>{{ if eq .ErrorType "OtherError" }}
				<siri:OtherError number="{{ .ErrorNumber }}">{{ else }}
				<siri:{{ .ErrorType }}>{{ end }}
					<siri:ErrorText>{{ .ErrorText }}</siri:ErrorText>
				</siri:{{ .ErrorType }}>
			</siri:ErrorCondition>{{ else }}{{ range .GeneralMessages }}
			{{ .BuildGeneralMessageXML }}{{ end }}{{ end }}
		 </siri:GeneralMessageDelivery>
	</Notification>
	<NotificationExtension />
</sw:NotifyGeneralMessage>`

func NewXMLNotifyGeneralMessage(node xml.Node) *XMLNotifyGeneralMessage {
	xmlGeneralMessageResponse := &XMLNotifyGeneralMessage{}
	xmlGeneralMessageResponse.node = NewXMLNode(node)
	return xmlGeneralMessageResponse
}

func NewXMLNotifyGeneralMessageFromContent(content []byte) (*XMLNotifyGeneralMessage, error) {
	doc, err := gokogiri.ParseXml(content)
	if err != nil {
		return nil, err
	}
	response := NewXMLNotifyGeneralMessage(doc.Root().XmlNode)
	return response, nil
}

func NewXMLGeneralMessageDelivery(node XMLNode) *XMLGeneralMessageDelivery {
	delivery := &XMLGeneralMessageDelivery{}
	delivery.node = node
	return delivery
}

func (notify *XMLNotifyGeneralMessage) GeneralMessagesDeliveries() []*XMLGeneralMessageDelivery {
	if notify.deliveries == nil {
		deliveries := []*XMLGeneralMessageDelivery{}
		nodes := notify.findNodes("GeneralMessageDelivery")
		if nodes != nil {
			for _, node := range nodes {
				deliveries = append(deliveries, NewXMLGeneralMessageDelivery(node))
			}
		}
		notify.deliveries = deliveries
	}
	return notify.deliveries
}

func (delivery *XMLGeneralMessageDelivery) SubscriptionRef() string {
	if delivery.subscriptionRef == "" {
		delivery.subscriptionRef = delivery.findStringChildContent("SubscriptionRef")
	}
	return delivery.subscriptionRef
}

func (delivery *XMLGeneralMessageDelivery) SubscriberRef() string {
	if delivery.subscriberRef == "" {
		delivery.subscriberRef = delivery.findStringChildContent("SubscriberRef")
	}
	return delivery.subscriberRef
}

func (delivery *XMLGeneralMessageDelivery) XMLGeneralMessages() []*XMLGeneralMessage {
	if delivery.xmlGeneralMessages == nil {
		nodes := delivery.findNodes("GeneralMessage")
		if nodes != nil {
			for _, node := range nodes {
				delivery.xmlGeneralMessages = append(delivery.xmlGeneralMessages, NewXMLGeneralMessage(node))
			}
		}
	}
	return delivery.xmlGeneralMessages
}

func (delivery *XMLGeneralMessageDelivery) XMLGeneralMessagesCancellations() []*XMLGeneralMessageCancellation {
	if delivery.xmlGeneralMessagesCancellations == nil {
		cancellations := []*XMLGeneralMessageCancellation{}
		nodes := delivery.findNodes("GeneralMessageCancellation")
		if nodes != nil {
			for _, node := range nodes {
				cancellations = append(cancellations, NewXMLCancelledGeneralMessage(node))
			}
		}
		delivery.xmlGeneralMessagesCancellations = cancellations
	}
	return delivery.xmlGeneralMessagesCancellations
}

func (notify *SIRINotifyGeneralMessage) BuildXML() (string, error) {
	var buffer bytes.Buffer
	var notifyDelivery = template.Must(template.New("generalMessageNotifyTemplate").Parse(generalMessageNotifyTemplate))
	if err := notifyDelivery.Execute(&buffer, notify); err != nil {
		return "", err
	}
	return buffer.String(), nil
}
<commit_msg>fix GM notification template. Refs #6154<commit_after>package siri

import (
	"bytes"
	"text/template"
	"time"

	"github.com/jbowtie/gokogiri"
	"github.com/jbowtie/gokogiri/xml"
)

type XMLNotifyGeneralMessage struct {
	ResponseXMLStructure

	deliveries []*XMLGeneralMessageDelivery
}

type XMLGeneralMessageDelivery struct {
	ResponseXMLStructure

	subscriptionRef string
	subscriberRef   string

	xmlGeneralMessages              []*XMLGeneralMessage
	xmlGeneralMessagesCancellations []*XMLGeneralMessageCancellation
}

type SIRINotifyGeneralMessage struct {
	Address                   string
	ProducerRef               string
	RequestMessageRef         string
	ResponseMessageIdentifier string
	SubscriberRef             string
	SubscriptionIdentifier    string

	ResponseTimestamp time.Time

	Status      bool
	ErrorType   string
	ErrorNumber int
	ErrorText   string

	GeneralMessages []*SIRIGeneralMessage
}

const generalMessageNotifyTemplate = `<sw:NotifyGeneralMessage xmlns:sw="http://wsdl.siri.org.uk" xmlns:siri="http://www.siri.org.uk/siri">
	<ServiceDeliveryInfo>
		<siri:ResponseTimestamp>{{ .ResponseTimestamp.Format "2006-01-02T15:04:05.000Z07:00" }}</siri:ResponseTimestamp>
		<siri:ProducerRef>{{ .ProducerRef }}</siri:ProducerRef>{{ if .Address }}
		<siri:Address>{{ .Address }}</siri:Address>{{ end }}
		<siri:ResponseMessageIdentifier>{{ .ResponseMessageIdentifier }}</siri:ResponseMessageIdentifier>
		<siri:RequestMessageRef>{{ .RequestMessageRef }}</siri:RequestMessageRef>
	</ServiceDeliveryInfo>
	<Notification>
		<siri:GeneralMessageDelivery version="2.0:FR-IDF-2.4" xmlns:stif="http://wsdl.siri.org.uk/siri">
			<siri:ResponseTimestamp>{{ .ResponseTimestamp.Format "2006-01-02T15:04:05.000Z07:00" }}</siri:ResponseTimestamp>
			<siri:RequestMessageRef>{{ .RequestMessageRef }}</siri:RequestMessageRef>
			<siri:SubscriberRef>{{ .SubscriberRef }}</siri:SubscriberRef>
			<siri:SubscriptionRef>{{ .SubscriptionIdentifier }}</siri:SubscriptionRef>
			<siri:Status>{{ .Status }}</siri:Status>{{ if not .Status }}
			<siri:ErrorCondition>{{ if eq .ErrorType "OtherError" }}
				<siri:OtherError number="{{ .ErrorNumber }}">{{ else }}
				<siri:{{ .ErrorType }}>{{ end }}
					<siri:ErrorText>{{ .ErrorText }}</siri:ErrorText>
				</siri:{{ .ErrorType }}>
			</siri:ErrorCondition>{{ else }}{{ range .GeneralMessages }}
			{{ .BuildGeneralMessageXML }}{{ end }}{{ end }}
		 </siri:GeneralMessageDelivery>
	</Notification>
	<NotificationExtension />
</sw:NotifyGeneralMessage>`

func NewXMLNotifyGeneralMessage(node xml.Node) *XMLNotifyGeneralMessage {
	xmlGeneralMessageResponse := &XMLNotifyGeneralMessage{}
	xmlGeneralMessageResponse.node = NewXMLNode(node)
	return xmlGeneralMessageResponse
}

func NewXMLNotifyGeneralMessageFromContent(content []byte) (*XMLNotifyGeneralMessage, error) {
	doc, err := gokogiri.ParseXml(content)
	if err != nil {
		return nil, err
	}
	response := NewXMLNotifyGeneralMessage(doc.Root().XmlNode)
	return response, nil
}

func NewXMLGeneralMessageDelivery(node XMLNode) *XMLGeneralMessageDelivery {
	delivery := &XMLGeneralMessageDelivery{}
	delivery.node = node
	return delivery
}

func (notify *XMLNotifyGeneralMessage) GeneralMessagesDeliveries() []*XMLGeneralMessageDelivery {
	if notify.deliveries == nil {
		deliveries := []*XMLGeneralMessageDelivery{}
		nodes := notify.findNodes("GeneralMessageDelivery")
		if nodes != nil {
			for _, node := range nodes {
				deliveries = append(deliveries, NewXMLGeneralMessageDelivery(node))
			}
		}
		notify.deliveries = deliveries
	}
	return notify.deliveries
}

func (delivery *XMLGeneralMessageDelivery) SubscriptionRef() string {
	if delivery.subscriptionRef == "" {
		delivery.subscriptionRef = delivery.findStringChildContent("SubscriptionRef")
	}
	return delivery.subscriptionRef
}

func (delivery *XMLGeneralMessageDelivery) SubscriberRef() string {
	if delivery.subscriberRef == "" {
		delivery.subscriberRef = delivery.findStringChildContent("SubscriberRef")
	}
	return delivery.subscriberRef
}

func (delivery *XMLGeneralMessageDelivery) XMLGeneralMessages() []*XMLGeneralMessage {
	if delivery.xmlGeneralMessages == nil {
		nodes := delivery.findNodes("GeneralMessage")
		if nodes != nil {
			for _, node := range nodes {
				delivery.xmlGeneralMessages = append(delivery.xmlGeneralMessages, NewXMLGeneralMessage(node))
			}
		}
	}
	return delivery.xmlGeneralMessages
}

func (delivery *XMLGeneralMessageDelivery) XMLGeneralMessagesCancellations() []*XMLGeneralMessageCancellation {
	if delivery.xmlGeneralMessagesCancellations == nil {
		cancellations := []*XMLGeneralMessageCancellation{}
		nodes := delivery.findNodes("GeneralMessageCancellation")
		if nodes != nil {
			for _, node := range nodes {
				cancellations = append(cancellations, NewXMLCancelledGeneralMessage(node))
			}
		}
		delivery.xmlGeneralMessagesCancellations = cancellations
	}
	return delivery.xmlGeneralMessagesCancellations
}

func (notify *SIRINotifyGeneralMessage) BuildXML() (string, error) {
	var buffer bytes.Buffer
	var notifyDelivery = template.Must(template.New("generalMessageNotifyTemplate").Parse(generalMessageNotifyTemplate))
	if err := notifyDelivery.Execute(&buffer, notify); err != nil {
		return "", err
	}
	return buffer.String(), nil
}
<|endoftext|>


<commit_before>package cvmfs

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	"github.com/rubyist/lockfile"

	exec "github.com/cvmfs/ducc/exec"
	l "github.com/cvmfs/ducc/log"
	log "github.com/sirupsen/logrus"
)

type TransactionOption interface {
	ToString() string
}

type TemplateTransaction struct {
	source      string
	destination string
}

func NewTemplateTransaction(source, destination string) TemplateTransaction {
	return TemplateTransaction{source, destination}
}

func (t TemplateTransaction) ToString() string {
	return fmt.Sprintf("-T %s=%s", t.source, t.destination)
}

var locksMap = make(map[string]*sync.Mutex)
var lockMap = &sync.Mutex{}
var lockFile = lockfile.NewFcntlLockfile("/tmp/DUCC.lock")

func getLock(CVMFSRepo string) {
	lockMap.Lock()
	lc := locksMap[CVMFSRepo]
	if lc == nil {
		locksMap[CVMFSRepo] = &sync.Mutex{}
		lc = locksMap[CVMFSRepo]
	}
	lc.Lock()

	err := lockFile.LockWriteB()
	for err != nil {
		l.LogE(err).Info("Error in getting the FS lock")
		time.Sleep(100 * time.Millisecond)
		err = lockFile.LockWriteB()
	}
}

func unlock(CVMFSRepo string) {
	lockMap.Lock()
	l := locksMap[CVMFSRepo]
	lockMap.Unlock()
	l.Unlock()
	lockFile.Unlock()
}

func ExecuteAndOpenTransaction(CVMFSRepo string, f func() error, options ...TransactionOption) error {
	cmd := []string{"cvmfs_server", "transaction"}
	for _, opt := range options {
		cmd = append(cmd, opt.ToString())
	}
	cmd = append(cmd, CVMFSRepo)
	getLock(CVMFSRepo)
	if err := f(); err != nil {
		unlock(CVMFSRepo)
		return err
	}
	err := exec.ExecCommand(cmd...).Start()
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in opening the transaction")
		Abort(CVMFSRepo)
	}
	return err

}

func OpenTransaction(CVMFSRepo string, options ...TransactionOption) error {
	return ExecuteAndOpenTransaction(CVMFSRepo, func() error { return nil }, options...)
}

func Publish(CVMFSRepo string) error {
	defer unlock(CVMFSRepo)
	err := exec.ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in publishing the repository")
		abort(CVMFSRepo)
		return err
	}

	l.LogE(err).WithFields(
		log.Fields{"repo": CVMFSRepo}).
		Info("Publish complete")
	return nil
}

func Abort(CVMFSRepo string) error {
	defer unlock(CVMFSRepo)
	err := abort(CVMFSRepo)
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in abort the transaction")
	}
	return err
}

func abort(CVMFSRepo string) error {
	return exec.ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
}

func RepositoryExists(CVMFSRepo string) bool {
	cmd := exec.ExecCommand("cvmfs_server", "list")
	err, stdout, _ := cmd.StartWithOutput()
	if err != nil {
		l.LogE(fmt.Errorf("Error in listing the repository")).
			Error("Repo not present")
		return false
	}
	stdoutString := string(stdout.Bytes())

	if strings.Contains(stdoutString, CVMFSRepo) {
		return true
	} else {
		return false
	}
}

func WithinTransaction(CVMFSRepo string, f func() error, opts ...TransactionOption) error {
	err := OpenTransaction(CVMFSRepo, opts...)
	if err != nil {
		return err
	}
	err = f()
	if err != nil {
		return Abort(CVMFSRepo)
	}
	return Publish(CVMFSRepo)
}

func Ingest(CVMFSRepo string, input io.ReadCloser, options ...string) error {
	cmd := []string{"cvmfs_server", "ingest"}
	for _, opt := range options {
		cmd = append(cmd, opt)
	}
	cmd = append(cmd, CVMFSRepo)
	getLock(CVMFSRepo)
	defer unlock(CVMFSRepo)
	return exec.ExecCommand(cmd...).StdIn(input).Start()
}

func IngestDelete(CVMFSRepo string, path string) error {
	getLock(CVMFSRepo)
	defer unlock(CVMFSRepo)
	return exec.ExecCommand("cvmfs_server", "ingest", "--delete", path, CVMFSRepo).Start()
}
<commit_msg>put back the unlock<commit_after>package cvmfs

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	"github.com/rubyist/lockfile"

	exec "github.com/cvmfs/ducc/exec"
	l "github.com/cvmfs/ducc/log"
	log "github.com/sirupsen/logrus"
)

type TransactionOption interface {
	ToString() string
}

type TemplateTransaction struct {
	source      string
	destination string
}

func NewTemplateTransaction(source, destination string) TemplateTransaction {
	return TemplateTransaction{source, destination}
}

func (t TemplateTransaction) ToString() string {
	return fmt.Sprintf("-T %s=%s", t.source, t.destination)
}

var locksMap = make(map[string]*sync.Mutex)
var lockMap = &sync.Mutex{}
var lockFile = lockfile.NewFcntlLockfile("/tmp/DUCC.lock")

func getLock(CVMFSRepo string) {
	lockMap.Lock()
	lc := locksMap[CVMFSRepo]
	if lc == nil {
		locksMap[CVMFSRepo] = &sync.Mutex{}
		lc = locksMap[CVMFSRepo]
	}
	lc.Lock()
	lockMap.Unlock()

	err := lockFile.LockWriteB()
	for err != nil {
		// this may happen if the kernel detect a deadlock
		// it should never happen in our case, (of a single global lock)
		// but still we can protect against it
		l.LogE(err).Info("Error in getting the FS lock")
		time.Sleep(100 * time.Millisecond)
		err = lockFile.LockWriteB()
	}
}

func unlock(CVMFSRepo string) {
	lockMap.Lock()
	l := locksMap[CVMFSRepo]
	lockMap.Unlock()
	l.Unlock()
	lockFile.Unlock()
}

func ExecuteAndOpenTransaction(CVMFSRepo string, f func() error, options ...TransactionOption) error {
	cmd := []string{"cvmfs_server", "transaction"}
	for _, opt := range options {
		cmd = append(cmd, opt.ToString())
	}
	cmd = append(cmd, CVMFSRepo)
	getLock(CVMFSRepo)
	if err := f(); err != nil {
		unlock(CVMFSRepo)
		return err
	}
	err := exec.ExecCommand(cmd...).Start()
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in opening the transaction")
		Abort(CVMFSRepo)
	}
	return err

}

func OpenTransaction(CVMFSRepo string, options ...TransactionOption) error {
	return ExecuteAndOpenTransaction(CVMFSRepo, func() error { return nil }, options...)
}

func Publish(CVMFSRepo string) error {
	defer unlock(CVMFSRepo)
	err := exec.ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in publishing the repository")
		abort(CVMFSRepo)
		return err
	}

	l.LogE(err).WithFields(
		log.Fields{"repo": CVMFSRepo}).
		Info("Publish complete")
	return nil
}

func Abort(CVMFSRepo string) error {
	defer unlock(CVMFSRepo)
	err := abort(CVMFSRepo)
	if err != nil {
		l.LogE(err).WithFields(
			log.Fields{"repo": CVMFSRepo}).
			Error("Error in abort the transaction")
	}
	return err
}

func abort(CVMFSRepo string) error {
	return exec.ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
}

func RepositoryExists(CVMFSRepo string) bool {
	cmd := exec.ExecCommand("cvmfs_server", "list")
	err, stdout, _ := cmd.StartWithOutput()
	if err != nil {
		l.LogE(fmt.Errorf("Error in listing the repository")).
			Error("Repo not present")
		return false
	}
	stdoutString := string(stdout.Bytes())

	if strings.Contains(stdoutString, CVMFSRepo) {
		return true
	} else {
		return false
	}
}

func WithinTransaction(CVMFSRepo string, f func() error, opts ...TransactionOption) error {
	err := OpenTransaction(CVMFSRepo, opts...)
	if err != nil {
		return err
	}
	err = f()
	if err != nil {
		return Abort(CVMFSRepo)
	}
	return Publish(CVMFSRepo)
}

func Ingest(CVMFSRepo string, input io.ReadCloser, options ...string) error {
	cmd := []string{"cvmfs_server", "ingest"}
	for _, opt := range options {
		cmd = append(cmd, opt)
	}
	cmd = append(cmd, CVMFSRepo)
	getLock(CVMFSRepo)
	defer unlock(CVMFSRepo)
	return exec.ExecCommand(cmd...).StdIn(input).Start()
}

func IngestDelete(CVMFSRepo string, path string) error {
	getLock(CVMFSRepo)
	defer unlock(CVMFSRepo)
	return exec.ExecCommand("cvmfs_server", "ingest", "--delete", path, CVMFSRepo).Start()
}
<|endoftext|>


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

package solrmanapi

import (
	"fmt"
)

const (
	OpStatus     = "Status"
	OpMoveShard  = "MoveShard"
	OpSplitShard = "SplitShard"
)

type OpRecord struct {
	StartedMs  int64  `json:"Started"`  // start time, in millis since epoch
	FinishedMs int64  `json:"Finished"` // end time, in millis since epoch
	NumDocs    int64  `json:"NumDocs"`
	IndexSize  int64  `json:"IndexSize"`
	Operation  string // one of the Op* constants
	Collection string
	Shard      string
	Replica    string // replica name (i.e. core_node09)
	SrcNode    string // instance name of source node (in the case of a split, this node has the parent)
	DstNode    string // instance name of destination node
	Requestor  string // Who requested the operation (either a user, or "solrman" if automation)
	Error      string // non-empty if the operation failed; always set on a Status op
	AsyncId    string // Async ID if present
}

func (r *OpRecord) String() string {
	switch r.Operation {
	case OpStatus:
		return fmt.Sprintf("status: %s", r.Error)
	case OpMoveShard:
		var replica string
		if r.Replica != "" {
			replica = "_" + r.Replica
		}
		return fmt.Sprintf("move %s_%s%s from %s to %s", r.Collection, r.Shard, replica, r.SrcNode, r.DstNode)
	case OpSplitShard:
		return fmt.Sprintf("split %s_%s", r.Collection, r.Shard)
	default:
		return fmt.Sprintf("unknown operation %q", r.Operation)
	}
}

func (r *OpRecord) Key() string {
	return fmt.Sprintf("SolrOp:%s:%s", r.Collection, r.Shard)
}

type ByStartedRecently []OpRecord

func (a ByStartedRecently) Len() int           { return len(a) }
func (a ByStartedRecently) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByStartedRecently) Less(i, j int) bool { return a[i].StartedMs > a[j].StartedMs }

type ByFinishedRecently []OpRecord

func (a ByFinishedRecently) Len() int           { return len(a) }
func (a ByFinishedRecently) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByFinishedRecently) Less(i, j int) bool { return a[i].FinishedMs > a[j].FinishedMs }
<commit_msg>Added replica to opRecord key as now there is more than one replica per (#49)<commit_after>// Copyright 2016 FullStory, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package solrmanapi

import (
	"fmt"
)

const (
	OpStatus     = "Status"
	OpMoveShard  = "MoveShard"
	OpSplitShard = "SplitShard"
)

type OpRecord struct {
	StartedMs  int64  `json:"Started"`  // start time, in millis since epoch
	FinishedMs int64  `json:"Finished"` // end time, in millis since epoch
	NumDocs    int64  `json:"NumDocs"`
	IndexSize  int64  `json:"IndexSize"`
	Operation  string // one of the Op* constants
	Collection string
	Shard      string
	Replica    string // replica name (i.e. core_node09)
	SrcNode    string // instance name of source node (in the case of a split, this node has the parent)
	DstNode    string // instance name of destination node
	Requestor  string // Who requested the operation (either a user, or "solrman" if automation)
	Error      string // non-empty if the operation failed; always set on a Status op
	AsyncId    string // Async ID if present
}

func (r *OpRecord) String() string {
	switch r.Operation {
	case OpStatus:
		return fmt.Sprintf("status: %s", r.Error)
	case OpMoveShard:
		var replica string
		if r.Replica != "" {
			replica = "_" + r.Replica
		}
		return fmt.Sprintf("move %s_%s%s from %s to %s", r.Collection, r.Shard, replica, r.SrcNode, r.DstNode)
	case OpSplitShard:
		return fmt.Sprintf("split %s_%s", r.Collection, r.Shard)
	default:
		return fmt.Sprintf("unknown operation %q", r.Operation)
	}
}

func (r *OpRecord) Key() string {
	return fmt.Sprintf("SolrOp:%s:%s:%s", r.Collection, r.Shard, r.Replica)
}

type ByStartedRecently []OpRecord

func (a ByStartedRecently) Len() int           { return len(a) }
func (a ByStartedRecently) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByStartedRecently) Less(i, j int) bool { return a[i].StartedMs > a[j].StartedMs }

type ByFinishedRecently []OpRecord

func (a ByFinishedRecently) Len() int           { return len(a) }
func (a ByFinishedRecently) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByFinishedRecently) Less(i, j int) bool { return a[i].FinishedMs > a[j].FinishedMs }
<|endoftext|>


<commit_before>package efaceconv

import (
	"testing"
)

var (
	str = "string"
	sb  = []byte("slice of byte")
)

func TestEface2String(t *testing.T) {
	res, ok := Eface2String(str)
	if !ok {
		t.Error("Wrong type!")
	}
	if *res != str {
		t.Error("Not equal")
	}
	_, ok = Eface2String(ok)
	if ok {
		t.Error("Wrong type!")
	}
}

func BenchmarkEface2String(b *testing.B) {
	var v *string
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = Eface2String(str)
	}
	b.Log(*v, ok)

}

func classic(arg interface{}) (v string, ok bool) {
	v, ok = arg.(string)
	return v, ok
}

func BenchmarkClassic(b *testing.B) {
	var v string
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = classic(str)
	}
	b.Log(v, ok)
}

func BenchmarkEface2ByteSlice(b *testing.B) {
	var v *[]byte
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = Eface2ByteSlice(sb)
	}
	b.Log(*v, ok)
}

func sbClassic(arg interface{}) (v []byte, ok bool) {
	v, ok = arg.([]byte)
	return v, ok
}

func BenchmarkSBClassic(b *testing.B) {
	var v []byte
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = sbClassic(sb)
	}
	b.Log(v, ok)
}
<commit_msg>add tests for Eface2ByteSlice<commit_after>package efaceconv

import (
	"testing"
)

var (
	str = "string"
	sb  = []byte("slice of byte")
)

func TestEface2String(t *testing.T) {
	res, ok := Eface2String(str)
	if !ok {
		t.Error("Wrong type!")
	}
	if *res != str {
		t.Error("Not equal")
	}
	_, ok = Eface2String(ok)
	if ok {
		t.Error("Wrong type!")
	}
}

func TestEface2ByteSlice(t *testing.T) {
	res, ok := Eface2ByteSlice(sb)
	if !ok {
		t.Error("Wrong type!")
	}
	if len(*res) != len(sb) {
		t.Error("Not equal")
	}
	for i := range *res {
		if (*res)[i] != sb[i] {
			t.Error("Not equal")
		}
	}
	_, ok = Eface2ByteSlice(ok)
	if ok {
		t.Error("Wrong type!")
	}
}

func BenchmarkEface2String(b *testing.B) {
	var v *string
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = Eface2String(str)
	}
	b.Log(*v, ok)

}

func classic(arg interface{}) (v string, ok bool) {
	v, ok = arg.(string)
	return v, ok
}

func BenchmarkClassic(b *testing.B) {
	var v string
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = classic(str)
	}
	b.Log(v, ok)
}

func BenchmarkEface2ByteSlice(b *testing.B) {
	var v *[]byte
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = Eface2ByteSlice(sb)
	}
	b.Log(*v, ok)
}

func sbClassic(arg interface{}) (v []byte, ok bool) {
	v, ok = arg.([]byte)
	return v, ok
}

func BenchmarkSBClassic(b *testing.B) {
	var v []byte
	var ok bool
	for n := 0; n < b.N; n++ {
		v, ok = sbClassic(sb)
	}
	b.Log(v, ok)
}
<|endoftext|>


<commit_before>package marathon

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Event struct {
	EventType  string    `json:eventType`
	Timestamp  time.Time `json:timestamp`
	SlaveID    string    `json:slaveId`
	TaskID     string    `json:taskId`
	TaskStatus string    `json:taskStatus`
	AppID      string    `json:appId`
	Host       string    `json:host`
	Ports      []int     `json:ports`
	Version    string    `json:version`
}

type Listener struct {
	events       chan Event
	host         string
	internalPort string // Internal/external ports are relative
	externalPort string // to the container this process runs in.
}

func NewListener(host string, internalPort, externalPort string) *Listener {
	listener := &Listener{
		events:       make(chan Event),
		host:         host,
		internalPort: internalPort,
		externalPort: externalPort,
	}
	http.HandleFunc("/push-listener", listener.handler)
	go http.ListenAndServe(":"+internalPort, nil)

	return listener
}

func (l *Listener) handler(res http.ResponseWriter, req *http.Request) {
	decoder := json.NewDecoder(req.Body)

	var event Event
	if err := decoder.Decode(&event); err != nil {
		log.Fatal(err)
	}

	if event.EventType == "status_update_event" { // We only care about container change events
		l.events <- event
	}

	res.Write([]byte("Thanks.")) // Marathon ignores replies.  Just being polite.
}

func (l *Listener) Events() <-chan Event {
	return l.events
}

func (l *Listener) Subscribe(marathonHost string) error {
	marathonURL := url.URL{Scheme: "http", Host: marathonHost, Path: "/v2/eventSubscriptions"}
	q := marathonURL.Query()
	q.Set("callbackUrl", fmt.Sprintf("http://%s:%s/push-listener", l.host, l.externalPort))
	marathonURL.RawQuery = q.Encode()

	res, err := http.Post(marathonURL.String(), "application/json", strings.NewReader(""))
	if err != nil {
		return err
	}
	defer res.Body.Close()

	var data map[string]interface{}
	decoder := json.NewDecoder(res.Body)
	if err := decoder.Decode(&data); err != nil {
		return err
	}

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("Bad status code while subscribing to marathon events: " + res.Status)
	}

	return nil
}
<commit_msg>Moved status check to above body parsing.<commit_after>package marathon

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Event struct {
	EventType  string    `json:eventType`
	Timestamp  time.Time `json:timestamp`
	SlaveID    string    `json:slaveId`
	TaskID     string    `json:taskId`
	TaskStatus string    `json:taskStatus`
	AppID      string    `json:appId`
	Host       string    `json:host`
	Ports      []int     `json:ports`
	Version    string    `json:version`
}

type Listener struct {
	events       chan Event
	host         string
	internalPort string // Internal/external ports are relative
	externalPort string // to the container this process runs in.
}

func NewListener(host string, internalPort, externalPort string) *Listener {
	listener := &Listener{
		events:       make(chan Event),
		host:         host,
		internalPort: internalPort,
		externalPort: externalPort,
	}
	http.HandleFunc("/push-listener", listener.handler)
	go http.ListenAndServe(":"+internalPort, nil)

	return listener
}

func (l *Listener) handler(res http.ResponseWriter, req *http.Request) {
	decoder := json.NewDecoder(req.Body)

	var event Event
	if err := decoder.Decode(&event); err != nil {
		log.Fatal(err)
	}

	if event.EventType == "status_update_event" { // We only care about container change events
		l.events <- event
	}

	res.Write([]byte("Thanks.")) // Marathon ignores replies.  Just being polite.
}

func (l *Listener) Events() <-chan Event {
	return l.events
}

func (l *Listener) Subscribe(marathonHost string) error {
	marathonURL := url.URL{Scheme: "http", Host: marathonHost, Path: "/v2/eventSubscriptions"}
	q := marathonURL.Query()
	q.Set("callbackUrl", fmt.Sprintf("http://%s:%s/push-listener", l.host, l.externalPort))
	marathonURL.RawQuery = q.Encode()

	res, err := http.Post(marathonURL.String(), "application/json", strings.NewReader(""))
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("Bad status code while subscribing to marathon events: " + res.Status)
	}

	var data map[string]interface{}
	decoder := json.NewDecoder(res.Body)
	if err := decoder.Decode(&data); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package replay

import (
	"encoding/json"
	"github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
	"log"
	"net/http"
	"strconv"
	"time"
)

type ESPlugin struct {
	Active  bool
	ApiPort int
	Host    string
	Index   string
	indexor *core.BulkIndexor
	done    chan bool
}

type ESRequestResponse struct {
	ReqUrl               string         `json:"Req_URL"`
	ReqMethod            string         `json:"Req_Method"`
	ReqUserAgent         string         `json:"Req_User-Agent"`
	ReqAcceptLanguage    string         `json:"Req_Accept-Language,omitempty"`
	ReqAccept            string         `json:"Req_Accept,omitempty"`
	ReqAcceptEncoding    string         `json:"Req_Accept-Encoding,omitempty"`
	ReqIfModifiedSince   string         `json:"Req_If-Modified-Since,omitempty"`
	ReqConnection        string         `json:"Req_Connection,omitempty"`
	ReqCookies           []*http.Cookie `json:"Req_Cookies,omitempty"`
	RespStatus           string         `json:"Resp_Status"`
	RespStatusCode       int            `json:"Resp_Status-Code"`
	RespProto            string         `json:"Resp_Proto,omitempty"`
	RespContentLength    int64          `json:"Resp_Content-Length,omitempty"`
	RespContentType      string         `json:"Resp_Content-Type,omitempty"`
	RespTransferEncoding []string       `json:"Resp_Transfer-Encoding,omitempty"`
	RespContentEncoding  string         `json:"Resp_Content-Encoding,omitempty"`
	RespExpires          string         `json:"Resp_Expires,omitempty"`
	RespCacheControl     string         `json:"Resp_Cache-Control,omitempty"`
	RespVary             string         `json:"Resp_Vary,omitempty"`
	RespSetCookie        string         `json:"Resp_Set-Cookie,omitempty"`
	Rtt                  int64          `json:"RTT"`
	Timestamp            time.Time
}

func (p *ESPlugin) Init() {
	// Start the Handler go routine
	api.Domain = p.Host
	api.Port = strconv.Itoa(p.ApiPort)
	p.indexor = core.NewBulkIndexorErrors(50, 60)
	p.done = make(chan bool)
	p.indexor.Run(p.done)
	if Settings.Verbose {
		// Only start the ErrorHandler goroutine when in verbose mode
		// no need to burn ressources otherwise
		go p.ErrorHandler()
	}
	log.Println("Initialized Elasticsearch Plugin")
	return
}

func (p *ESPlugin) IndexerShutdown() {
	p.done <- true
	return
}

func (p *ESPlugin) ErrorHandler() {
	for {
		errBuf := <-p.indexor.ErrorChannel
		log.Println(errBuf.Err)
	}
}

func (p *ESPlugin) RttDurationToMs(d time.Duration) int64 {
	sec := d / time.Second
	nsec := d % time.Second
	fl := float64(sec) + float64(nsec)*1e-6
	return int64(fl)
}

func (p *ESPlugin) ResponseAnalyze(r *HttpResponse) {
	t := time.Now()
	rtt := p.RttDurationToMs(r.timing.respDone.Sub(r.timing.reqStart))

	resp := ESRequestResponse{
		ReqUrl:               r.req.URL.String(),
		ReqMethod:            r.req.Method,
		ReqUserAgent:         r.req.UserAgent(),
		ReqAcceptLanguage:    r.req.Header.Get("Accept-Language"),
		ReqAccept:            r.req.Header.Get("Accept"),
		ReqAcceptEncoding:    r.req.Header.Get("Accept-Encoding"),
		ReqIfModifiedSince:   r.req.Header.Get("If-Modified-Since"),
		ReqConnection:        r.req.Header.Get("Connection"),
		ReqCookies:           r.req.Cookies(),
		RespStatus:           r.resp.Status,
		RespStatusCode:       r.resp.StatusCode,
		RespProto:            r.resp.Proto,
		RespContentLength:    r.resp.ContentLength,
		RespContentType:      r.resp.Header.Get("Content-Type"),
		RespTransferEncoding: r.resp.TransferEncoding,
		RespContentEncoding:  r.resp.Header.Get("Content-Encoding"),
		RespExpires:          r.resp.Header.Get("Expires"),
		RespCacheControl:     r.resp.Header.Get("Cache-Control"),
		RespVary:             r.resp.Header.Get("Vary"),
		RespSetCookie:        r.resp.Header.Get("Set-Cookie"),
		Rtt:                  rtt,
		Timestamp:            t,
	}
	j, err := json.Marshal(&resp)
	if err != nil {
		log.Println(err)
	} else {
		if Settings.Verbose {
			log.Printf("Elasticsearch - Response to Index: %s", j)
		}
		p.indexor.Index(p.Index, "RequestResponse", "", "", &t, j)
	}
	return
}
<commit_msg>Fixed elasicsearch plugin<commit_after>package replay

import (
	"encoding/json"
	"github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
	"log"
	"net/http"
	"strconv"
	"time"
)

type ESPlugin struct {
	Active  bool
	ApiPort int
	Host    string
	Index   string
	indexor *core.BulkIndexor
	done    chan bool
}

type ESRequestResponse struct {
	ReqUrl               string         `json:"Req_URL"`
	ReqMethod            string         `json:"Req_Method"`
	ReqUserAgent         string         `json:"Req_User-Agent"`
	ReqAcceptLanguage    string         `json:"Req_Accept-Language,omitempty"`
	ReqAccept            string         `json:"Req_Accept,omitempty"`
	ReqAcceptEncoding    string         `json:"Req_Accept-Encoding,omitempty"`
	ReqIfModifiedSince   string         `json:"Req_If-Modified-Since,omitempty"`
	ReqConnection        string         `json:"Req_Connection,omitempty"`
	ReqCookies           []*http.Cookie `json:"Req_Cookies,omitempty"`
	RespStatus           string         `json:"Resp_Status"`
	RespStatusCode       int            `json:"Resp_Status-Code"`
	RespProto            string         `json:"Resp_Proto,omitempty"`
	RespContentLength    int64          `json:"Resp_Content-Length,omitempty"`
	RespContentType      string         `json:"Resp_Content-Type,omitempty"`
	RespTransferEncoding []string       `json:"Resp_Transfer-Encoding,omitempty"`
	RespContentEncoding  string         `json:"Resp_Content-Encoding,omitempty"`
	RespExpires          string         `json:"Resp_Expires,omitempty"`
	RespCacheControl     string         `json:"Resp_Cache-Control,omitempty"`
	RespVary             string         `json:"Resp_Vary,omitempty"`
	RespSetCookie        string         `json:"Resp_Set-Cookie,omitempty"`
	Rtt                  int64          `json:"RTT"`
	Timestamp            time.Time
}

func (p *ESPlugin) Init() {
	// Start the Handler go routine
	api.Domain = p.Host
	api.Port = strconv.Itoa(p.ApiPort)
	p.indexor = core.NewBulkIndexorErrors(50, 60)
	p.done = make(chan bool)
	p.indexor.Run(p.done)
	if Settings.Verbose {
		// Only start the ErrorHandler goroutine when in verbose mode
		// no need to burn ressources otherwise
		go p.ErrorHandler()
	}
	log.Println("Initialized Elasticsearch Plugin")
	return
}

func (p *ESPlugin) IndexerShutdown() {
	p.done <- true
	return
}

func (p *ESPlugin) ErrorHandler() {
	for {
		errBuf := <-p.indexor.ErrorChannel
		log.Println(errBuf.Err)
	}
}

func (p *ESPlugin) RttDurationToMs(d time.Duration) int64 {
	sec := d / time.Second
	nsec := d % time.Second
	fl := float64(sec) + float64(nsec)*1e-6
	return int64(fl)
}

func (p *ESPlugin) ResponseAnalyze(r *HttpResponse) {
	if r.resp == nil {
		Debug("nil http response - skipped elasticsearch export for this request")
		return
	}
	t := time.Now()
	rtt := p.RttDurationToMs(r.timing.respDone.Sub(r.timing.reqStart))

	resp := ESRequestResponse{
		ReqUrl:               r.req.URL.String(),
		ReqMethod:            r.req.Method,
		ReqUserAgent:         r.req.UserAgent(),
		ReqAcceptLanguage:    r.req.Header.Get("Accept-Language"),
		ReqAccept:            r.req.Header.Get("Accept"),
		ReqAcceptEncoding:    r.req.Header.Get("Accept-Encoding"),
		ReqIfModifiedSince:   r.req.Header.Get("If-Modified-Since"),
		ReqConnection:        r.req.Header.Get("Connection"),
		ReqCookies:           r.req.Cookies(),
		RespStatus:           r.resp.Status,
		RespStatusCode:       r.resp.StatusCode,
		RespProto:            r.resp.Proto,
		RespContentLength:    r.resp.ContentLength,
		RespContentType:      r.resp.Header.Get("Content-Type"),
		RespTransferEncoding: r.resp.TransferEncoding,
		RespContentEncoding:  r.resp.Header.Get("Content-Encoding"),
		RespExpires:          r.resp.Header.Get("Expires"),
		RespCacheControl:     r.resp.Header.Get("Cache-Control"),
		RespVary:             r.resp.Header.Get("Vary"),
		RespSetCookie:        r.resp.Header.Get("Set-Cookie"),
		Rtt:                  rtt,
		Timestamp:            t,
	}
	j, err := json.Marshal(&resp)
	if err != nil {
		log.Println(err)
	} else {
		if Settings.Verbose {
			log.Printf("Elasticsearch - Response to Index: %s", j)
		}
		p.indexor.Index(p.Index, "RequestResponse", "", "", &t, j)
	}
	return
}
<|endoftext|>


<commit_before>// Peano integers are represented by a linked
// list whose nodes contain no data
// (the nodes are the data).
// http://en.wikipedia.org/wiki/Peano_axioms

// This program demonstrates the effectiveness
// of the Go runtime's dynamically growing
// stacks for heavily recursive computations.

package main

import "fmt"

// Number is a pointer to a Number
type Number *Number

// The arithmetic value of a Number is the
// count of the nodes comprising the list.
// (See the count function below.)

// -------------------------------------
// Peano primitives

func zero() *Number {
	return nil
}

func isZero(x *Number) bool {
	return x == nil
}

func add1(x *Number) *Number {
	e := new(Number)
	*e = x
	return e
}

func sub1(x *Number) *Number {
	return *x
}

func add(x, y *Number) *Number {
	if isZero(y) {
		return x
	}
	return add(add1(x), sub1(y))
}

func mul(x, y *Number) *Number {
	if isZero(x) || isZero(y) {
		return zero()
	}
	return add(mul(x, sub1(y)), x)
}

func fact(n *Number) *Number {
	if isZero(n) {
		return add1(zero())
	}
	return mul(fact(sub1(n)), n)
}

// -------------------------------------
// Helpers to generate/count Peano integers

func gen(n int) *Number {
	if n > 0 {
		return add1(gen(n - 1))
	}
	return zero()
}

func count(x *Number) int {
	if isZero(x) {
		return 0
	}
	return count(sub1(x)) + 1
}

// -------------------------------------
// Print i! for i in [0,9]

func main() {
	for i := 0; i <= 9; i++ {
		f := count(fact(gen(i)))
		fmt.Println(i, "! =", f)
	}
}
<commit_msg>doc/play: revise peano.go comment again.<commit_after>// Peano integers are represented by a linked
// list whose nodes contain no data
// (the nodes are the data).
// http://en.wikipedia.org/wiki/Peano_axioms

// This program demonstrates that Go's automatic
// stack management can handle heavily recursive
// computations.

package main

import "fmt"

// Number is a pointer to a Number
type Number *Number

// The arithmetic value of a Number is the
// count of the nodes comprising the list.
// (See the count function below.)

// -------------------------------------
// Peano primitives

func zero() *Number {
	return nil
}

func isZero(x *Number) bool {
	return x == nil
}

func add1(x *Number) *Number {
	e := new(Number)
	*e = x
	return e
}

func sub1(x *Number) *Number {
	return *x
}

func add(x, y *Number) *Number {
	if isZero(y) {
		return x
	}
	return add(add1(x), sub1(y))
}

func mul(x, y *Number) *Number {
	if isZero(x) || isZero(y) {
		return zero()
	}
	return add(mul(x, sub1(y)), x)
}

func fact(n *Number) *Number {
	if isZero(n) {
		return add1(zero())
	}
	return mul(fact(sub1(n)), n)
}

// -------------------------------------
// Helpers to generate/count Peano integers

func gen(n int) *Number {
	if n > 0 {
		return add1(gen(n - 1))
	}
	return zero()
}

func count(x *Number) int {
	if isZero(x) {
		return 0
	}
	return count(sub1(x)) + 1
}

// -------------------------------------
// Print i! for i in [0,9]

func main() {
	for i := 0; i <= 9; i++ {
		f := count(fact(gen(i)))
		fmt.Println(i, "! =", f)
	}
}
<|endoftext|>


<commit_before>package uuid

import (
	"database/sql/driver"
	"fmt"
	"reflect"
)

// ErrInvalidType occurs when *UUID.Scan() does not receive a string.
type ErrInvalidType struct {
	Type reflect.Type
}

func (e ErrInvalidType) Error() string {
	return fmt.Sprintf("uuid Scan(): invalid type '%s', expected string.", e.Type.String())
}

// Scan scans a uuid from the given interface instance.
// If scanning fails the state of the UUID is undetermined.
func (u *UUID) Scan(val interface{}) error {
	if s, ok := val.(string); ok {
		return u.SetString(s)
	}
	if b, ok := val.([]byte); ok {
		return u.ReadBytes(b)
	}

	return &ErrInvalidType{reflect.TypeOf(val)}
}

// Value gives the database driver representation of the UUID.
func (u UUID) Value() (driver.Value, error) {
	// The return here causes a second allocation because of the driver.Value interface{} box
	return u.String(), nil
}

// Scan scans a uuid or null from the given value.
// If the supplied value is nil, Valid will be set to false and the
// UUID will be zeroed.
func (nu *NullUUID) Scan(val interface{}) error {
	if val == nil {
		nu.UUID, nu.Valid = [16]byte{}, false

		return nil
	}

	nu.Valid = true

	return nu.UUID.Scan(val)
}

// Value gives the database driver representation of the UUID or NULL.
func (nu NullUUID) Value() (driver.Value, error) {
	if !nu.Valid {
		return nil, nil
	}

	// The return here causes a second allocation because of the driver.Value interface{} box
	return nu.UUID.String(), nil
}
<commit_msg>Handle nil Type in ErrInvalidType.Error.<commit_after>package uuid

import (
	"database/sql/driver"
	"fmt"
	"reflect"
)

// ErrInvalidType occurs when *UUID.Scan() does not receive a string.
type ErrInvalidType struct {
	Type reflect.Type
}

func (e ErrInvalidType) Error() string {
	t := "<nil>"
	if e.Type != nil {
		t = e.Type.String()
	}
	return fmt.Sprintf("uuid Scan(): invalid type '%s', expected string.", t)
}

// Scan scans a uuid from the given interface instance.
// If scanning fails the state of the UUID is undetermined.
func (u *UUID) Scan(val interface{}) error {
	if s, ok := val.(string); ok {
		return u.SetString(s)
	}
	if b, ok := val.([]byte); ok {
		return u.ReadBytes(b)
	}

	return &ErrInvalidType{reflect.TypeOf(val)}
}

// Value gives the database driver representation of the UUID.
func (u UUID) Value() (driver.Value, error) {
	// The return here causes a second allocation because of the driver.Value interface{} box
	return u.String(), nil
}

// Scan scans a uuid or null from the given value.
// If the supplied value is nil, Valid will be set to false and the
// UUID will be zeroed.
func (nu *NullUUID) Scan(val interface{}) error {
	if val == nil {
		nu.UUID, nu.Valid = [16]byte{}, false

		return nil
	}

	nu.Valid = true

	return nu.UUID.Scan(val)
}

// Value gives the database driver representation of the UUID or NULL.
func (nu NullUUID) Value() (driver.Value, error) {
	if !nu.Valid {
		return nil, nil
	}

	// The return here causes a second allocation because of the driver.Value interface{} box
	return nu.UUID.String(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/lib/pq"
)

// initDB connects to the DB and creates the tables if they don't exist
func initDB() *sql.DB {
	// Connect to DB
	dbInfo := os.Getenv("DATABASE_URL")
	if dbInfo == "" {
		dbInfo = "host=localhost port=5432 user=dev password=dev dbname=chat_dev sslmode=disable"
	}

	db, err := sql.Open("postgres", dbInfo)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	err = db.Ping()
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	log.Println("Connected to DB.")

	// Create tables if not exists
	_, err = db.Exec("CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"")
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
	_, err = db.Exec(`
		CREATE TABLE IF NOT EXISTS messages (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			user_id VARCHAR(255),
			user_name VARCHAR(255),
			user_avatar VARCHAR(255),
			type VARCHAR(255),
			content TEXT,
			date_post TIMESTAMP
		)
	`)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	log.Println("Tables created or already existing.")

	// All good!
	return db
}

// insertMessage inserts a single message into the database
// and returns either the id or an error
func insertMessage(db *sql.DB, msg Message) (string, error) {
	stmt := `
		INSERT INTO messages (
			user_id,
			user_name,
			user_avatar,
			type,
			content,
			date_post
		)
		VALUES ($1, $2, $3, $4, $5, $6)
		RETURNING id
	`

	var id string

	err := db.QueryRow(stmt, msg.UserID, msg.UserName, msg.UserAvatar, msg.Type, msg.Content, msg.Date).Scan(&id)

	if err != nil {
		return "", err
	}
	return id, nil
}

func selectPreviousMessage(db *sql.DB, userID string) (*sql.Rows, error) {
	stmt := `
		(SELECT *
		FROM messages
		WHERE type = 'message'
		LIMIT 10)

		UNION

		(SELECT *
		FROM messages
		WHERE type = 'message'
			AND user_id != $1
			AND date_post > (
				SELECT date_post
				FROM messages
				WHERE type = 'notice'
					AND content = 'logout'
					AND user_id = $1
				ORDER BY date_post DESC
				LIMIT 1
			)
		)
	`

	rows, err := db.Query(stmt, userID)
	if err != nil {
		return &sql.Rows{}, err
	}
	return rows, nil
}
<commit_msg>Correctly select last 10 messages<commit_after>package main

import (
	"database/sql"
	"log"
	"os"

	_ "github.com/lib/pq"
)

// initDB connects to the DB and creates the tables if they don't exist
func initDB() *sql.DB {
	// Connect to DB
	dbInfo := os.Getenv("DATABASE_URL")
	if dbInfo == "" {
		dbInfo = "host=localhost port=5432 user=dev password=dev dbname=chat_dev sslmode=disable"
	}

	db, err := sql.Open("postgres", dbInfo)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	err = db.Ping()
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	log.Println("Connected to DB.")

	// Create tables if not exists
	_, err = db.Exec("CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"")
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
	_, err = db.Exec(`
		CREATE TABLE IF NOT EXISTS messages (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			user_id VARCHAR(255),
			user_name VARCHAR(255),
			user_avatar VARCHAR(255),
			type VARCHAR(255),
			content TEXT,
			date_post TIMESTAMP
		)
	`)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	log.Println("Tables created or already existing.")

	// All good!
	return db
}

// insertMessage inserts a single message into the database
// and returns either the id or an error
func insertMessage(db *sql.DB, msg Message) (string, error) {
	stmt := `
		INSERT INTO messages (
			user_id,
			user_name,
			user_avatar,
			type,
			content,
			date_post
		)
		VALUES ($1, $2, $3, $4, $5, $6)
		RETURNING id
	`

	var id string

	err := db.QueryRow(stmt, msg.UserID, msg.UserName, msg.UserAvatar, msg.Type, msg.Content, msg.Date).Scan(&id)

	if err != nil {
		return "", err
	}
	return id, nil
}

func selectPreviousMessage(db *sql.DB, userID string) (*sql.Rows, error) {
	stmt := `
		(SELECT *
		FROM messages
		WHERE type = 'message'
		ORDER BY date_post DESC
		LIMIT 10)

		UNION

		(SELECT *
		FROM messages
		WHERE type = 'message'
			AND user_id != $1
			AND date_post > (
				SELECT date_post
				FROM messages
				WHERE type = 'notice'
					AND content = 'logout'
					AND user_id = $1
				ORDER BY date_post DESC
				LIMIT 1
			)
		)
	`

	rows, err := db.Query(stmt, userID)
	if err != nil {
		return &sql.Rows{}, err
	}
	return rows, nil
}
<|endoftext|>


<commit_before>package muta

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/leeola/muta/logging"
)

type SrcOpts struct {
	Name     string
	ReadSize uint
}

func Src(srcs ...string) *Stream {
	s := &Stream{}
	return s.Pipe(SrcStreamer(srcs, SrcOpts{}))
}

func SrcStreamer(ps []string, opts SrcOpts) Streamer {
	if opts.Name == "" {
		opts.Name = "muta.Src"
	}
	if opts.ReadSize == 0 {
		opts.ReadSize = 50
	}

	// Setup our channels
	fi := make(chan *FileInfo)
	chunk := make(chan []byte)
	err := make(chan error)
	read := make(chan bool)

	// This method of reading files needs to be abstracted further
	// to ensure that the file closing is deferred. In this
	// implementation i can't think of a way to test that.
	// Also, moving it out would let us ensure closing of the files
	// in tests
	go func() {
		sendErr := func(_fi *FileInfo, _chunk []byte, _err error) {
			<-read
			fi <- _fi
			chunk <- _chunk
			err <- _err
		}

		loadFile := func(p string) error {
			pchunks := make([]byte, opts.ReadSize)
			pfi := NewFileInfo(p)

			f, ferr := os.Open(p)
			defer f.Close()
			if ferr != nil {
				sendErr(pfi, nil, ferr)
				return ferr
			}

			// Wait for a read request
			for <-read {
				// Read
				count, ferr := f.Read(pchunks)
				if ferr != nil && ferr == io.EOF {
					break
				}

				// Send
				fi <- pfi
				chunk <- pchunks[0:count]
				err <- ferr
				if ferr != nil {
					return ferr
				}
			}

			// The for loop stopped, send EOF
			fi <- pfi
			chunk <- nil
			err <- nil
			return nil
		}

		// Go through the paths and globbify any globbed paths
		globbedPaths := []string{}
		for _, p := range ps {
			// If it hs a *, it is a glob
			if strings.Contains(p, "*") {
				expandedGlobs, err := filepath.Glob(p)
				if err != nil {
					sendErr(nil, nil, err)
					return
				}
				globbedPaths = append(globbedPaths, expandedGlobs...)
			} else {
				globbedPaths = append(globbedPaths, p)
			}
		}

		for _, p := range globbedPaths {
			logging.Debug([]string{opts.Name}, "Opening", p)
			err := loadFile(p)
			if err != nil {
				return
			}
		}

		<-read
		// send EOS
		fi <- nil
		chunk <- nil
		err <- nil
	}()

	return NewEasyStreamer(opts.Name, func(inFi *FileInfo,
		inC []byte) (*FileInfo, []byte, error) {
		// If there is an incoming file pass the data along unmodified. This
		// func doesn't care to modify the data in any way
		if inFi != nil {
			return inFi, inC, nil
		}

		read <- true
		return <-fi, <-chunk, <-err
	})
}
<commit_msg>Changed debug wording<commit_after>package muta

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/leeola/muta/logging"
)

type SrcOpts struct {
	Name     string
	ReadSize uint
}

func Src(srcs ...string) *Stream {
	s := &Stream{}
	return s.Pipe(SrcStreamer(srcs, SrcOpts{}))
}

func SrcStreamer(ps []string, opts SrcOpts) Streamer {
	if opts.Name == "" {
		opts.Name = "muta.Src"
	}
	if opts.ReadSize == 0 {
		opts.ReadSize = 50
	}

	// Setup our channels
	fi := make(chan *FileInfo)
	chunk := make(chan []byte)
	err := make(chan error)
	read := make(chan bool)

	// This method of reading files needs to be abstracted further
	// to ensure that the file closing is deferred. In this
	// implementation i can't think of a way to test that.
	// Also, moving it out would let us ensure closing of the files
	// in tests
	go func() {
		sendErr := func(_fi *FileInfo, _chunk []byte, _err error) {
			<-read
			fi <- _fi
			chunk <- _chunk
			err <- _err
		}

		loadFile := func(p string) error {
			pchunks := make([]byte, opts.ReadSize)
			pfi := NewFileInfo(p)

			f, ferr := os.Open(p)
			defer f.Close()
			if ferr != nil {
				sendErr(pfi, nil, ferr)
				return ferr
			}

			// Wait for a read request
			for <-read {
				// Read
				count, ferr := f.Read(pchunks)
				if ferr != nil && ferr == io.EOF {
					break
				}

				// Send
				fi <- pfi
				chunk <- pchunks[0:count]
				err <- ferr
				if ferr != nil {
					return ferr
				}
			}

			// The for loop stopped, send EOF
			fi <- pfi
			chunk <- nil
			err <- nil
			return nil
		}

		// Go through the paths and globbify any globbed paths
		globbedPaths := []string{}
		for _, p := range ps {
			// If it hs a *, it is a glob
			if strings.Contains(p, "*") {
				expandedGlobs, err := filepath.Glob(p)
				if err != nil {
					sendErr(nil, nil, err)
					return
				}
				globbedPaths = append(globbedPaths, expandedGlobs...)
			} else {
				globbedPaths = append(globbedPaths, p)
			}
		}

		for _, p := range globbedPaths {
			logging.Debug([]string{opts.Name}, "Reading", p)
			err := loadFile(p)
			if err != nil {
				return
			}
		}

		<-read
		// send EOS
		fi <- nil
		chunk <- nil
		err <- nil
	}()

	return NewEasyStreamer(opts.Name, func(inFi *FileInfo,
		inC []byte) (*FileInfo, []byte, error) {
		// If there is an incoming file pass the data along unmodified. This
		// func doesn't care to modify the data in any way
		if inFi != nil {
			return inFi, inC, nil
		}

		read <- true
		return <-fi, <-chunk, <-err
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// SSHClient is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	User         string
	Host         string
	Agent        net.Conn
	Conn         *ssh.Client
	Sess         *ssh.Session
	RemoteStdin  io.WriteCloser
	RemoteStdout io.Reader
	RemoteStderr io.Reader
	//TODO: Use Session RequestPty, Shell() and Session.Env()
	//Env      map[string]string
	Env        string //export FOO="bar"; export BAR="baz";
	ConnOpened bool
	SessOpened bool
	Running    bool
	Prefix     string
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.Host = host

	// Remove extra "ssh://" schema
	if c.Host[:6] == "ssh://" {
		c.Host = c.Host[6:]
	}

	if at := strings.Index(c.Host, "@"); at != -1 {
		c.User = c.Host[:at]
		c.Host = c.Host[at+1:]
	}

	// Add default user, if not set
	if c.User == "" {
		c.User = os.Getenv("USER")
	}

	if strings.Index(c.Host, "/") != -1 {
		return ErrConnect{c.User, c.Host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.Host, ":") == -1 {
		c.Host += ":22"
	}

	return nil
}

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	if c.ConnOpened {
		return fmt.Errorf("Already connected")
	}

	if err := c.parseHost(host); err != nil {
		return err
	}

	// TODO: add the keys from ~/ssh/config ..
	// Look for IdentityFiles .. etc...

	var signers []ssh.Signer

	// If there's a running SSH Agent, use its Private keys
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		agentSigners, err := agent.Signers()
		if err == nil && len(agentSigners) > 0 {
			signers = append(signers, agentSigners...)
		}
	}

	config := &ssh.ClientConfig{
		User: c.User,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signers...),
		},
	}

	c.Conn, err = ssh.Dial("tcp", c.Host, config)
	if err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}
	c.ConnOpened = true

	return nil
}

// reconnect creates new session for the SSH connection.
func (c *SSHClient) reconnect() error {
	if c.SessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.Conn.NewSession()
	if err != nil {
		return err
	}

	c.RemoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.RemoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	c.RemoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.Sess = sess
	c.SessOpened = true
	return nil
}

// Run runs the cmd.Run command remotely on cmd.Host.
func (c *SSHClient) Run(cmd Command) error {
	if c.Running {
		return fmt.Errorf("Session already running")
	}

	// Reconnect session.
	if err := c.reconnect(); err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}

	// Start the remote command.
	if err := c.Sess.Start(c.Env + "echo '+++ Running `" + cmd.Run + "`';" + cmd.Run); err != nil {
		return ErrCmd{cmd, err.Error()}
	}

	c.Running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.Running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.Sess.Wait()
	c.Sess.Close()
	c.Running = false
	c.SessOpened = false

	return err
}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.SessOpened {
		c.Sess.Close()
		c.SessOpened = false
	}
	if !c.ConnOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.Conn.Close()
	c.ConnOpened = false

	return err
}
<commit_msg>Be verbose, set -x by default<commit_after>package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// SSHClient is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	User         string
	Host         string
	Agent        net.Conn
	Conn         *ssh.Client
	Sess         *ssh.Session
	RemoteStdin  io.WriteCloser
	RemoteStdout io.Reader
	RemoteStderr io.Reader
	//TODO: Use Session RequestPty, Shell() and Session.Env()
	//Env      map[string]string
	Env        string //export FOO="bar"; export BAR="baz";
	ConnOpened bool
	SessOpened bool
	Running    bool
	Prefix     string
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.Host = host

	// Remove extra "ssh://" schema
	if c.Host[:6] == "ssh://" {
		c.Host = c.Host[6:]
	}

	if at := strings.Index(c.Host, "@"); at != -1 {
		c.User = c.Host[:at]
		c.Host = c.Host[at+1:]
	}

	// Add default user, if not set
	if c.User == "" {
		c.User = os.Getenv("USER")
	}

	if strings.Index(c.Host, "/") != -1 {
		return ErrConnect{c.User, c.Host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.Host, ":") == -1 {
		c.Host += ":22"
	}

	return nil
}

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	if c.ConnOpened {
		return fmt.Errorf("Already connected")
	}

	if err := c.parseHost(host); err != nil {
		return err
	}

	// TODO: add the keys from ~/ssh/config ..
	// Look for IdentityFiles .. etc...

	var signers []ssh.Signer

	// If there's a running SSH Agent, use its Private keys
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		agentSigners, err := agent.Signers()
		if err == nil && len(agentSigners) > 0 {
			signers = append(signers, agentSigners...)
		}
	}

	config := &ssh.ClientConfig{
		User: c.User,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signers...),
		},
	}

	c.Conn, err = ssh.Dial("tcp", c.Host, config)
	if err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}
	c.ConnOpened = true

	return nil
}

// reconnect creates new session for the SSH connection.
func (c *SSHClient) reconnect() error {
	if c.SessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.Conn.NewSession()
	if err != nil {
		return err
	}

	c.RemoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.RemoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	c.RemoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.Sess = sess
	c.SessOpened = true
	return nil
}

// Run runs the cmd.Run command remotely on cmd.Host.
func (c *SSHClient) Run(cmd Command) error {
	if c.Running {
		return fmt.Errorf("Session already running")
	}

	// Reconnect session.
	if err := c.reconnect(); err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}

	// Start the remote command.
	if err := c.Sess.Start(c.Env + "set -x;" + cmd.Run); err != nil {
		return ErrCmd{cmd, err.Error()}
	}

	c.Running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.Running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.Sess.Wait()
	c.Sess.Close()
	c.Running = false
	c.SessOpened = false

	return err
}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.SessOpened {
		c.Sess.Close()
		c.SessOpened = false
	}
	if !c.ConnOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.Conn.Close()
	c.ConnOpened = false

	return err
}
<|endoftext|>