<commit_before>package api

import (
	"encoding/json"
	"fmt"
	"github.com/golang/glog"
	"github.com/nais/naisd/api/app"
	"github.com/nais/naisd/api/naisrequest"
	ver "github.com/nais/naisd/api/version"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"goji.io"
	"goji.io/pat"
	"io"
	"io/ioutil"
	"k8s.io/client-go/kubernetes"
	"net/http"
	"net/url"
	"os"
)

type Api struct {
	Clientset              kubernetes.Interface
	FasitURL               string
	ClusterSubdomain       string
	ClusterName            string
	IstioEnabled           bool
	AuthenticationEnabled  bool
	DeploymentStatusViewer DeploymentStatusViewer
}

var (
	clientID     = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_APP_ID")
	clientSecret = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_PASSWORD")
	tenantID     = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_TENANT")
)

type AppError interface {
	error
	Code() int
}

type appError struct {
	OriginalError error
	Message       string
	StatusCode    int
}

func (e appError) Code() int {
	return e.StatusCode
}
func (e appError) Error() string {
	if e.OriginalError != nil {
		return fmt.Sprintf("%s: %s (%d)", e.Message, e.OriginalError.Error(), e.StatusCode)
	}

	return fmt.Sprintf("%s (%d)", e.Message, e.StatusCode)
}

type appHandler func(w http.ResponseWriter, r *http.Request) *appError

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *appError, not os.Error.
		glog.Errorf(e.Error())
		http.Error(w, e.Error(), e.StatusCode)
	}
}

var (
	requests = prometheus.NewCounterVec(
		prometheus.CounterOpts{Name: "requests", Help: "requests pr path"}, []string{"path"},
	)
	deploys = prometheus.NewCounterVec(
		prometheus.CounterOpts{Name: "deployments", Help: "deployments done by NaisD"}, []string{"nais_app"},
	)
)

func init() {
	prometheus.MustRegister(requests)
	prometheus.MustRegister(deploys)
}

func (api Api) Handler() http.Handler {
	mux := goji.NewMux()

	mux.Handle(pat.Get("/isalive"), appHandler(api.isAlive))
	mux.Handle(pat.Post("/deploy"), appHandler(api.deploy))
	mux.Handle(pat.Get("/metrics"), promhttp.Handler())
	mux.Handle(pat.Get("/version"), appHandler(api.version))
	mux.Handle(pat.Get("/deploystatus/:namespace/:deployName"), appHandler(api.deploymentStatusHandler))
	mux.Handle(pat.Delete("/app/:namespace/:deployName"), appHandler(api.deleteApplication))
	return mux
}

func NewApi(clientset kubernetes.Interface, fasitUrl, clusterDomain, clusterName string, istioEnabled bool, authenticationEnabled bool, d DeploymentStatusViewer) Api {
	return Api{
		Clientset:              clientset,
		FasitURL:               fasitUrl,
		ClusterSubdomain:       clusterDomain,
		ClusterName:            clusterName,
		IstioEnabled:           istioEnabled,
		AuthenticationEnabled:  authenticationEnabled,
		DeploymentStatusViewer: d,
	}
}
func authenticate(username, password string) *appError {
	authURL := fmt.Sprintf("https://login.microsoftonline.com/authority/%s/oauth2/token", tenantID)
	content := url.Values{}
	content.Add("grant_type", "password")
	content.Add("username", username)
	content.Add("password", password)
	content.Add("client_id", clientID)
	content.Add("client_secret", clientSecret)
	content.Add("resource", clientID)

	res, err := http.PostForm(authURL, content)
	if err != nil {
		return &appError{err, "Failed during authentication", 500}
	}
	if res.StatusCode != 200 {
		return &appError{err, "Authentication failure", 401}
	}
	return nil
}

func (api Api) deploy(w http.ResponseWriter, r *http.Request) *appError {
	requests.With(prometheus.Labels{"path": "deploy"}).Inc()
	deploymentRequest, err := unmarshalDeploymentRequest(r.Body)

	if api.AuthenticationEnabled {
		username, password, _ := r.BasicAuth()
		if clientID == "" || tenantID == "" || clientSecret == ""  {
			return &appError{ fmt.Errorf("missing Azure AD configuration"), "", 500}
		}
		appErr := authenticate(username, password)
		if appErr != nil {
			return appErr
		}
	}

	// Warn about deprecated fields in deploymentRequest and set default env if not set
	warnings := ensurePropertyCompatibility(&deploymentRequest)
	if len(deploymentRequest.Namespace) == 0 {
		deploymentRequest.Namespace = "app"
	}

	if len(deploymentRequest.Namespace) == 0 {
		deploymentRequest.Namespace = "default"
	}

	if err != nil {
		return &appError{err, "unable to unmarshal deployment request", http.StatusBadRequest}
	}
	glog.Infof("Received deployment request: %s", deploymentRequest)

	fasit := FasitClient{api.FasitURL, deploymentRequest.FasitUsername, deploymentRequest.FasitPassword}

	glog.Infof("Starting deployment. Deploying %s:%s to %s\n", deploymentRequest.Application, deploymentRequest.Version, deploymentRequest.FasitEnvironment)

	manifest, err := GenerateManifest(deploymentRequest)
	if err != nil {
		return &appError{err, "unable to generate manifest/nais.yaml", http.StatusInternalServerError}
	}

	var fasitEnvironmentClass string
	var naisResources []NaisResource

	if !deploymentRequest.SkipFasit {
		glog.Infof("Starting deployment. Deploying %s:%s to %s\n", deploymentRequest.Application, deploymentRequest.Version, deploymentRequest.FasitEnvironment)
	} else {
		glog.Infof("Starting deployment. Deploying %s:%s. Fasit will be skipped\n", deploymentRequest.Application, deploymentRequest.Version)
	}

	if !deploymentRequest.SkipFasit {
		if hasResources(manifest) {
			if deploymentRequest.FasitEnvironment == "" {
				return &appError{err, "no fasit environment provided, but contains resources to be consumed or exposed", http.StatusInternalServerError}
			}
			if err := validateFasitRequirements(fasit, deploymentRequest.Application, deploymentRequest.FasitEnvironment); err != nil {
				return &appError{err, "validating requirements for deployment failed", http.StatusInternalServerError}
			}
			fasitEnvironmentClass, err = fasit.GetFasitEnvironmentClass(deploymentRequest.FasitEnvironment)
		}

		naisResources, err = FetchFasitResources(fasit, deploymentRequest.Application, deploymentRequest.FasitEnvironment, deploymentRequest.Zone, manifest.FasitResources.Used)
		if err != nil {
			return &appError{err, "unable to fetch fasit resources", http.StatusBadRequest}
		}
	}

	spec := app.Spec{
		Application: deploymentRequest.Application,
		Namespace:   deploymentRequest.Namespace,
		Team:        manifest.Team,
	}
	deploymentResult, err := createOrUpdateK8sResources(spec, deploymentRequest, manifest, naisResources, api.ClusterSubdomain, api.IstioEnabled, api.Clientset)
	if err != nil {
		return &appError{err, "failed while creating or updating k8s-resources", http.StatusInternalServerError}
	}

	deploys.With(prometheus.Labels{"nais_app": deploymentRequest.Application}).Inc()

	if !deploymentRequest.SkipFasit && hasResources(manifest) {
		if err := updateFasit(fasit, deploymentRequest, naisResources, manifest, createIngressHostname(deploymentRequest.Application, deploymentRequest.Namespace, api.ClusterSubdomain), fasitEnvironmentClass, deploymentRequest.FasitEnvironment, api.ClusterSubdomain); err != nil {
			return &appError{err, "failed while updating Fasit", http.StatusInternalServerError}
		}
	}

	NotifySensuAboutDeploy(spec, &deploymentRequest, &api.ClusterName)

	w.WriteHeader(200)
	w.Write(createResponse(deploymentResult, warnings))
	return nil
}

func (api Api) deploymentStatusHandler(w http.ResponseWriter, r *http.Request) *appError {
	namespace := pat.Param(r, "namespace")
	deployName := pat.Param(r, "deployName")

	status, view, err := api.DeploymentStatusViewer.DeploymentStatusView(namespace, deployName)

	if err != nil {
		return &appError{err, "deployment not found ", http.StatusNotFound}
	}

	switch status {
	case InProgress:
		w.WriteHeader(http.StatusAccepted)
	case Failed:
		w.WriteHeader(http.StatusInternalServerError)
	case Success:
		w.WriteHeader(http.StatusOK)
	}

	if b, err := json.Marshal(view); err == nil {
		w.Write(b)
	} else {
		glog.Errorf("Unable to marshal deploy status view: %+v", view)
	}

	return nil
}

func (api Api) isAlive(w http.ResponseWriter, _ *http.Request) *appError {
	requests.With(prometheus.Labels{"path": "isAlive"}).Inc()
	fmt.Fprint(w, "")
	return nil
}

func (api Api) version(w http.ResponseWriter, _ *http.Request) *appError {
	response := map[string]string{"version": ver.Version, "revision": ver.Revision}

	if err := json.NewEncoder(w).Encode(response); err != nil {
		return &appError{err, "unable to encode JSON", 500}
	}

	return nil
}

func (api Api) deleteApplication(w http.ResponseWriter, r *http.Request) *appError {
	namespace := pat.Param(r, "namespace")
	application := pat.Param(r, "deployName")

	spec := app.Spec{Application: application, Namespace: namespace}
	result, err := deleteK8sResouces(spec, api.Clientset)

	response := ""
	if len(result) > 0 {
		response = "result: \n"
		for _, res := range result {
			response += res + "\n"
		}
	}

	if err != nil {
		return &appError{err, fmt.Sprintf("there were errors when trying to delete app: %+v", response), http.StatusInternalServerError}
	}

	glog.Infof("Deleted application %s in %s\n", application, namespace)

	w.Write([]byte(response))
	w.WriteHeader(http.StatusOK)
	return nil
}

func validateFasitRequirements(fasit FasitClientAdapter, application, fasitEnvironment string) error {
	if _, err := fasit.GetFasitEnvironmentClass(fasitEnvironment); err != nil {
		glog.Errorf("Environment '%s' does not exist in Fasit", fasitEnvironment)
		return fmt.Errorf("unable to get fasit environment: %s. %s", fasitEnvironment, err)
	}
	if err := fasit.GetFasitApplication(application); err != nil {
		glog.Errorf("Application '%s' does not exist in Fasit", application)
		return fmt.Errorf("unable to get fasit application: %s. %s", application, err)
	}

	return nil
}

func hasResources(manifest NaisManifest) bool {
	if len(manifest.FasitResources.Used) == 0 && len(manifest.FasitResources.Exposed) == 0 {
		return false
	}
	return true
}

func createResponse(deploymentResult DeploymentResult, warnings []string) []byte {
	response := "result: \n"

	if deploymentResult.Deployment != nil {
		response += "- created deployment\n"
	}
	if deploymentResult.Secret != nil {
		response += "- created secret\n"
	}
	if deploymentResult.Service != nil {
		response += "- created service\n"
	}
	if deploymentResult.Ingress != nil {
		response += "- created ingress\n"
	}
	if deploymentResult.Autoscaler != nil {
		response += "- created autoscaler\n"
	}
	if deploymentResult.AlertsConfigMap != nil {
		response += "- updated alerts configmap (app-rules)\n"
	}
	if deploymentResult.Redis != nil {
		response += "- created redis\n"
	}
	if deploymentResult.ServiceAccount != nil {
		response += "- created serviceaccount\n"
	}
	if deploymentResult.RoleBinding != nil {
		response += "- created rolebinding\n"
	}

	if len(warnings) > 0 {
		response += "\nWarnings:\n"
		for _, warning := range warnings {
			response += fmt.Sprintf("- %s\n", warning)
		}
	}

	return []byte(response)
}

func unmarshalDeploymentRequest(body io.ReadCloser) (naisrequest.Deploy, error) {
	requestBody, err := ioutil.ReadAll(body)
	if err != nil {
		return naisrequest.Deploy{}, fmt.Errorf("could not read deployment request body %s", err)
	}

	deploymentRequest := naisrequest.Deploy{}

	if err = json.Unmarshal(requestBody, &deploymentRequest); err != nil {
		return naisrequest.Deploy{}, fmt.Errorf("could not unmarshal body %s", err)
	}

	return deploymentRequest, nil
}

func ensurePropertyCompatibility(deploy *naisrequest.Deploy) []string {
	var warnings []string

	if len(deploy.Environment) > 0 {
		warnings = append(warnings, "Specifying environment is deprecated, specify namespace instead.")
	}

	return warnings
}
<commit_msg>Makes gofmt happy<commit_after>package api

import (
	"encoding/json"
	"fmt"
	"github.com/golang/glog"
	"github.com/nais/naisd/api/app"
	"github.com/nais/naisd/api/naisrequest"
	ver "github.com/nais/naisd/api/version"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"goji.io"
	"goji.io/pat"
	"io"
	"io/ioutil"
	"k8s.io/client-go/kubernetes"
	"net/http"
	"net/url"
	"os"
)

type Api struct {
	Clientset              kubernetes.Interface
	FasitURL               string
	ClusterSubdomain       string
	ClusterName            string
	IstioEnabled           bool
	AuthenticationEnabled  bool
	DeploymentStatusViewer DeploymentStatusViewer
}

var (
	clientID     = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_APP_ID")
	clientSecret = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_PASSWORD")
	tenantID     = os.Getenv("AZURE_AD_SERVICE_PRINCIPAL_TENANT")
)

type AppError interface {
	error
	Code() int
}

type appError struct {
	OriginalError error
	Message       string
	StatusCode    int
}

func (e appError) Code() int {
	return e.StatusCode
}
func (e appError) Error() string {
	if e.OriginalError != nil {
		return fmt.Sprintf("%s: %s (%d)", e.Message, e.OriginalError.Error(), e.StatusCode)
	}

	return fmt.Sprintf("%s (%d)", e.Message, e.StatusCode)
}

type appHandler func(w http.ResponseWriter, r *http.Request) *appError

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if e := fn(w, r); e != nil { // e is *appError, not os.Error.
		glog.Errorf(e.Error())
		http.Error(w, e.Error(), e.StatusCode)
	}
}

var (
	requests = prometheus.NewCounterVec(
		prometheus.CounterOpts{Name: "requests", Help: "requests pr path"}, []string{"path"},
	)
	deploys = prometheus.NewCounterVec(
		prometheus.CounterOpts{Name: "deployments", Help: "deployments done by NaisD"}, []string{"nais_app"},
	)
)

func init() {
	prometheus.MustRegister(requests)
	prometheus.MustRegister(deploys)
}

func (api Api) Handler() http.Handler {
	mux := goji.NewMux()

	mux.Handle(pat.Get("/isalive"), appHandler(api.isAlive))
	mux.Handle(pat.Post("/deploy"), appHandler(api.deploy))
	mux.Handle(pat.Get("/metrics"), promhttp.Handler())
	mux.Handle(pat.Get("/version"), appHandler(api.version))
	mux.Handle(pat.Get("/deploystatus/:namespace/:deployName"), appHandler(api.deploymentStatusHandler))
	mux.Handle(pat.Delete("/app/:namespace/:deployName"), appHandler(api.deleteApplication))
	return mux
}

func NewApi(clientset kubernetes.Interface, fasitUrl, clusterDomain, clusterName string, istioEnabled bool, authenticationEnabled bool, d DeploymentStatusViewer) Api {
	return Api{
		Clientset:              clientset,
		FasitURL:               fasitUrl,
		ClusterSubdomain:       clusterDomain,
		ClusterName:            clusterName,
		IstioEnabled:           istioEnabled,
		AuthenticationEnabled:  authenticationEnabled,
		DeploymentStatusViewer: d,
	}
}
func authenticate(username, password string) *appError {
	authURL := fmt.Sprintf("https://login.microsoftonline.com/authority/%s/oauth2/token", tenantID)
	content := url.Values{}
	content.Add("grant_type", "password")
	content.Add("username", username)
	content.Add("password", password)
	content.Add("client_id", clientID)
	content.Add("client_secret", clientSecret)
	content.Add("resource", clientID)

	res, err := http.PostForm(authURL, content)
	if err != nil {
		return &appError{err, "Failed during authentication", 500}
	}
	if res.StatusCode != 200 {
		return &appError{err, "Authentication failure", 401}
	}
	return nil
}

func (api Api) deploy(w http.ResponseWriter, r *http.Request) *appError {
	requests.With(prometheus.Labels{"path": "deploy"}).Inc()
	deploymentRequest, err := unmarshalDeploymentRequest(r.Body)

	if api.AuthenticationEnabled {
		username, password, _ := r.BasicAuth()
		if clientID == "" || tenantID == "" || clientSecret == "" {
			return &appError{fmt.Errorf("missing Azure AD configuration"), "", 500}
		}
		appErr := authenticate(username, password)
		if appErr != nil {
			return appErr
		}
	}

	// Warn about deprecated fields in deploymentRequest and set default env if not set
	warnings := ensurePropertyCompatibility(&deploymentRequest)
	if len(deploymentRequest.Namespace) == 0 {
		deploymentRequest.Namespace = "app"
	}

	if len(deploymentRequest.Namespace) == 0 {
		deploymentRequest.Namespace = "default"
	}

	if err != nil {
		return &appError{err, "unable to unmarshal deployment request", http.StatusBadRequest}
	}
	glog.Infof("Received deployment request: %s", deploymentRequest)

	fasit := FasitClient{api.FasitURL, deploymentRequest.FasitUsername, deploymentRequest.FasitPassword}

	glog.Infof("Starting deployment. Deploying %s:%s to %s\n", deploymentRequest.Application, deploymentRequest.Version, deploymentRequest.FasitEnvironment)

	manifest, err := GenerateManifest(deploymentRequest)
	if err != nil {
		return &appError{err, "unable to generate manifest/nais.yaml", http.StatusInternalServerError}
	}

	var fasitEnvironmentClass string
	var naisResources []NaisResource

	if !deploymentRequest.SkipFasit {
		glog.Infof("Starting deployment. Deploying %s:%s to %s\n", deploymentRequest.Application, deploymentRequest.Version, deploymentRequest.FasitEnvironment)
	} else {
		glog.Infof("Starting deployment. Deploying %s:%s. Fasit will be skipped\n", deploymentRequest.Application, deploymentRequest.Version)
	}

	if !deploymentRequest.SkipFasit {
		if hasResources(manifest) {
			if deploymentRequest.FasitEnvironment == "" {
				return &appError{err, "no fasit environment provided, but contains resources to be consumed or exposed", http.StatusInternalServerError}
			}
			if err := validateFasitRequirements(fasit, deploymentRequest.Application, deploymentRequest.FasitEnvironment); err != nil {
				return &appError{err, "validating requirements for deployment failed", http.StatusInternalServerError}
			}
			fasitEnvironmentClass, err = fasit.GetFasitEnvironmentClass(deploymentRequest.FasitEnvironment)
		}

		naisResources, err = FetchFasitResources(fasit, deploymentRequest.Application, deploymentRequest.FasitEnvironment, deploymentRequest.Zone, manifest.FasitResources.Used)
		if err != nil {
			return &appError{err, "unable to fetch fasit resources", http.StatusBadRequest}
		}
	}

	spec := app.Spec{
		Application: deploymentRequest.Application,
		Namespace:   deploymentRequest.Namespace,
		Team:        manifest.Team,
	}
	deploymentResult, err := createOrUpdateK8sResources(spec, deploymentRequest, manifest, naisResources, api.ClusterSubdomain, api.IstioEnabled, api.Clientset)
	if err != nil {
		return &appError{err, "failed while creating or updating k8s-resources", http.StatusInternalServerError}
	}

	deploys.With(prometheus.Labels{"nais_app": deploymentRequest.Application}).Inc()

	if !deploymentRequest.SkipFasit && hasResources(manifest) {
		if err := updateFasit(fasit, deploymentRequest, naisResources, manifest, createIngressHostname(deploymentRequest.Application, deploymentRequest.Namespace, api.ClusterSubdomain), fasitEnvironmentClass, deploymentRequest.FasitEnvironment, api.ClusterSubdomain); err != nil {
			return &appError{err, "failed while updating Fasit", http.StatusInternalServerError}
		}
	}

	NotifySensuAboutDeploy(spec, &deploymentRequest, &api.ClusterName)

	w.WriteHeader(200)
	w.Write(createResponse(deploymentResult, warnings))
	return nil
}

func (api Api) deploymentStatusHandler(w http.ResponseWriter, r *http.Request) *appError {
	namespace := pat.Param(r, "namespace")
	deployName := pat.Param(r, "deployName")

	status, view, err := api.DeploymentStatusViewer.DeploymentStatusView(namespace, deployName)

	if err != nil {
		return &appError{err, "deployment not found ", http.StatusNotFound}
	}

	switch status {
	case InProgress:
		w.WriteHeader(http.StatusAccepted)
	case Failed:
		w.WriteHeader(http.StatusInternalServerError)
	case Success:
		w.WriteHeader(http.StatusOK)
	}

	if b, err := json.Marshal(view); err == nil {
		w.Write(b)
	} else {
		glog.Errorf("Unable to marshal deploy status view: %+v", view)
	}

	return nil
}

func (api Api) isAlive(w http.ResponseWriter, _ *http.Request) *appError {
	requests.With(prometheus.Labels{"path": "isAlive"}).Inc()
	fmt.Fprint(w, "")
	return nil
}

func (api Api) version(w http.ResponseWriter, _ *http.Request) *appError {
	response := map[string]string{"version": ver.Version, "revision": ver.Revision}

	if err := json.NewEncoder(w).Encode(response); err != nil {
		return &appError{err, "unable to encode JSON", 500}
	}

	return nil
}

func (api Api) deleteApplication(w http.ResponseWriter, r *http.Request) *appError {
	namespace := pat.Param(r, "namespace")
	application := pat.Param(r, "deployName")

	spec := app.Spec{Application: application, Namespace: namespace}
	result, err := deleteK8sResouces(spec, api.Clientset)

	response := ""
	if len(result) > 0 {
		response = "result: \n"
		for _, res := range result {
			response += res + "\n"
		}
	}

	if err != nil {
		return &appError{err, fmt.Sprintf("there were errors when trying to delete app: %+v", response), http.StatusInternalServerError}
	}

	glog.Infof("Deleted application %s in %s\n", application, namespace)

	w.Write([]byte(response))
	w.WriteHeader(http.StatusOK)
	return nil
}

func validateFasitRequirements(fasit FasitClientAdapter, application, fasitEnvironment string) error {
	if _, err := fasit.GetFasitEnvironmentClass(fasitEnvironment); err != nil {
		glog.Errorf("Environment '%s' does not exist in Fasit", fasitEnvironment)
		return fmt.Errorf("unable to get fasit environment: %s. %s", fasitEnvironment, err)
	}
	if err := fasit.GetFasitApplication(application); err != nil {
		glog.Errorf("Application '%s' does not exist in Fasit", application)
		return fmt.Errorf("unable to get fasit application: %s. %s", application, err)
	}

	return nil
}

func hasResources(manifest NaisManifest) bool {
	if len(manifest.FasitResources.Used) == 0 && len(manifest.FasitResources.Exposed) == 0 {
		return false
	}
	return true
}

func createResponse(deploymentResult DeploymentResult, warnings []string) []byte {
	response := "result: \n"

	if deploymentResult.Deployment != nil {
		response += "- created deployment\n"
	}
	if deploymentResult.Secret != nil {
		response += "- created secret\n"
	}
	if deploymentResult.Service != nil {
		response += "- created service\n"
	}
	if deploymentResult.Ingress != nil {
		response += "- created ingress\n"
	}
	if deploymentResult.Autoscaler != nil {
		response += "- created autoscaler\n"
	}
	if deploymentResult.AlertsConfigMap != nil {
		response += "- updated alerts configmap (app-rules)\n"
	}
	if deploymentResult.Redis != nil {
		response += "- created redis\n"
	}
	if deploymentResult.ServiceAccount != nil {
		response += "- created serviceaccount\n"
	}
	if deploymentResult.RoleBinding != nil {
		response += "- created rolebinding\n"
	}

	if len(warnings) > 0 {
		response += "\nWarnings:\n"
		for _, warning := range warnings {
			response += fmt.Sprintf("- %s\n", warning)
		}
	}

	return []byte(response)
}

func unmarshalDeploymentRequest(body io.ReadCloser) (naisrequest.Deploy, error) {
	requestBody, err := ioutil.ReadAll(body)
	if err != nil {
		return naisrequest.Deploy{}, fmt.Errorf("could not read deployment request body %s", err)
	}

	deploymentRequest := naisrequest.Deploy{}

	if err = json.Unmarshal(requestBody, &deploymentRequest); err != nil {
		return naisrequest.Deploy{}, fmt.Errorf("could not unmarshal body %s", err)
	}

	return deploymentRequest, nil
}

func ensurePropertyCompatibility(deploy *naisrequest.Deploy) []string {
	var warnings []string

	if len(deploy.Environment) > 0 {
		warnings = append(warnings, "Specifying environment is deprecated, specify namespace instead.")
	}

	return warnings
}
<|endoftext|>


<commit_before>package api

// !!! scope doit etre initialisé avant d'utiliser ce package

import (
	"github.com/toorop/tmail/core"
)

// USER

// UserGetByLoging returns an User by his login
func UserGetByLogin(login string) (user *core.User, err error) {
	return core.UserGetByLogin(login)
}

// UserAdd add a new usere
func UserAdd(login, passwd, mbQuota string, haveMailbox, authRelay bool) error {
	return core.UserAdd(login, passwd, mbQuota, haveMailbox, authRelay)
}

// UserDel delete an user (keep his mailboxe)
func UserDel(login string) error {
	return core.UserDel(login)
}

// UserList return all users
func UserGetAll() (users []core.User, err error) {
	return core.UserList()
}

// MAILBOXES
// MailboxAdd create a new mailbox
func MailboxAdd(mailbox string) error {
	return core.MailboxAdd(mailbox)
}

// MailboxDel delete a mailbox
func MailboxDel(mailbox string) error {
	return core.MailboxDel(mailbox)
}

// MailboxList return all mailboxes
func MailboxList() (mailboxes []core.Mailbox, err error) {
	return core.MailboxList()
}

// RELAY IP
// RelayIpAdd add an IP authozed to relay through tmail
func RelayIpAdd(ip string) error {
	return core.RelayIpAdd(ip)
}

// RelayIpDel remove an ip from authorized IP
func RelayIpDel(ip string) error {
	return core.RelayIpDel(ip)
}

// RelayIpGetAll returns all IPs which are authorized to relay through tmail
func RelayIpGetAll() (ips []core.RelayIpOk, err error) {
	return core.RelayIpGetAll()
}

// Queue
// QueueGetMessages returns all message in queue
func QueueGetMessages() ([]core.QMessage, error) {
	return core.QueueListMessages()
}

// QueueGetMessage return a message by its id
func QueueGetMessage(id int64) (core.QMessage, error) {
	return core.QueueGetMessageById(id)
}

// QueueDiscardMsgByKey discard a message (delete without bouncing) by his id
func QueueDiscardMsg(id int64) error {
	m, err := core.QueueGetMessageById(id)
	if err != nil {
		return err
	}
	return m.Discard()
}

// QueueBounceMsgByKey bounce a message by his key
func QueueBounceMsg(id int64) error {
	m, err := core.QueueGetMessageById(id)
	if err != nil {
		return err
	}
	return m.Bounce()
}

// ROUTES
// RoutesGet returns all routes
func RoutesGet() ([]core.Route, error) {
	return core.GetAllRoutes()
}

// RoutesAdd adds en new route
func RoutesAdd(host, localIp, remoteHost string, remotePort, priority int, user, mailFrom, smtpAuthLogin, smtpAuthPasswd string) error {
	return core.AddRoute(host, localIp, remoteHost, remotePort, priority, user, mailFrom, smtpAuthLogin, smtpAuthPasswd)
}

// RoutesDel delete route routeId
func RoutesDel(routeId int64) error {
	return core.DelRoute(routeId)
}

// RCPTHOSTS ie locals domains

// RcptHostAdd add a rcpthost
func RcpthostAdd(host string, isLocal bool) error {
	return core.RcpthostAdd(host, isLocal)
}

// RcpthostDel delete a rcpthost
func RcpthostDel(host string) error {
	return core.RcpthostDel(host)
}

// RcpthostList returns all rcpthosts
func RcpthostList() (hosts []core.RcptHost, err error) {
	return core.RcpthostGetAll()
}

// DKIM

// DkimEnable Enable DKIL for domain domain
// DkimEnable will create keys pair
func DkimEnable(domain string) (dkimConfig *core.DkimConfig, err error) {
	return core.DkimEnable(domain)
}

// DkimDisable will remove DKIM confi for doimain domain from DB
// resulting in desactivate DKIM for outgoing message from the domain.
func DkimDisable(domain string) error {
	return core.DkimDisable(domain)
}

func DkimGetConfig(domain string) (dkimConfig *core.DkimConfig, err error) {
	return core.DkimGetConfig(domain)
}
<commit_msg>add warning<commit_after>package api

// WARNING core.ScopeBootstrap() must be called
// WARNING 2: useless to be removed

import (
	"github.com/toorop/tmail/core"
)

// USER

// UserGetByLoging returns an User by his login
func UserGetByLogin(login string) (user *core.User, err error) {
	return core.UserGetByLogin(login)
}

// UserAdd add a new usere
func UserAdd(login, passwd, mbQuota string, haveMailbox, authRelay bool) error {
	return core.UserAdd(login, passwd, mbQuota, haveMailbox, authRelay)
}

// UserDel delete an user (keep his mailboxe)
func UserDel(login string) error {
	return core.UserDel(login)
}

// UserList return all users
func UserGetAll() (users []core.User, err error) {
	return core.UserList()
}

// MAILBOXES
// MailboxAdd create a new mailbox
func MailboxAdd(mailbox string) error {
	return core.MailboxAdd(mailbox)
}

// MailboxDel delete a mailbox
func MailboxDel(mailbox string) error {
	return core.MailboxDel(mailbox)
}

// MailboxList return all mailboxes
func MailboxList() (mailboxes []core.Mailbox, err error) {
	return core.MailboxList()
}

// RELAY IP
// RelayIpAdd add an IP authozed to relay through tmail
func RelayIpAdd(ip string) error {
	return core.RelayIpAdd(ip)
}

// RelayIpDel remove an ip from authorized IP
func RelayIpDel(ip string) error {
	return core.RelayIpDel(ip)
}

// RelayIpGetAll returns all IPs which are authorized to relay through tmail
func RelayIpGetAll() (ips []core.RelayIpOk, err error) {
	return core.RelayIpGetAll()
}

// Queue
// QueueGetMessages returns all message in queue
func QueueGetMessages() ([]core.QMessage, error) {
	return core.QueueListMessages()
}

// QueueGetMessage return a message by its id
func QueueGetMessage(id int64) (core.QMessage, error) {
	return core.QueueGetMessageById(id)
}

// QueueDiscardMsgByKey discard a message (delete without bouncing) by his id
func QueueDiscardMsg(id int64) error {
	m, err := core.QueueGetMessageById(id)
	if err != nil {
		return err
	}
	return m.Discard()
}

// QueueBounceMsgByKey bounce a message by his key
func QueueBounceMsg(id int64) error {
	m, err := core.QueueGetMessageById(id)
	if err != nil {
		return err
	}
	return m.Bounce()
}

// ROUTES
// RoutesGet returns all routes
func RoutesGet() ([]core.Route, error) {
	return core.GetAllRoutes()
}

// RoutesAdd adds en new route
func RoutesAdd(host, localIp, remoteHost string, remotePort, priority int, user, mailFrom, smtpAuthLogin, smtpAuthPasswd string) error {
	return core.AddRoute(host, localIp, remoteHost, remotePort, priority, user, mailFrom, smtpAuthLogin, smtpAuthPasswd)
}

// RoutesDel delete route routeId
func RoutesDel(routeId int64) error {
	return core.DelRoute(routeId)
}

// RCPTHOSTS ie locals domains

// RcptHostAdd add a rcpthost
func RcpthostAdd(host string, isLocal bool) error {
	return core.RcpthostAdd(host, isLocal)
}

// RcpthostDel delete a rcpthost
func RcpthostDel(host string) error {
	return core.RcpthostDel(host)
}

// RcpthostList returns all rcpthosts
func RcpthostList() (hosts []core.RcptHost, err error) {
	return core.RcpthostGetAll()
}

// DKIM

// DkimEnable Enable DKIL for domain domain
// DkimEnable will create keys pair
func DkimEnable(domain string) (dkimConfig *core.DkimConfig, err error) {
	return core.DkimEnable(domain)
}

// DkimDisable will remove DKIM confi for doimain domain from DB
// resulting in desactivate DKIM for outgoing message from the domain.
func DkimDisable(domain string) error {
	return core.DkimDisable(domain)
}

func DkimGetConfig(domain string) (dkimConfig *core.DkimConfig, err error) {
	return core.DkimGetConfig(domain)
}
<|endoftext|>


<commit_before><commit_msg>[Fix]spec: rename "DefaultTrigSet" to "DefaultTrig"<commit_after><|endoftext|>


<commit_before>package api

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/leancloud/lean-cli/apps"
	"github.com/levigross/grequests"
)

// Log is EngineLogs's type structure
type Log struct {
	InstanceName string `json:"instanceName"`
	Content      string `json:"content"`
	Type         string `json:"type"`
	Time         string `json:"time"`
	GroupName    string `json:"groupName"`
	Production   int    `json:"production"`
	OID          string `json:"oid"`
	Level        string `json:"level"`
	Instance     string `json:"instance"`
}

// LogReceiver is print func interface to PrintLogs
type LogReceiver func(*Log) error

// ReceiveLogsByLimit will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByLimit(printer LogReceiver, appID string, masterKey string, isProd bool, group string, limit int, follow bool) error {
	params := map[string]string{
		"limit":      strconv.Itoa(limit),
		"production": "0",
		"group":      group,
	}
	if isProd {
		params["production"] = "1"
	}

	for {
		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}
		for i := len(logs); i > 0; i-- {
			log := logs[i-1]

			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if !follow {
			break
		}

		// limit is not necessary in second round of fetch
		delete(params, "limit")

		if len(logs) > 0 {
			params["since"] = logs[0].Time
		}

		time.Sleep(5 * time.Second)
	}

	return nil
}

// ReceiveLogsByRange will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByRange(printer LogReceiver, appID string, masterKey string, isProd bool, group string, from time.Time, to time.Time) error {
	params := map[string]string{
		"ascend":     "true",
		"since":      from.UTC().Format("2006-01-02T15:04:05.000000000Z"),
		"production": "0",
		"group":      group,
		"limit":      "1000",
	}
	if isProd {
		params["production"] = "1"
	}

	for {
		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}
		for i := len(logs); i > 0; i-- {
			log := logs[i-1]

			logTime, err := time.Parse("2006-01-02T15:04:05.999999999Z", log.Time)
			if err != nil {
				return err
			}
			if to != (time.Time{}) && logTime.After(to) {
				// reached the end
				return nil
			}

			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if len(logs) == 0 {
			// no more logs
			return nil
		}

		if len(logs) > 0 {
			params["since"] = logs[len(logs)-1].Time
		}
	}
}

func fetchLogs(appID string, masterKey string, params map[string]string, isProd bool) ([]Log, error) {
	region, err := apps.GetAppRegion(appID)
	if err != nil {
		return nil, err
	}

	url := NewClientByRegion(region).GetBaseURL() + "/1.1/tables/EngineLogs"

	options := &grequests.RequestOptions{
		Headers: map[string]string{
			"X-AVOSCloud-Application-Id": appID,
			"X-AVOSCloud-Master-Key":     masterKey,
			"Content-Type":               "application/json",
		},
		Params: params,
	}

	var resp *grequests.Response
	retryCount := 0
	for {
		resp, err = grequests.Get(url, options)
		if err == nil {
			break
		}
		if retryCount >= 3 {
			return nil, err
		}
		retryCount++
		time.Sleep(1123 * time.Millisecond) // 1123 is a prime number, prime number makes less bugs.
	}

	var logs []Log
	err = resp.JSON(&logs)
	return logs, err
}
<commit_msg>:bug: Fix ReceiveLogsByRange<commit_after>package api

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/leancloud/lean-cli/apps"
	"github.com/levigross/grequests"
)

// Log is EngineLogs's type structure
type Log struct {
	InstanceName string `json:"instanceName"`
	Content      string `json:"content"`
	Type         string `json:"type"`
	Time         string `json:"time"`
	GroupName    string `json:"groupName"`
	Production   int    `json:"production"`
	OID          string `json:"oid"`
	Level        string `json:"level"`
	Instance     string `json:"instance"`
}

// LogReceiver is print func interface to PrintLogs
type LogReceiver func(*Log) error

// ReceiveLogsByLimit will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByLimit(printer LogReceiver, appID string, masterKey string, isProd bool, group string, limit int, follow bool) error {
	params := map[string]string{
		"limit":      strconv.Itoa(limit),
		"production": "0",
		"group":      group,
	}
	if isProd {
		params["production"] = "1"
	}

	for {
		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}
		for i := len(logs); i > 0; i-- {
			log := logs[i-1]

			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if !follow {
			break
		}

		// limit is not necessary in second round of fetch
		delete(params, "limit")

		if len(logs) > 0 {
			params["since"] = logs[0].Time
		}

		time.Sleep(5 * time.Second)
	}

	return nil
}

// ReceiveLogsByRange will poll the leanengine's log and print it to the giver io.Writer
func ReceiveLogsByRange(printer LogReceiver, appID string, masterKey string, isProd bool, group string, from time.Time, to time.Time) error {
	params := map[string]string{
		"ascend":     "true",
		"since":      from.UTC().Format("2006-01-02T15:04:05.000000000Z"),
		"production": "0",
		"group":      group,
		"limit":      "1000",
	}
	if isProd {
		params["production"] = "1"
	}

	for {
		logs, err := fetchLogs(appID, masterKey, params, isProd)
		if err != nil {
			return err
		}
		for _, log := range logs {
			logTime, err := time.Parse("2006-01-02T15:04:05.999999999Z", log.Time)
			if err != nil {
				return err
			}
			if to != (time.Time{}) && logTime.After(to) {
				// reached the end
				return nil
			}

			err = printer(&log)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error \"%v\" while parsing log: %v\r\n", err, log)
			}
		}

		if len(logs) == 0 {
			// no more logs
			return nil
		}

		if len(logs) > 0 {
			params["since"] = logs[len(logs)-1].Time
		}
	}
}

func fetchLogs(appID string, masterKey string, params map[string]string, isProd bool) ([]Log, error) {
	region, err := apps.GetAppRegion(appID)
	if err != nil {
		return nil, err
	}

	url := NewClientByRegion(region).GetBaseURL() + "/1.1/tables/EngineLogs"

	options := &grequests.RequestOptions{
		Headers: map[string]string{
			"X-AVOSCloud-Application-Id": appID,
			"X-AVOSCloud-Master-Key":     masterKey,
			"Content-Type":               "application/json",
		},
		Params: params,
	}

	var resp *grequests.Response
	retryCount := 0
	for {
		resp, err = grequests.Get(url, options)
		if err == nil {
			break
		}
		if retryCount >= 3 {
			return nil, err
		}
		retryCount++
		time.Sleep(1123 * time.Millisecond) // 1123 is a prime number, prime number makes less bugs.
	}

	var logs []Log
	err = resp.JSON(&logs)
	return logs, err
}
<|endoftext|>


<commit_before>package auth

import (
	"encoding/json"
	"net/http"

	"github.com/captaincodeman/go-firebase"
	"github.com/rs/cors"
	"golang.org/x/net/context"
)

var auth *firebase.Auth

func init() {
	// default firebase app, uses firebase-credentials.json file
	fb, _ := firebase.New()
	auth = fb.Auth()

	// auth server comes with CORS included
	http.Handle("/", auth.Server(customClaims))

	// but we need to add it for the API endpoint
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"Authorization"},
	})

	// example API endpoint with role checks
	http.Handle("/api", c.Handler(auth.AnyRole(http.HandlerFunc(apiHandler), "operator")))
}

func customClaims(ctx context.Context, token *firebase.Token) (*firebase.Claims, error) {
	// get the firebase user id for lookup
	// userID, _ := token.UID()

	// Here is where we'd lookup the user and set the custom claims
	// that we want to be added to the token we're going to produce
	claims := make(firebase.Claims)
	claims["uid"] = 1
	claims["roles"] = []string{
		"operator",
	}

	return &claims, nil
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	data := struct {
		ID      int    `json:"id"`
		Message string `json:"message"`
	}{
		ID:      1,
		Message: "Hello World",
	}

	enc := json.NewEncoder(w)
	enc.Encode(data)
}
<commit_msg>Server options<commit_after>package auth

import (
	"encoding/json"
	"net/http"

	"github.com/captaincodeman/go-firebase"
	"github.com/rs/cors"
	"golang.org/x/net/context"
)

var auth *firebase.Auth

func init() {
	// default firebase app, uses firebase-credentials.json file
	fb, _ := firebase.New()
	auth = fb.Auth()

	// auth server comes with CORS included
	http.Handle("/", auth.Server(customClaims,
		// Custom AllowedOrigins
		firebase.ServerAllowedOrigins([]string{"*"}),
		// Custom URI for verifyHandler
		firebase.ServerVerifyURI("/customVerify"),
		// Custom URI for generateHandler
		firebase.ServerGenerateURI("/customGenerate")))

	// but we need to add it for the API endpoint
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"Authorization"},
	})

	// example API endpoint with role checks
	http.Handle("/api", c.Handler(auth.AnyRole(http.HandlerFunc(apiHandler), "operator")))
}

func customClaims(ctx context.Context, token *firebase.Token) (*firebase.Claims, error) {
	// get the firebase user id for lookup
	// userID, _ := token.UID()

	// Here is where we'd lookup the user and set the custom claims
	// that we want to be added to the token we're going to produce
	claims := make(firebase.Claims)
	claims["uid"] = 1
	claims["roles"] = []string{
		"operator",
	}

	return &claims, nil
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	data := struct {
		ID      int    `json:"id"`
		Message string `json:"message"`
	}{
		ID:      1,
		Message: "Hello World",
	}

	enc := json.NewEncoder(w)
	enc.Encode(data)
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/base64"
	"fmt"
	"os"
	"strings"

	"github.com/dokku/dokku/plugins/common"
)

//CommandShow implements config:show
func CommandShow(args []string, global bool, shell bool, export bool, merged bool) {
	appName, _ := getCommonArgs(global, args)
	env := getEnvironment(appName, merged)
	if shell && export {
		common.LogFail("Only one of --shell and --export can be given")
	}
	if shell {
		fmt.Print(env.Export(ExportFormatShell))
	} else if export {
		fmt.Println(env.Export(ExportFormatExports))
	} else {
		contextName := "global"
		if appName != "" {
			contextName = appName
		}
		common.LogInfo2(contextName + " env vars")
		fmt.Println(env.Export(ExportFormatPretty))
	}
}

//CommandGet implements config:get
func CommandGet(args []string, global bool, quoted bool) {
	appName, keys := getCommonArgs(global, args)
	if len(keys) > 1 {
		common.LogFail(fmt.Sprintf("Unexpected argument(s): %v", keys[1:]))
	}
	if len(keys) == 0 {
		common.LogFail("Expected: key")
	}
	if value, ok := Get(appName, keys[0]); !ok {
		os.Exit(1)
	} else {
		if quoted {
			fmt.Printf("'%s'", singleQuoteEscape(value))
		} else {
			fmt.Printf("%s", value)
		}
	}
}

//CommandUnset implements config:unset
func CommandUnset(args []string, global bool, noRestart bool) {
	appName, keys := getCommonArgs(global, args)
	err := UnsetMany(appName, keys, !noRestart)
	if err != nil {
		common.LogFail(err.Error())
	}
}

//CommandSet implements config:set
func CommandSet(args []string, global bool, noRestart bool, encoded bool) {
	appName, pairs := getCommonArgs(global, args)
	updated := make(map[string]string)
	for _, e := range pairs {
		parts := strings.SplitN(e, "=", 2)
		if len(parts) == 1 {
			common.LogFail("Invalid env pair: " + e)
		}
		key, value := parts[0], parts[1]
		if encoded {
			decoded, err := base64.StdEncoding.DecodeString(value)
			if err != nil {
				common.LogFail(fmt.Sprintf("%s for key '%s'", err.Error(), key))
			}
			value = string(decoded)
		}
		updated[key] = value
	}
	err := SetMany(appName, updated, !noRestart)
	if err != nil {
		common.LogFail(err.Error())
	}
}

//CommandKeys implements config:keys
func CommandKeys(args []string, global bool, merged bool) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	for _, k := range env.Keys() {
		fmt.Println(k)
	}
}

//CommandExport implements config:export
func CommandExport(args []string, global bool, merged bool, format string) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	exportType := ExportFormatExports
	suffix := "\n"
	switch format {
	case "exports":
		exportType = ExportFormatExports
	case "envfile":
		exportType = ExportFormatEnvfile
	case "docker-args":
		exportType = ExportFormatDockerArgs
	case "shell":
		exportType = ExportFormatShell
		suffix = " "
	case "pretty":
		exportType = ExportFormatPretty
	default:
		common.LogFail(fmt.Sprintf("Unknown export format: %v", format))
	}
	exported := env.Export(exportType)
	fmt.Print(exported + suffix)
}

//CommandBundle implements config:bundle
func CommandBundle(args []string, global bool, merged bool) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	env.ExportBundle(os.Stdout)
}

//getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true.
func getEnvironment(appName string, merged bool) (env *Env) {
	var err error
	if appName != "" && merged {
		env, err = LoadMergedAppEnv(appName)
	} else {
		env, err = loadAppOrGlobalEnv(appName)
	}
	if err != nil {
		common.LogFail(err.Error())
	}
	return env
}

//getCommonArgs extracts common positional args (appName and keys)
func getCommonArgs(global bool, args []string) (appName string, keys []string) {
	keys = args
	if !global {
		if len(args) > 0 {
			appName = args[0]
		}
		if appName == "" {
			common.LogFail("Please specify an app or --global")
		} else {
			keys = args[1:]
		}
	}
	return appName, keys
}
<commit_msg>feat: add newline to config:get output<commit_after>package config

import (
	"encoding/base64"
	"fmt"
	"os"
	"strings"

	"github.com/dokku/dokku/plugins/common"
)

//CommandShow implements config:show
func CommandShow(args []string, global bool, shell bool, export bool, merged bool) {
	appName, _ := getCommonArgs(global, args)
	env := getEnvironment(appName, merged)
	if shell && export {
		common.LogFail("Only one of --shell and --export can be given")
	}
	if shell {
		fmt.Print(env.Export(ExportFormatShell))
	} else if export {
		fmt.Println(env.Export(ExportFormatExports))
	} else {
		contextName := "global"
		if appName != "" {
			contextName = appName
		}
		common.LogInfo2(contextName + " env vars")
		fmt.Println(env.Export(ExportFormatPretty))
	}
}

//CommandGet implements config:get
func CommandGet(args []string, global bool, quoted bool) {
	appName, keys := getCommonArgs(global, args)
	if len(keys) > 1 {
		common.LogFail(fmt.Sprintf("Unexpected argument(s): %v", keys[1:]))
	}
	if len(keys) == 0 {
		common.LogFail("Expected: key")
	}
	if value, ok := Get(appName, keys[0]); !ok {
		os.Exit(1)
	} else {
		if quoted {
			fmt.Printf("'%s'\n", singleQuoteEscape(value))
		} else {
			fmt.Printf("%s\n", value)
		}
	}
}

//CommandUnset implements config:unset
func CommandUnset(args []string, global bool, noRestart bool) {
	appName, keys := getCommonArgs(global, args)
	err := UnsetMany(appName, keys, !noRestart)
	if err != nil {
		common.LogFail(err.Error())
	}
}

//CommandSet implements config:set
func CommandSet(args []string, global bool, noRestart bool, encoded bool) {
	appName, pairs := getCommonArgs(global, args)
	updated := make(map[string]string)
	for _, e := range pairs {
		parts := strings.SplitN(e, "=", 2)
		if len(parts) == 1 {
			common.LogFail("Invalid env pair: " + e)
		}
		key, value := parts[0], parts[1]
		if encoded {
			decoded, err := base64.StdEncoding.DecodeString(value)
			if err != nil {
				common.LogFail(fmt.Sprintf("%s for key '%s'", err.Error(), key))
			}
			value = string(decoded)
		}
		updated[key] = value
	}
	err := SetMany(appName, updated, !noRestart)
	if err != nil {
		common.LogFail(err.Error())
	}
}

//CommandKeys implements config:keys
func CommandKeys(args []string, global bool, merged bool) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	for _, k := range env.Keys() {
		fmt.Println(k)
	}
}

//CommandExport implements config:export
func CommandExport(args []string, global bool, merged bool, format string) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	exportType := ExportFormatExports
	suffix := "\n"
	switch format {
	case "exports":
		exportType = ExportFormatExports
	case "envfile":
		exportType = ExportFormatEnvfile
	case "docker-args":
		exportType = ExportFormatDockerArgs
	case "shell":
		exportType = ExportFormatShell
		suffix = " "
	case "pretty":
		exportType = ExportFormatPretty
	default:
		common.LogFail(fmt.Sprintf("Unknown export format: %v", format))
	}
	exported := env.Export(exportType)
	fmt.Print(exported + suffix)
}

//CommandBundle implements config:bundle
func CommandBundle(args []string, global bool, merged bool) {
	appName, trailingArgs := getCommonArgs(global, args)
	if len(trailingArgs) > 0 {
		common.LogFail(fmt.Sprintf("Trailing argument(s): %v", trailingArgs))
	}
	env := getEnvironment(appName, merged)
	env.ExportBundle(os.Stdout)
}

//getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true.
func getEnvironment(appName string, merged bool) (env *Env) {
	var err error
	if appName != "" && merged {
		env, err = LoadMergedAppEnv(appName)
	} else {
		env, err = loadAppOrGlobalEnv(appName)
	}
	if err != nil {
		common.LogFail(err.Error())
	}
	return env
}

//getCommonArgs extracts common positional args (appName and keys)
func getCommonArgs(global bool, args []string) (appName string, keys []string) {
	keys = args
	if !global {
		if len(args) > 0 {
			appName = args[0]
		}
		if appName == "" {
			common.LogFail("Please specify an app or --global")
		} else {
			keys = args[1:]
		}
	}
	return appName, keys
}
<|endoftext|>


<commit_before>package app

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

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/events"
	"github.com/docker/docker/api/types/swarm"
	drydocker "github.com/moncho/dry/docker"
	"github.com/moncho/dry/ui"
	cache "github.com/patrickmn/go-cache"
)

// state tracks dry state
type state struct {
	sync.RWMutex
	previousViewMode viewMode
	viewMode         viewMode
}

//Dry represents the application.
type Dry struct {
	widgetRegistry   *WidgetRegistry
	dockerDaemon     drydocker.ContainerDaemon
	dockerEvents     <-chan events.Message
	dockerEventsDone chan<- struct{}
	info             types.Info
	output           chan string
	state            *state
	//cache is a potential replacement for state
	cache *cache.Cache
}

//changeViewMode changes the view mode of dry and refreshes the screen
func (d *Dry) changeViewMode(newViewMode viewMode) {
	d.SetViewMode(newViewMode)
	refreshScreen()
}

//SetViewMode changes the view mode of dry
func (d *Dry) SetViewMode(newViewMode viewMode) {
	d.state.Lock()
	defer d.state.Unlock()
	//If the new view is one of the main screens, it must be
	//considered as the view to go back to.
	if newViewMode.isMainScreen() {
		d.state.previousViewMode = newViewMode
	}
	d.state.viewMode = newViewMode
}

//Close closes dry, releasing any resources held by it
func (d *Dry) Close() {
	close(d.dockerEventsDone)
	close(d.output)
}

//OuputChannel returns the channel where dry messages are written
func (d *Dry) OuputChannel() <-chan string {
	return d.output
}

//Ok returns the state of dry
func (d *Dry) Ok() (bool, error) {
	return d.dockerDaemon.Ok()
}

//Prune runs docker prune
func (d *Dry) Prune() {
	pr, err := d.dockerDaemon.Prune()
	if err == nil {
		d.cache.Add(pruneReport, pr, 30*time.Second)
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error running prune. %s</>", err))
	}
}

//PruneReport returns docker prune report, if any available
func (d *Dry) PruneReport() *drydocker.PruneReport {
	if pr, ok := d.cache.Get(pruneReport); ok {
		return pr.(*drydocker.PruneReport)
	}
	return nil
}

//RemoveAllStoppedContainers removes all stopped containers
func (d *Dry) RemoveAllStoppedContainers() {
	d.appmessage(fmt.Sprintf("<red>Removing all stopped containers</>"))
	if count, err := d.dockerDaemon.RemoveAllStoppedContainers(); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed %d stopped containers</>", count))
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error removing all stopped containers. %s</>", err))
	}
}

//RemoveDanglingImages removes dangling images
func (d *Dry) RemoveDanglingImages() {

	d.appmessage("<red>Removing dangling images</>")
	if count, err := d.dockerDaemon.RemoveDanglingImages(); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed %d dangling images</>", count))
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error removing dangling images. %s</>", err))
	}
}

//RemoveImage removes the Docker image with the given id
func (d *Dry) RemoveImage(id string, force bool) {
	shortID := drydocker.TruncateID(id)
	d.appmessage(fmt.Sprintf("<red>Removing image:</> <white>%s</>", shortID))
	if _, err := d.dockerDaemon.Rmi(id, force); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed image:</> <white>%s</>", shortID))
	} else {
		d.appmessage(fmt.Sprintf("<red>Error removing image </><white>%s: %s</>", shortID, err.Error()))
	}
}

//RemoveNetwork removes the Docker network with the given id
func (d *Dry) RemoveNetwork(id string) {
	shortID := drydocker.TruncateID(id)
	d.appmessage(fmt.Sprintf("<red>Removing network:</> <white>%s</>", shortID))
	if err := d.dockerDaemon.RemoveNetwork(id); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed network:</> <white>%s</>", shortID))
	} else {
		d.appmessage(fmt.Sprintf("<red>Error network image </><white>%s: %s</>", shortID, err.Error()))
	}
}

//Rm removes the container with the given id
func (d *Dry) Rm(id string) {
	shortID := drydocker.TruncateID(id)
	d.actionMessage(shortID, "Removing")
	if err := d.dockerDaemon.Rm(id); err == nil {
		d.actionMessage(shortID, "Removed")
	} else {
		d.errorMessage(shortID, "removing", err)
	}
}

//ServiceInspect returns information about the service with the given ID
func (d *Dry) ServiceInspect(id string) (*swarm.Service, error) {
	return d.dockerDaemon.Service(id)
}

//ServiceLogs retrieves the log of the service with the given id
func (d *Dry) ServiceLogs(id string) (io.ReadCloser, error) {
	return d.dockerDaemon.ServiceLogs(id)
}

//ShowMainView changes the state of dry to show the main view, main views are
//the container list, the image list or the network list
func (d *Dry) ShowMainView() {
	d.changeViewMode(d.state.previousViewMode)
}

//ShowContainers changes the state of dry to show the container list
func (d *Dry) ShowContainers() {
	d.changeViewMode(Main)
}

//ShowDiskUsage changes the state of dry to show docker disk usage
func (d *Dry) ShowDiskUsage() {
	d.changeViewMode(DiskUsage)
}

//ShowDockerEvents changes the state of dry to show the log of docker events
func (d *Dry) ShowDockerEvents() {
	d.changeViewMode(EventsMode)
}

//ShowHelp changes the state of dry to show the extended help
func (d *Dry) ShowHelp() {
	d.changeViewMode(HelpMode)
}

//ShowImages changes the state of dry to show the list of Docker images reported
//by the daemon
func (d *Dry) ShowImages() {
	d.changeViewMode(Images)
}

//ShowInfo retrieves Docker Host info.
func (d *Dry) ShowInfo() error {
	info, err := d.dockerDaemon.Info()
	if err == nil {
		d.changeViewMode(InfoMode)
		d.info = info
		return nil
	}
	return err

}

//ShowMonitor changes the state of dry to show the containers monitor
func (d *Dry) ShowMonitor() {
	d.changeViewMode(Monitor)
}

//ShowNetworks changes the state of dry to show the list of Docker networks reported
//by the daemon
func (d *Dry) ShowNetworks() {
	d.changeViewMode(Networks)
}

//ShowNodes changes the state of dry to show the node list
func (d *Dry) ShowNodes() {
	d.changeViewMode(Nodes)
}

//ShowServices changes the state of dry to show the service list
func (d *Dry) ShowServices() {
	d.changeViewMode(Services)
}

//ShowTasks changes the state of dry to show the stack list
func (d *Dry) ShowStacks() {
	d.changeViewMode(Stacks)
}

//ShowServiceTasks changes the state of dry to show the given service task list
func (d *Dry) ShowServiceTasks(serviceID string) {
	d.widgetRegistry.ServiceTasks.ForService(serviceID)
	d.changeViewMode(ServiceTasks)
}

//ShowStackTasks changes the state of dry to show the given stack task list
func (d *Dry) ShowStackTasks(task string) {
	d.widgetRegistry.StackTasks.ForStack(task)
	d.changeViewMode(StackTasks)
}

//ShowTasks changes the state of dry to show the given node task list
func (d *Dry) ShowTasks(nodeID string) {
	d.widgetRegistry.NodeTasks.ForNode(nodeID)
	d.changeViewMode(Tasks)
}

func (d *Dry) startDry() {
	de := dockerEventsListener{d}
	de.init()
}

func (d *Dry) appmessage(message string) {
	go func() {
		select {
		case d.output <- message:
		default:
		}
	}()
}

func (d *Dry) actionMessage(cid interface{}, action string) {
	d.appmessage(fmt.Sprintf("<red>%s container with id </><white>%v</>",
		action, cid))
}

func (d *Dry) errorMessage(cid interface{}, action string, err error) {
	d.appmessage(
		fmt.Sprintf(
			"<red>Error %s container </><white>%v. %s</>",
			action, cid, err.Error()))
}

func (d *Dry) viewMode() viewMode {
	d.state.RLock()
	defer d.state.RUnlock()
	return d.state.viewMode
}

func newDry(screen *ui.Screen, d *drydocker.DockerDaemon) (*Dry, error) {
	dockerEvents, dockerEventsDone, err := d.Events()
	c := cache.New(5*time.Minute, 30*time.Second)
	if err == nil {

		state := &state{
			viewMode:         Main,
			previousViewMode: Main,
		}
		app := &Dry{}
		app.widgetRegistry = NewWidgetRegistry(d)
		app.state = state
		app.dockerDaemon = d
		app.output = make(chan string)
		app.dockerEvents = dockerEvents
		app.dockerEventsDone = dockerEventsDone
		app.cache = c
		app.startDry()
		return app, nil
	}
	return nil, err
}

//NewDry creates a new dry application
func NewDry(screen *ui.Screen, env *drydocker.Env) (*Dry, error) {
	d, err := drydocker.ConnectToDaemon(env)
	if err != nil {
		return nil, err
	}
	return newDry(screen, d)
}
<commit_msg>Minor<commit_after>package app

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

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/events"
	"github.com/docker/docker/api/types/swarm"
	drydocker "github.com/moncho/dry/docker"
	"github.com/moncho/dry/ui"
	cache "github.com/patrickmn/go-cache"
)

// state tracks dry state
type state struct {
	sync.RWMutex
	previousViewMode viewMode
	viewMode         viewMode
}

//Dry represents the application.
type Dry struct {
	widgetRegistry   *WidgetRegistry
	dockerDaemon     drydocker.ContainerDaemon
	dockerEvents     <-chan events.Message
	dockerEventsDone chan<- struct{}
	info             types.Info
	output           chan string
	state            *state
	//cache is a potential replacement for state
	cache *cache.Cache
}

//changeViewMode changes the view mode of dry and refreshes the screen
func (d *Dry) changeViewMode(newViewMode viewMode) {
	d.SetViewMode(newViewMode)
	refreshScreen()
}

//SetViewMode changes the view mode of dry
func (d *Dry) SetViewMode(newViewMode viewMode) {
	d.state.Lock()
	defer d.state.Unlock()
	//If the new view is one of the main screens, it must be
	//considered as the view to go back to.
	if newViewMode.isMainScreen() {
		d.state.previousViewMode = newViewMode
	}
	d.state.viewMode = newViewMode
}

//Close closes dry, releasing any resources held by it
func (d *Dry) Close() {
	close(d.dockerEventsDone)
	close(d.output)
}

//OuputChannel returns the channel where dry messages are written
func (d *Dry) OuputChannel() <-chan string {
	return d.output
}

//Ok returns the state of dry
func (d *Dry) Ok() (bool, error) {
	return d.dockerDaemon.Ok()
}

//Prune runs docker prune
func (d *Dry) Prune() {
	pr, err := d.dockerDaemon.Prune()
	if err == nil {
		d.cache.Add(pruneReport, pr, 30*time.Second)
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error running prune. %s</>", err))
	}
}

//PruneReport returns docker prune report, if any available
func (d *Dry) PruneReport() *drydocker.PruneReport {
	if pr, ok := d.cache.Get(pruneReport); ok {
		return pr.(*drydocker.PruneReport)
	}
	return nil
}

//RemoveAllStoppedContainers removes all stopped containers
func (d *Dry) RemoveAllStoppedContainers() {
	d.appmessage(fmt.Sprintf("<red>Removing all stopped containers</>"))
	if count, err := d.dockerDaemon.RemoveAllStoppedContainers(); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed %d stopped containers</>", count))
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error removing all stopped containers. %s</>", err))
	}
}

//RemoveDanglingImages removes dangling images
func (d *Dry) RemoveDanglingImages() {

	d.appmessage("<red>Removing dangling images</>")
	if count, err := d.dockerDaemon.RemoveDanglingImages(); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed %d dangling images</>", count))
	} else {
		d.appmessage(
			fmt.Sprintf(
				"<red>Error removing dangling images. %s</>", err))
	}
}

//RemoveImage removes the Docker image with the given id
func (d *Dry) RemoveImage(id string, force bool) {
	shortID := drydocker.TruncateID(id)
	d.appmessage(fmt.Sprintf("<red>Removing image:</> <white>%s</>", shortID))
	if _, err := d.dockerDaemon.Rmi(id, force); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed image:</> <white>%s</>", shortID))
	} else {
		d.appmessage(fmt.Sprintf("<red>Error removing image </><white>%s: %s</>", shortID, err.Error()))
	}
}

//RemoveNetwork removes the Docker network with the given id
func (d *Dry) RemoveNetwork(id string) {
	shortID := drydocker.TruncateID(id)
	d.appmessage(fmt.Sprintf("<red>Removing network:</> <white>%s</>", shortID))
	if err := d.dockerDaemon.RemoveNetwork(id); err == nil {
		d.appmessage(fmt.Sprintf("<red>Removed network:</> <white>%s</>", shortID))
	} else {
		d.appmessage(fmt.Sprintf("<red>Error network image </><white>%s: %s</>", shortID, err.Error()))
	}
}

//Rm removes the container with the given id
func (d *Dry) Rm(id string) {
	shortID := drydocker.TruncateID(id)
	d.actionMessage(shortID, "Removing")
	if err := d.dockerDaemon.Rm(id); err == nil {
		d.actionMessage(shortID, "Removed")
	} else {
		d.errorMessage(shortID, "removing", err)
	}
}

//ServiceInspect returns information about the service with the given ID
func (d *Dry) ServiceInspect(id string) (*swarm.Service, error) {
	return d.dockerDaemon.Service(id)
}

//ServiceLogs retrieves the log of the service with the given id
func (d *Dry) ServiceLogs(id string) (io.ReadCloser, error) {
	return d.dockerDaemon.ServiceLogs(id)
}

//ShowMainView changes the state of dry to show the main view, main views are
//the container list, the image list or the network list
func (d *Dry) ShowMainView() {
	d.changeViewMode(d.state.previousViewMode)
}

//ShowContainers changes the state of dry to show the container list
func (d *Dry) ShowContainers() {
	d.changeViewMode(Main)
}

//ShowDiskUsage changes the state of dry to show docker disk usage
func (d *Dry) ShowDiskUsage() {
	d.changeViewMode(DiskUsage)
}

//ShowDockerEvents changes the state of dry to show the log of docker events
func (d *Dry) ShowDockerEvents() {
	d.changeViewMode(EventsMode)
}

//ShowHelp changes the state of dry to show the extended help
func (d *Dry) ShowHelp() {
	d.changeViewMode(HelpMode)
}

//ShowImages changes the state of dry to show the list of Docker images reported
//by the daemon
func (d *Dry) ShowImages() {
	d.changeViewMode(Images)
}

//ShowInfo retrieves Docker Host info.
func (d *Dry) ShowInfo() error {
	info, err := d.dockerDaemon.Info()
	if err == nil {
		d.changeViewMode(InfoMode)
		d.info = info
		return nil
	}
	return err

}

//ShowMonitor changes the state of dry to show the containers monitor
func (d *Dry) ShowMonitor() {
	d.changeViewMode(Monitor)
}

//ShowNetworks changes the state of dry to show the list of Docker networks reported
//by the daemon
func (d *Dry) ShowNetworks() {
	d.changeViewMode(Networks)
}

//ShowNodes changes the state of dry to show the node list
func (d *Dry) ShowNodes() {
	d.changeViewMode(Nodes)
}

//ShowServices changes the state of dry to show the service list
func (d *Dry) ShowServices() {
	d.changeViewMode(Services)
}

//ShowStacks changes the state of dry to show the stack list
func (d *Dry) ShowStacks() {
	d.changeViewMode(Stacks)
}

//ShowServiceTasks changes the state of dry to show the given service task list
func (d *Dry) ShowServiceTasks(serviceID string) {
	d.widgetRegistry.ServiceTasks.ForService(serviceID)
	d.changeViewMode(ServiceTasks)
}

//ShowStackTasks changes the state of dry to show the given stack task list
func (d *Dry) ShowStackTasks(task string) {
	d.widgetRegistry.StackTasks.ForStack(task)
	d.changeViewMode(StackTasks)
}

//ShowTasks changes the state of dry to show the given node task list
func (d *Dry) ShowTasks(nodeID string) {
	d.widgetRegistry.NodeTasks.ForNode(nodeID)
	d.changeViewMode(Tasks)
}

func (d *Dry) startDry() {
	de := dockerEventsListener{d}
	de.init()
}

func (d *Dry) appmessage(message string) {
	go func() {
		select {
		case d.output <- message:
		default:
		}
	}()
}

func (d *Dry) actionMessage(cid interface{}, action string) {
	d.appmessage(fmt.Sprintf("<red>%s container with id </><white>%v</>",
		action, cid))
}

func (d *Dry) errorMessage(cid interface{}, action string, err error) {
	d.appmessage(
		fmt.Sprintf(
			"<red>Error %s container </><white>%v. %s</>",
			action, cid, err.Error()))
}

func (d *Dry) viewMode() viewMode {
	d.state.RLock()
	defer d.state.RUnlock()
	return d.state.viewMode
}

func newDry(screen *ui.Screen, d *drydocker.DockerDaemon) (*Dry, error) {
	dockerEvents, dockerEventsDone, err := d.Events()
	c := cache.New(5*time.Minute, 30*time.Second)
	if err == nil {

		state := &state{
			viewMode:         Main,
			previousViewMode: Main,
		}
		app := &Dry{}
		app.widgetRegistry = NewWidgetRegistry(d)
		app.state = state
		app.dockerDaemon = d
		app.output = make(chan string)
		app.dockerEvents = dockerEvents
		app.dockerEventsDone = dockerEventsDone
		app.cache = c
		app.startDry()
		return app, nil
	}
	return nil, err
}

//NewDry creates a new dry application
func NewDry(screen *ui.Screen, env *drydocker.Env) (*Dry, error) {
	d, err := drydocker.ConnectToDaemon(env)
	if err != nil {
		return nil, err
	}
	return newDry(screen, d)
}
<|endoftext|>


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

package safehttp

import (
	"net/http"
)

// TODO: add the missing methods
const (
	// HTTP GET request
	MethodGet = "GET"
	// HTTP Post request
	MethodPost = "POST"
)

// ServeMux is an HTTP request multiplexer. It matches the URL of each incoming
// request against a list of registered patterns and calls the handler for
// the pattern that most closely matches the URL.
//
// When creating the multiplexer, the user needs to specify a list of allowed
// domains. The server will only serve requests target to those domains and
// otherwise will reply with HTTP 404 Not Found.
// TODO(@mihalimara22, @mattiasgrenfeldt): add a link to docs/ explaining
// why this is done.
//
// Patterns names are fixed, rooted paths, like "/favicon.ico", or rooted
// subtrees like "/images/" (note the trailing slash). Longer patterns take
// precedence over shorter ones, so that if there are handlers registered for
// both "/images/" and "/images/thumbnails/", the latter handler will be called
// for paths beginning "/images/thumbnails/" and the former will receive
// requests for any other paths in the "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree, the
// pattern "/" matches all paths not matched by other registered patterns,
// not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the subtree
// root without its trailing slash, ServeMux redirects that request to
// the subtree root (adding the trailing slash). This behavior can be overridden
// with a separate registration for the path without the trailing slash. For
// example, registering "/images/" causes ServeMux to redirect a request for
// "/images" to "/images/", unless "/images" has been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to URLs
// on that host only. This host name must be in the list of allowed domains passed
// when creating the ServeMux. Host-specific patterns take precedence over general
// patterns, so that a handler might register for the two patterns "/codesearch"
// and "codesearch.google.com/" without also taking over requests for
// "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
//
// Multiple handlers can be registered for a single pattern, as long as they
// handle different HTTP methods.
type ServeMux struct {
	mux     *http.ServeMux
	domains map[string]bool
	disp    Dispatcher

	// Maps patterns to handlers supporting multiple HTTP methods.
	handlers map[string]methodHandler

	// Maps interceptor key to interceptor.
	interceps map[string]Interceptor
}

// NewServeMux allocates and returns a new ServeMux
func NewServeMux(d Dispatcher, domains ...string) *ServeMux {
	// TODO(@mattiasgrenfeldt, @mihalimara22): make domains a variadic of string **literals**.
	dm := map[string]bool{}
	for _, host := range domains {
		dm[host] = true
	}
	return &ServeMux{
		mux:       http.NewServeMux(),
		domains:   dm,
		disp:      d,
		handlers:  map[string]methodHandler{},
		interceps: map[string]Interceptor{},
	}
}

// Handle registers a handler for the given pattern and method. If another
// handler is already registered for the same pattern and method, Handle panics.
func (m *ServeMux) Handle(pattern string, method string, h Handler) {
	mh, ok := m.handlers[pattern]
	if !ok {
		mh := methodHandler{
			handlers:     map[string]Handler{method: h},
			domains:      m.domains,
			disp:         m.disp,
			muxInterceps: m.interceps,
		}

		m.handlers[pattern] = mh
		m.mux.Handle(pattern, mh)
		return
	}

	if _, ok := mh.handlers[method]; ok {
		panic("method already registered")
	}
	mh.handlers[method] = h
}

// Install installs an Interceptor. Interceptor keys need to be unique. If an
// Interceptor with the same key has already been installed, Install panics.
//
// TODO(@empijei, @grenfeldt, @kele, @mihalimara22): Right now you could install
// the same interceptor twice with different keys, we need to figure out how
// exactly we want to avoid that and how we define key uniqueness.
func (m *ServeMux) Install(key string, i Interceptor) {
	if _, exists := m.interceps[key]; exists {
		panic("interceptor with same key already installed")
	}
	m.interceps[key] = i
}

// ServeHTTP dispatches the request to the handler whose method matches the
// incoming request and whose pattern most closely matches the request URL.
func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mux.ServeHTTP(w, r)
}

// methodHandler is a collection of handlers based on the request method.
type methodHandler struct {
	// Maps an HTTP method to its handler
	handlers     map[string]Handler
	domains      map[string]bool
	disp         Dispatcher
	muxInterceps map[string]Interceptor
}

// ServeHTTP dispatches the request to the handler associated with
// the incoming request's method after calling the Before function of all
// ServeMux interceptors the handler is registered on.
func (m methodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	rw := NewResponseWriter(m.disp, w, m.muxInterceps)
	ir := NewIncomingRequest(r)

	if !m.domains[r.Host] {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	h, ok := m.handlers[r.Method]
	if !ok {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	// TODO: run After/Commit stages if Before panics.
	defer func() {
		if r := recover(); r != nil {
			rw.ServerError(StatusInternalServerError)
		}
	}()

	for _, intercep := range m.muxInterceps {
		if res := intercep.Before(rw, ir); res.written {
			return
		}
	}

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

package safehttp

import (
	"net/http"
)

// TODO: add the missing methods
const (
	// HTTP GET request
	MethodGet = "GET"
	// HTTP Post request
	MethodPost = "POST"
)

// ServeMux is an HTTP request multiplexer. It matches the URL of each incoming
// request against a list of registered patterns and calls the handler for
// the pattern that most closely matches the URL.
//
// When creating the multiplexer, the user needs to specify a list of allowed
// domains. The server will only serve requests target to those domains and
// otherwise will reply with HTTP 404 Not Found.
// TODO(@mihalimara22, @mattiasgrenfeldt): add a link to docs/ explaining
// why this is done.
//
// Patterns names are fixed, rooted paths, like "/favicon.ico", or rooted
// subtrees like "/images/" (note the trailing slash). Longer patterns take
// precedence over shorter ones, so that if there are handlers registered for
// both "/images/" and "/images/thumbnails/", the latter handler will be called
// for paths beginning "/images/thumbnails/" and the former will receive
// requests for any other paths in the "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree, the
// pattern "/" matches all paths not matched by other registered patterns,
// not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the subtree
// root without its trailing slash, ServeMux redirects that request to
// the subtree root (adding the trailing slash). This behavior can be overridden
// with a separate registration for the path without the trailing slash. For
// example, registering "/images/" causes ServeMux to redirect a request for
// "/images" to "/images/", unless "/images" has been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to URLs
// on that host only. This host name must be in the list of allowed domains passed
// when creating the ServeMux. Host-specific patterns take precedence over general
// patterns, so that a handler might register for the two patterns "/codesearch"
// and "codesearch.google.com/" without also taking over requests for
// "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
//
// Multiple handlers can be registered for a single pattern, as long as they
// handle different HTTP methods.
type ServeMux struct {
	mux     *http.ServeMux
	domains map[string]bool
	disp    Dispatcher

	// Maps patterns to handlers supporting multiple HTTP methods.
	handlers map[string]methodHandler

	// Maps interceptor key to interceptor.
	interceps map[string]Interceptor
}

// NewServeMux allocates and returns a new ServeMux
func NewServeMux(d Dispatcher, domains ...string) *ServeMux {
	// TODO(@mattiasgrenfeldt, @mihalimara22): make domains a variadic of string **literals**.
	dm := map[string]bool{}
	for _, host := range domains {
		dm[host] = true
	}
	return &ServeMux{
		mux:       http.NewServeMux(),
		domains:   dm,
		disp:      d,
		handlers:  map[string]methodHandler{},
		interceps: map[string]Interceptor{},
	}
}

// Handle registers a handler for the given pattern and method. If another
// handler is already registered for the same pattern and method, Handle panics.
func (m *ServeMux) Handle(pattern string, method string, h Handler) {
	mh, ok := m.handlers[pattern]
	if !ok {
		mh := methodHandler{
			handlers:     map[string]Handler{method: h},
			domains:      m.domains,
			disp:         m.disp,
			muxInterceps: m.interceps,
		}

		m.handlers[pattern] = mh
		m.mux.Handle(pattern, mh)
		return
	}

	if _, ok := mh.handlers[method]; ok {
		panic("method already registered")
	}
	mh.handlers[method] = h
}

// Install installs an Interceptor. Interceptor keys need to be unique. If an
// Interceptor with the same key has already been installed, Install panics.
//
// TODO(@empijei, @grenfeldt, @kele, @mihalimara22): Right now you could install
// the same interceptor twice with different keys, we need to figure out how
// exactly we want to avoid that and how we define key uniqueness.
func (m *ServeMux) Install(key string, i Interceptor) {
	if _, exists := m.interceps[key]; exists {
		panic("interceptor with same key already installed")
	}
	m.interceps[key] = i
}

// ServeHTTP dispatches the request to the handler whose method matches the
// incoming request and whose pattern most closely matches the request URL.
func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mux.ServeHTTP(w, r)
}

// methodHandler is a collection of handlers based on the request method.
type methodHandler struct {
	// Maps an HTTP method to its handler
	handlers     map[string]Handler
	domains      map[string]bool
	disp         Dispatcher
	muxInterceps map[string]Interceptor
}

// ServeHTTP dispatches the request to the handler associated with
// the incoming request's method after calling the Before function of all
// ServeMux interceptors the handler is registered on.
func (m methodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	rw := NewResponseWriter(m.disp, w, m.muxInterceps)
	ir := NewIncomingRequest(r)

	if !m.domains[r.Host] {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	h, ok := m.handlers[r.Method]
	if !ok {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	// The `net/http` package recovers handler panics, but we cannot rely on that behavior here.
	// The reason is, we might need to run After/Commit stages of the interceptors before we
	// respond with a 500 Internal Server Error.
	defer func() {
		if r := recover(); r != nil {
			rw.ServerError(StatusInternalServerError)
		}
	}()

	for _, intercep := range m.muxInterceps {
		if res := intercep.Before(rw, ir); res.written {
			return
		}
	}

	h.ServeHTTP(rw, ir)
}
<|endoftext|>


<commit_before>package archive

import (
	"fmt"
	"io"
	"path"

	"pault.ag/go/blobstore"
	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/transput"
)

// New {{{

func New(path string) (*Archive, error) {
	store, err := blobstore.Load(path)
	if err != nil {
		return nil, err
	}
	return &Archive{
		store: *store,
	}, nil
}

// }}}

// Archive magic {{{

type Archive struct {
	store blobstore.Store
}

func (a Archive) Suite(name string) (*Suite, error) {
	/* Get the Release / InRelease */
	inRelease := Release{}
	components := map[string]*Component{}

	fd, err := a.store.OpenPath(path.Join("dists", name, "InRelease"))
	if err == nil {
		defer fd.Close()
		if err := control.Unmarshal(&inRelease, fd); err != nil {
			return nil, err
		}

		for _, name := range inRelease.Components {
			components[name] = &Component{Packages: []Package{}}
		}
	}

	suite := Suite{
		Name: name,

		release:    inRelease,
		Components: components,
	}

	suite.features.Hashes = []string{"sha256", "sha1"}

	return &suite, nil
}

func (a Archive) encode(suite Suite, path string, data interface{}) (*blobstore.Object, []control.FileHash, error) {
	writer, err := a.store.Create()
	if err != nil {
		return nil, nil, err
	}
	defer writer.Close()

	hashers := []*transput.Hasher{}
	writers := []io.Writer{writer}

	for _, algorithm := range suite.features.Hashes {
		hasher, err := transput.NewHasher(algorithm)
		if err != nil {
			return nil, nil, err
		}
		hashers = append(hashers, hasher)
		writers = append(writers, hasher)
	}

	multiWriter := io.MultiWriter(writers...)

	encoder, err := control.NewEncoder(multiWriter)
	if err != nil {
		return nil, nil, err
	}

	if err := encoder.Encode(data); err != nil {
		return nil, nil, err
	}

	obj, err := a.store.Commit(*writer)
	if err != nil {
		return nil, nil, err
	}

	fileHashs := []control.FileHash{}
	for _, hasher := range hashers {
		fileHashs = append(fileHashs, control.FileHashFromHasher(path, *hasher))
	}

	return obj, fileHashs, nil
}

func (a Archive) Engross(suite Suite) (map[string]blobstore.Object, error) {
	files := map[string]blobstore.Object{}

	release := Release{
		Description:   "",
		Origin:        "",
		Label:         "",
		Version:       "",
		Suite:         suite.Name,
		Codename:      "",
		Components:    suite.ComponenetNames(),
		Architectures: suite.Arches(),
		SHA256:        []control.SHA256FileHash{},
		SHA1:          []control.SHA1FileHash{},
		SHA512:        []control.SHA512FileHash{},
		MD5Sum:        []control.MD5FileHash{},
	}

	for name, component := range suite.Components {
		for arch, pkgs := range component.ByArch() {
			filePath := path.Join("dists", suite.Name, name,
				fmt.Sprintf("binary-%s", arch), "Packages")

			obj, hashes, err := a.encode(suite, filePath, pkgs)
			if err != nil {
				return nil, err
			}

			for _, hash := range hashes {
				if err := release.AddHash(hash); err != nil {
					return nil, err
				}
			}

			files[filePath] = *obj
		}
	}

	filePath := path.Join("dists", suite.Name, "Release")
	obj, _, err := a.encode(suite, filePath, release)
	if err != nil {
		return nil, err
	}
	files[filePath] = *obj

	return files, nil
}

func (a Archive) Link(blobs map[string]blobstore.Object) error {
	for p, obj := range blobs {
		if err := a.store.Link(obj, p); err != nil {
			return err
		}
	}
	return nil
}

// }}}

// Suite magic {{{

type Suite struct {
	Name string

	release    Release
	Components map[string]*Component

	features struct {
		Hashes []string
	}
}

func (s Suite) Arches() []dependency.Arch {
	ret := map[dependency.Arch]bool{}
	for _, component := range s.Components {
		for _, arch := range component.Arches() {
			ret[arch] = true
		}
	}
	r := []dependency.Arch{}
	for arch, _ := range ret {
		r = append(r, arch)
	}
	return r
}

func (s Suite) ComponenetNames() []string {
	ret := []string{}
	for name, _ := range s.Components {
		ret = append(ret, name)
	}
	return ret
}

func (s Suite) Add(name string, pkg Package) {
	if _, ok := s.Components[name]; !ok {
		s.Components[name] = &Component{Packages: []Package{}}
	}
	s.Components[name].Add(pkg)
}

// }}}

// Component magic {{{

type Component struct {
	Packages []Package
}

func (c *Component) ByArch() map[dependency.Arch][]Package {
	ret := map[dependency.Arch][]Package{}

	for _, pkg := range c.Packages {
		packages := ret[pkg.Architecture]
		ret[pkg.Architecture] = append(packages, pkg)
	}

	return ret
}

func (c *Component) Arches() []dependency.Arch {
	ret := []dependency.Arch{}
	for _, pkg := range c.Packages {
		ret = append(ret, pkg.Architecture)
	}
	return ret
}

func (c *Component) Add(p Package) {
	c.Packages = append(c.Packages, p)
}

// }}}

// vim: foldmethod=marker
<commit_msg>overhaul signing<commit_after>package archive

import (
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/crypto/openpgp"

	"pault.ag/go/blobstore"
	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/transput"
)

// New {{{

func New(path string) (*Archive, error) {
	store, err := blobstore.Load(path)
	if err != nil {
		return nil, err
	}

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

	return &Archive{
		store: *store,
	}, nil
}

func NewWritable(path string, keyring string, keyid uint64) (*Archive, error) {
	archive, err := New(path)
	if err != nil {
		return nil, err
	}

	fd, err := os.Open(keyring)
	if err != nil {
		return nil, err
	}

	el, err := openpgp.ReadKeyRing(fd)
	if err != nil {
		return nil, err
	}

	keys := el.KeysById(keyid)

	if len(keys) == 0 {
		return nil, fmt.Errorf("No keys matched that key ID")
	}

	if len(keys) != 1 {
		return nil, fmt.Errorf("Too many keys matched that key ID")
	}

	archive.signingKey = keys[0].Entity

	return archive, err
}

// }}}

// Archive magic {{{

type Archive struct {
	store      blobstore.Store
	signingKey *openpgp.Entity
}

func (a Archive) Suite(name string) (*Suite, error) {
	/* Get the Release / InRelease */
	inRelease := Release{}
	components := map[string]*Component{}

	fd, err := a.store.OpenPath(path.Join("dists", name, "InRelease"))
	if err == nil {
		defer fd.Close()
		if err := control.Unmarshal(&inRelease, fd); err != nil {
			return nil, err
		}

		for _, name := range inRelease.Components {
			components[name] = &Component{Packages: []Package{}}
		}
	}

	suite := Suite{
		Name: name,

		release:    inRelease,
		Components: components,
	}

	suite.features.Hashes = []string{"sha256", "sha1"}

	return &suite, nil
}

func (a Archive) encodeHashedBySuite(path string, suite Suite, data interface{}) (*blobstore.Object, []control.FileHash, error) {

	hashers := []*transput.Hasher{}
	for _, algorithm := range suite.features.Hashes {
		hasher, err := transput.NewHasher(algorithm)
		if err != nil {
			return nil, nil, err
		}
		hashers = append(hashers, hasher)
	}

	return a.encodeHashed(path, hashers, data)
}

func (a Archive) encodeHashed(path string, hashers []*transput.Hasher, data interface{}) (*blobstore.Object, []control.FileHash, error) {

	writers := []io.Writer{}
	for _, hasher := range hashers {
		writers = append(writers, hasher)
	}

	obj, err := a.encode(data, io.MultiWriter(writers...))
	if err != nil {
		return nil, nil, err
	}

	fileHashs := []control.FileHash{}
	for _, hasher := range hashers {
		fileHashs = append(fileHashs, control.FileHashFromHasher(path, *hasher))
	}

	return obj, fileHashs, nil
}

func (a Archive) encode(data interface{}, tee io.Writer) (*blobstore.Object, error) {
	writer, err := a.store.Create()
	if err != nil {
		return nil, err
	}
	defer writer.Close()

	var target io.Writer = writer
	if tee != nil {
		target = io.MultiWriter(writer, tee)
	}

	encoder, err := control.NewEncoder(target)
	if err != nil {
		return nil, err
	}

	if err := encoder.Encode(data); err != nil {
		return nil, err
	}

	obj, err := a.store.Commit(*writer)
	if err != nil {
		return nil, err
	}

	return obj, nil
}

func (a Archive) Engross(suite Suite) (map[string]blobstore.Object, error) {
	files := map[string]blobstore.Object{}

	release := Release{
		Description:   "",
		Origin:        "",
		Label:         "",
		Version:       "",
		Suite:         suite.Name,
		Codename:      "",
		Components:    suite.ComponenetNames(),
		Architectures: suite.Arches(),
		SHA256:        []control.SHA256FileHash{},
		SHA1:          []control.SHA1FileHash{},
		SHA512:        []control.SHA512FileHash{},
		MD5Sum:        []control.MD5FileHash{},
	}

	for name, component := range suite.Components {
		for arch, pkgs := range component.ByArch() {
			filePath := path.Join("dists", suite.Name, name,
				fmt.Sprintf("binary-%s", arch), "Packages")

			obj, hashes, err := a.encodeHashedBySuite(filePath, suite, pkgs)
			if err != nil {
				return nil, err
			}

			for _, hash := range hashes {
				if err := release.AddHash(hash); err != nil {
					return nil, err
				}
			}

			files[filePath] = *obj
		}
	}

	filePath := path.Join("dists", suite.Name, "Release")
	obj, sig, err := a.encodeSigned(release)
	if err != nil {
		return nil, err
	}
	files[filePath] = *obj
	files[fmt.Sprintf("%s.asc", filePath)] = *sig

	return files, nil
}

func (a Archive) Link(blobs map[string]blobstore.Object) error {
	for p, obj := range blobs {
		if err := a.store.Link(obj, p); err != nil {
			return err
		}
	}
	return nil
}

// }}}

// Suite magic {{{

type Suite struct {
	Name string

	release    Release
	Components map[string]*Component

	features struct {
		Hashes []string
	}
}

func (s Suite) Arches() []dependency.Arch {
	ret := map[dependency.Arch]bool{}
	for _, component := range s.Components {
		for _, arch := range component.Arches() {
			ret[arch] = true
		}
	}
	r := []dependency.Arch{}
	for arch, _ := range ret {
		r = append(r, arch)
	}
	return r
}

func (s Suite) ComponenetNames() []string {
	ret := []string{}
	for name, _ := range s.Components {
		ret = append(ret, name)
	}
	return ret
}

func (s Suite) Add(name string, pkg Package) {
	if _, ok := s.Components[name]; !ok {
		s.Components[name] = &Component{Packages: []Package{}}
	}
	s.Components[name].Add(pkg)
}

// }}}

// Component magic {{{

type Component struct {
	Packages []Package
}

func (c *Component) ByArch() map[dependency.Arch][]Package {
	ret := map[dependency.Arch][]Package{}

	for _, pkg := range c.Packages {
		packages := ret[pkg.Architecture]
		ret[pkg.Architecture] = append(packages, pkg)
	}

	return ret
}

func (c *Component) Arches() []dependency.Arch {
	ret := []dependency.Arch{}
	for _, pkg := range c.Packages {
		ret = append(ret, pkg.Architecture)
	}
	return ret
}

func (c *Component) Add(p Package) {
	c.Packages = append(c.Packages, p)
}

// }}}

// vim: foldmethod=marker
<|endoftext|>


<commit_before>package docker

import (
	"errors"
	"io"
	"io/ioutil"
	"os/exec"
)

type Archive io.Reader

type Compression uint32

const (
	Uncompressed Compression = iota
	Bzip2
	Gzip
)

func (compression *Compression) Flag() string {
	switch *compression {
	case Bzip2:
		return "j"
	case Gzip:
		return "z"
	}
	return ""
}

func Tar(path string, compression Compression) (io.Reader, error) {
	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
	return CmdStream(cmd)
}

func Untar(archive io.Reader, path string) error {
	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
	cmd.Stdin = archive
	output, err := cmd.CombinedOutput()
	if err != nil {
		return errors.New(err.Error() + ": " + string(output))
	}
	return nil
}

// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}
	pipeR, pipeW := io.Pipe()
	errChan := make(chan []byte)
	go func() {
		errText, e := ioutil.ReadAll(stderr)
		if e != nil {
			errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
		}
		errChan <- errText
	}()
	go func() {
		_, err := io.Copy(pipeW, stdout)
		if err != nil {
			pipeW.CloseWithError(err)
		}
		errText := <-errChan
		if err := cmd.Wait(); err != nil {
			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
		} else {
			pipeW.Close()
		}
	}()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	return pipeR, nil
}
<commit_msg>Comment CmdStream a little bit to facilitate future fixes and contributions<commit_after>package docker

import (
	"errors"
	"io"
	"io/ioutil"
	"os/exec"
)

type Archive io.Reader

type Compression uint32

const (
	Uncompressed Compression = iota
	Bzip2
	Gzip
)

func (compression *Compression) Flag() string {
	switch *compression {
	case Bzip2:
		return "j"
	case Gzip:
		return "z"
	}
	return ""
}

func Tar(path string, compression Compression) (io.Reader, error) {
	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
	return CmdStream(cmd)
}

func Untar(archive io.Reader, path string) error {
	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
	cmd.Stdin = archive
	output, err := cmd.CombinedOutput()
	if err != nil {
		return errors.New(err.Error() + ": " + string(output))
	}
	return nil
}

// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}
	pipeR, pipeW := io.Pipe()
	errChan := make(chan []byte)
	// Collect stderr, we will use it in case of an error
	go func() {
		errText, e := ioutil.ReadAll(stderr)
		if e != nil {
			errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
		}
		errChan <- errText
	}()
	// Copy stdout to the returned pipe
	go func() {
		_, err := io.Copy(pipeW, stdout)
		if err != nil {
			pipeW.CloseWithError(err)
		}
		errText := <-errChan
		if err := cmd.Wait(); err != nil {
			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
		} else {
			pipeW.Close()
		}
	}()
	// Run the command and return the pipe
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	return pipeR, nil
}
<|endoftext|>