<commit_before>package saml2

import (
	"bytes"
	"compress/flate"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"

	"github.com/beevik/etree"
	"github.com/russellhaering/gosaml2/uuid"
)

const issueInstantFormat = "2006-01-02T15:04:05Z"

func (sp *SAMLServiceProvider) buildAuthnRequest(includeSig bool) (*etree.Document, error) {
	authnRequest := &etree.Element{
		Space: "samlp",
		Tag:   "AuthnRequest",
	}

	authnRequest.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
	authnRequest.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")

	arId := uuid.NewV4()

	authnRequest.CreateAttr("ID", "_"+arId.String())
	authnRequest.CreateAttr("Version", "2.0")
	authnRequest.CreateAttr("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
	authnRequest.CreateAttr("AssertionConsumerServiceURL", sp.AssertionConsumerServiceURL)
	authnRequest.CreateAttr("IssueInstant", sp.Clock.Now().UTC().Format(issueInstantFormat))
	authnRequest.CreateAttr("Destination", sp.IdentityProviderSSOURL)

	// NOTE(russell_h): In earlier versions we mistakenly sent the IdentityProviderIssuer
	// in the AuthnRequest. For backwards compatibility we will fall back to that
	// behavior when ServiceProviderIssuer isn't set.
	if sp.ServiceProviderIssuer != "" {
		authnRequest.CreateElement("saml:Issuer").SetText(sp.ServiceProviderIssuer)
	} else {
		authnRequest.CreateElement("saml:Issuer").SetText(sp.IdentityProviderIssuer)
	}

	nameIdPolicy := authnRequest.CreateElement("samlp:NameIDPolicy")
	nameIdPolicy.CreateAttr("AllowCreate", "true")
	nameIdPolicy.CreateAttr("Format", sp.NameIdFormat)

	if sp.RequestedAuthnContext != nil {
		requestedAuthnContext := authnRequest.CreateElement("samlp:RequestedAuthnContext")
		requestedAuthnContext.CreateAttr("Comparison", sp.RequestedAuthnContext.Comparison)

		for _, context := range sp.RequestedAuthnContext.Contexts {
			authnContextClassRef := requestedAuthnContext.CreateElement("saml:AuthnContextClassRef")
			authnContextClassRef.SetText(context)
		}
	}

	doc := etree.NewDocument()

	// Only POST binding includes <Signature> in <AuthnRequest> (includeSig)
	if sp.SignAuthnRequests && includeSig {
		signed, err := sp.SignAuthnRequest(authnRequest)
		if err != nil {
			return nil, err
		}

		doc.SetRoot(signed)
	} else {
		doc.SetRoot(authnRequest)
	}
	return doc, nil
}

func (sp *SAMLServiceProvider) BuildAuthRequestDocument() (*etree.Document, error) {
	return sp.buildAuthnRequest(true)
}

func (sp *SAMLServiceProvider) BuildAuthRequestDocumentNoSig() (*etree.Document, error) {
	return sp.buildAuthnRequest(false)
}

// SignAuthnRequest takes a document, builds a signature, creates another document
// and inserts the signature in it. According to the schema, the position of the
// signature is right after the Issuer [1] then all other children.
//
// [1] https://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd
func (sp *SAMLServiceProvider) SignAuthnRequest(el *etree.Element) (*etree.Element, error) {
	ctx := sp.SigningContext()

	sig, err := ctx.ConstructSignature(el, true)
	if err != nil {
		return nil, err
	}

	ret := el.Copy()

	var children []etree.Token
	children = append(children, ret.Child[0])     // issuer is always first
	children = append(children, sig)              // next is the signature
	children = append(children, ret.Child[1:]...) // then all other children
	ret.Child = children

	return ret, nil
}

// BuildAuthRequest builds <AuthnRequest> for identity provider
func (sp *SAMLServiceProvider) BuildAuthRequest() (string, error) {
	doc, err := sp.BuildAuthRequestDocument()
	if err != nil {
		return "", err
	}
	return doc.WriteToString()
}

func (sp *SAMLServiceProvider) buildAuthURLFromDocument(relayState, binding string, doc *etree.Document) (string, error) {
	parsedUrl, err := url.Parse(sp.IdentityProviderSSOURL)
	if err != nil {
		return "", err
	}

	authnRequest, err := doc.WriteToString()
	if err != nil {
		return "", err
	}

	buf := &bytes.Buffer{}

	fw, err := flate.NewWriter(buf, flate.DefaultCompression)
	if err != nil {
		return "", fmt.Errorf("flate NewWriter error: %v", err)
	}

	_, err = fw.Write([]byte(authnRequest))
	if err != nil {
		return "", fmt.Errorf("flate.Writer Write error: %v", err)
	}

	err = fw.Close()
	if err != nil {
		return "", fmt.Errorf("flate.Writer Close error: %v", err)
	}

	qs := parsedUrl.Query()

	qs.Add("SAMLRequest", base64.StdEncoding.EncodeToString(buf.Bytes()))

	if relayState != "" {
		qs.Add("RelayState", relayState)
	}

	if sp.SignAuthnRequests && binding == BindingHttpRedirect {
		// Sign URL encoded query (see Section 3.4.4.1 DEFLATE Encoding of saml-bindings-2.0-os.pdf)
		ctx := sp.SigningContext()
		qs.Add("SigAlg", ctx.GetSignatureMethodIdentifier())
		var rawSignature []byte
		if rawSignature, err = ctx.SignString(qs.Encode()); err != nil {
			return "", fmt.Errorf("unable to sign query string of redirect URL: %v", err)
		}

		// Now add base64 encoded Signature
		qs.Add("Signature", base64.StdEncoding.EncodeToString(rawSignature))
	}

	parsedUrl.RawQuery = qs.Encode()
	return parsedUrl.String(), nil
}

func (sp *SAMLServiceProvider) BuildAuthURLFromDocument(relayState string, doc *etree.Document) (string, error) {
	return sp.buildAuthURLFromDocument(relayState, BindingHttpPost, doc)
}

func (sp *SAMLServiceProvider) BuildAuthURLRedirect(relayState string, doc *etree.Document) (string, error) {
	return sp.buildAuthURLFromDocument(relayState, BindingHttpRedirect, doc)
}

// BuildAuthURL builds redirect URL to be sent to principal
func (sp *SAMLServiceProvider) BuildAuthURL(relayState string) (string, error) {
	doc, err := sp.BuildAuthRequestDocument()
	if err != nil {
		return "", err
	}
	return sp.BuildAuthURLFromDocument(relayState, doc)
}

// AuthRedirect takes a ResponseWriter and Request from an http interaction and
// redirects to the SAMLServiceProvider's configured IdP, including the
// relayState provided, if any.
func (sp *SAMLServiceProvider) AuthRedirect(w http.ResponseWriter, r *http.Request, relayState string) (err error) {
	url, err := sp.BuildAuthURL(relayState)
	if err != nil {
		return err
	}

	http.Redirect(w, r, url, http.StatusFound)
	return nil
}
<commit_msg>Fix signature calcuation when RelayState is not empty<commit_after>package saml2

import (
	"bytes"
	"compress/flate"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"

	"github.com/beevik/etree"
	"github.com/russellhaering/gosaml2/uuid"
)

const issueInstantFormat = "2006-01-02T15:04:05Z"

func (sp *SAMLServiceProvider) buildAuthnRequest(includeSig bool) (*etree.Document, error) {
	authnRequest := &etree.Element{
		Space: "samlp",
		Tag:   "AuthnRequest",
	}

	authnRequest.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
	authnRequest.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")

	arId := uuid.NewV4()

	authnRequest.CreateAttr("ID", "_"+arId.String())
	authnRequest.CreateAttr("Version", "2.0")
	authnRequest.CreateAttr("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
	authnRequest.CreateAttr("AssertionConsumerServiceURL", sp.AssertionConsumerServiceURL)
	authnRequest.CreateAttr("IssueInstant", sp.Clock.Now().UTC().Format(issueInstantFormat))
	authnRequest.CreateAttr("Destination", sp.IdentityProviderSSOURL)

	// NOTE(russell_h): In earlier versions we mistakenly sent the IdentityProviderIssuer
	// in the AuthnRequest. For backwards compatibility we will fall back to that
	// behavior when ServiceProviderIssuer isn't set.
	if sp.ServiceProviderIssuer != "" {
		authnRequest.CreateElement("saml:Issuer").SetText(sp.ServiceProviderIssuer)
	} else {
		authnRequest.CreateElement("saml:Issuer").SetText(sp.IdentityProviderIssuer)
	}

	nameIdPolicy := authnRequest.CreateElement("samlp:NameIDPolicy")
	nameIdPolicy.CreateAttr("AllowCreate", "true")
	nameIdPolicy.CreateAttr("Format", sp.NameIdFormat)

	if sp.RequestedAuthnContext != nil {
		requestedAuthnContext := authnRequest.CreateElement("samlp:RequestedAuthnContext")
		requestedAuthnContext.CreateAttr("Comparison", sp.RequestedAuthnContext.Comparison)

		for _, context := range sp.RequestedAuthnContext.Contexts {
			authnContextClassRef := requestedAuthnContext.CreateElement("saml:AuthnContextClassRef")
			authnContextClassRef.SetText(context)
		}
	}

	doc := etree.NewDocument()

	// Only POST binding includes <Signature> in <AuthnRequest> (includeSig)
	if sp.SignAuthnRequests && includeSig {
		signed, err := sp.SignAuthnRequest(authnRequest)
		if err != nil {
			return nil, err
		}

		doc.SetRoot(signed)
	} else {
		doc.SetRoot(authnRequest)
	}
	return doc, nil
}

func (sp *SAMLServiceProvider) BuildAuthRequestDocument() (*etree.Document, error) {
	return sp.buildAuthnRequest(true)
}

func (sp *SAMLServiceProvider) BuildAuthRequestDocumentNoSig() (*etree.Document, error) {
	return sp.buildAuthnRequest(false)
}

// SignAuthnRequest takes a document, builds a signature, creates another document
// and inserts the signature in it. According to the schema, the position of the
// signature is right after the Issuer [1] then all other children.
//
// [1] https://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd
func (sp *SAMLServiceProvider) SignAuthnRequest(el *etree.Element) (*etree.Element, error) {
	ctx := sp.SigningContext()

	sig, err := ctx.ConstructSignature(el, true)
	if err != nil {
		return nil, err
	}

	ret := el.Copy()

	var children []etree.Token
	children = append(children, ret.Child[0])     // issuer is always first
	children = append(children, sig)              // next is the signature
	children = append(children, ret.Child[1:]...) // then all other children
	ret.Child = children

	return ret, nil
}

// BuildAuthRequest builds <AuthnRequest> for identity provider
func (sp *SAMLServiceProvider) BuildAuthRequest() (string, error) {
	doc, err := sp.BuildAuthRequestDocument()
	if err != nil {
		return "", err
	}
	return doc.WriteToString()
}

func (sp *SAMLServiceProvider) buildAuthURLFromDocument(relayState, binding string, doc *etree.Document) (string, error) {
	parsedUrl, err := url.Parse(sp.IdentityProviderSSOURL)
	if err != nil {
		return "", err
	}

	authnRequest, err := doc.WriteToString()
	if err != nil {
		return "", err
	}

	buf := &bytes.Buffer{}

	fw, err := flate.NewWriter(buf, flate.DefaultCompression)
	if err != nil {
		return "", fmt.Errorf("flate NewWriter error: %v", err)
	}

	_, err = fw.Write([]byte(authnRequest))
	if err != nil {
		return "", fmt.Errorf("flate.Writer Write error: %v", err)
	}

	err = fw.Close()
	if err != nil {
		return "", fmt.Errorf("flate.Writer Close error: %v", err)
	}

	qs := parsedUrl.Query()

	qs.Add("SAMLRequest", base64.StdEncoding.EncodeToString(buf.Bytes()))

	if relayState != "" {
		qs.Add("RelayState", relayState)
	}

	if sp.SignAuthnRequests && binding == BindingHttpRedirect {
		// Sign URL encoded query (see Section 3.4.4.1 DEFLATE Encoding of saml-bindings-2.0-os.pdf)
		ctx := sp.SigningContext()
		qs.Add("SigAlg", ctx.GetSignatureMethodIdentifier())
		var rawSignature []byte
        //qs.Encode() sorts the keys (See https://golang.org/pkg/net/url/#Values.Encode).
        //If RelayState parameter is present then RelayState parameter
        //will be put first by Encode(). Hence encode them separately and concatenate.
        //Signature string has to have parameters in the order - SAMLRequest=value&RelayState=value&SigAlg=value.
        //(See Section 3.4.4.1 saml-bindings-2.0-os.pdf).
        var orderedParams = []string{"SAMLRequest", "RelayState", "SigAlg"}

        var paramValueMap = make(map[string]string)
        paramValueMap["SAMLRequest"] = base64.StdEncoding.EncodeToString(buf.Bytes())
        if relayState != "" {
            paramValueMap["RelayState"] = relayState
        }
        paramValueMap["SigAlg"] = ctx.GetSignatureMethodIdentifier()

        ss := ""

        for _, k := range orderedParams {
            v, ok := paramValueMap[k]
            if ok {
                //Add the value after URL encoding.
                u := url.Values{}
                u.Add(k, v)
                e := u.Encode()
                if ss != "" {
                    ss += "&" + e
                } else {
                    ss = e
                }
            }
        }

        //Now generate the signature on the string of ordered parameters.
		if rawSignature, err = ctx.SignString(ss); err != nil {
			return "", fmt.Errorf("unable to sign query string of redirect URL: %v", err)
		}

		// Now add base64 encoded Signature
		qs.Add("Signature", base64.StdEncoding.EncodeToString(rawSignature))
	}

    //Here the parameters may appear in any order.
	parsedUrl.RawQuery = qs.Encode()
	return parsedUrl.String(), nil
}

func (sp *SAMLServiceProvider) BuildAuthURLFromDocument(relayState string, doc *etree.Document) (string, error) {
	return sp.buildAuthURLFromDocument(relayState, BindingHttpPost, doc)
}

func (sp *SAMLServiceProvider) BuildAuthURLRedirect(relayState string, doc *etree.Document) (string, error) {
	return sp.buildAuthURLFromDocument(relayState, BindingHttpRedirect, doc)
}

// BuildAuthURL builds redirect URL to be sent to principal
func (sp *SAMLServiceProvider) BuildAuthURL(relayState string) (string, error) {
	doc, err := sp.BuildAuthRequestDocument()
	if err != nil {
		return "", err
	}
	return sp.BuildAuthURLFromDocument(relayState, doc)
}

// AuthRedirect takes a ResponseWriter and Request from an http interaction and
// redirects to the SAMLServiceProvider's configured IdP, including the
// relayState provided, if any.
func (sp *SAMLServiceProvider) AuthRedirect(w http.ResponseWriter, r *http.Request, relayState string) (err error) {
	url, err := sp.BuildAuthURL(relayState)
	if err != nil {
		return err
	}

	http.Redirect(w, r, url, http.StatusFound)
	return nil
}
<|endoftext|>


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

// Package buildenv contains definitions for the
// environments the Go build system can run in.
package buildenv

import (
	"context"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
	oauth2api "google.golang.org/api/oauth2/v2"
)

const (
	prefix = "https://www.googleapis.com/compute/v1/projects/"
)

// KubeConfig describes the configuration of a Kubernetes cluster.
type KubeConfig struct {
	// MinNodes is the minimum number of nodes in the Kubernetes cluster.
	// The autoscaler will ensure that at least this many nodes is always
	// running despite any scale-down decision.
	MinNodes int64

	// MaxNodes is the maximum number of nodes that the autoscaler can
	// provision in the Kubernetes cluster.
	// If MaxNodes is 0, Kubernetes is not used.
	MaxNodes int64

	// MachineType is the GCE machine type to use for the Kubernetes cluster nodes.
	MachineType string

	// Name is the name of the Kubernetes cluster that will be created.
	Name string
}

// Environment describes the configuration of the infrastructure for a
// coordinator and its buildlet resources running on Google Cloud Platform.
// Staging and Production are the two common build environments.
type Environment struct {
	// The GCP project name that the build infrastructure will be provisioned in.
	// This field may be overridden as necessary without impacting other fields.
	ProjectName string

	// ProjectNumber is the GCP build infrastructure project's number, as visible
	// in the admin console. This is used for things such as constructing the
	// "email" of the default service account.
	ProjectNumber int64

	// The GCP project name for the Go project, where build status is stored.
	// This field may be overridden as necessary without impacting other fields.
	GoProjectName string

	// The IsProd flag indicates whether production functionality should be
	// enabled. When true, GCE and Kubernetes builders are enabled and the
	// coordinator serves on 443. Otherwise, GCE and Kubernetes builders are
	// disabled and the coordinator serves on 8119.
	IsProd bool

	// ControlZone is the GCE zone that the coordinator instance and Kubernetes cluster
	// will run in. This field may be overridden as necessary without impacting
	// other fields.
	ControlZone string

	// PreferredEC2Zone is the preffered AWS availability zone.
	PreferredEC2Zone string

	// VMZones are the GCE zones that the VMs will be deployed to. These
	// GCE zones will be periodically cleaned by deleting old VMs. The zones
	// should all exist within a single region.
	VMZones []string

	// VMEC2Zones are the AWS availability zones that the VMs will be deployed to.
	// The availability zones should all exist within a single region.
	VMEC2Zones []string

	// StaticIP is the public, static IP address that will be attached to the
	// coordinator instance. The zero value means the address will be looked
	// up by name. This field is optional.
	StaticIP string

	// MachineType is the GCE machine type to use for the coordinator.
	MachineType string

	// KubeBuild is the Kubernetes config for the buildlet cluster.
	KubeBuild KubeConfig
	// KubeTools is the Kubernetes config for the tools cluster.
	KubeTools KubeConfig

	// PreferContainersOnCOS controls whether we do most builds on
	// Google's Container-Optimized OS Linux image running on a VM
	// rather than using Kubernetes for builds. This does not
	// affect cross-compiled builds just running make.bash. Those
	// still use Kubernetes for now.
	// See https://golang.org/issue/25108.
	PreferContainersOnCOS bool

	// DashURL is the base URL of the build dashboard, ending in a slash.
	DashURL string

	// PerfDataURL is the base URL of the benchmark storage server.
	PerfDataURL string

	// CoordinatorName is the hostname of the coordinator instance.
	CoordinatorName string

	// BuildletBucket is the GCS bucket that stores buildlet binaries.
	// TODO: rename. this is not just for buildlets; also for bootstrap.
	BuildletBucket string

	// LogBucket is the GCS bucket to which logs are written.
	LogBucket string

	// SnapBucket is the GCS bucket to which snapshots of
	// completed builds (after make.bash, before tests) are
	// written.
	SnapBucket string

	// MaxBuilds is the maximum number of concurrent builds that
	// can run. Zero means unlimited. This is typically only used
	// in a development or staging environment.
	MaxBuilds int

	// AutoCertCacheBucket is the GCS bucket to use for the
	// golang.org/x/crypto/acme/autocert (LetsEncrypt) cache.
	// If empty, LetsEncrypt isn't used.
	AutoCertCacheBucket string

	// COSServiceAccount (Container Optimized OS) is the service
	// account that will be assigned to a VM instance that hosts
	// a container when the instance is created.
	COSServiceAccount string
}

// ComputePrefix returns the URI prefix for Compute Engine resources in a project.
func (e Environment) ComputePrefix() string {
	return prefix + e.ProjectName
}

// RandomVMZone returns a randomly selected zone from the zones in VMZones.
// The Zone value will be returned if VMZones is not set.
func (e Environment) RandomVMZone() string {
	if len(e.VMZones) == 0 {
		return e.ControlZone
	}
	return e.VMZones[rand.Intn(len(e.VMZones))]
}

// RandomEC2VMZone returns a randomly selected zone from the zones in
// VMAvailabilityZones. The PreferredAvailabilityZone value will be
// returned if VMAvailabilityZones is not set.
func (e Environment) RandomEC2VMZone() string {
	if len(e.VMEC2Zones) == 0 {
		return e.PreferredEC2Zone
	}
	return e.VMEC2Zones[rand.Intn(len(e.VMEC2Zones))]
}

// Region returns the GCE region, derived from its zone.
func (e Environment) Region() string {
	return e.ControlZone[:strings.LastIndex(e.ControlZone, "-")]
}

// SnapshotURL returns the absolute URL of the .tar.gz containing a
// built Go tree for the builderType and Go rev (40 character Git
// commit hash). The tarball is suitable for passing to
// (*buildlet.Client).PutTarFromURL.
func (e Environment) SnapshotURL(builderType, rev string) string {
	return fmt.Sprintf("https://storage.googleapis.com/%s/go/%s/%s.tar.gz", e.SnapBucket, builderType, rev)
}

// DashBase returns the base URL of the build dashboard, ending in a slash.
func (e Environment) DashBase() string {
	// TODO(quentin): Should we really default to production? That's what the old code did.
	if e.DashURL != "" {
		return e.DashURL
	}
	return Production.DashURL
}

// Credentials returns the credentials required to access the GCP environment
// with the necessary scopes.
func (e Environment) Credentials(ctx context.Context) (*google.Credentials, error) {
	// TODO: this method used to do much more. maybe remove it
	// when TODO below is addressed, pushing scopes to caller? Or
	// add a Scopes func/method somewhere instead?
	scopes := []string{
		// Cloud Platform should include all others, but the
		// old code duplicated compute and the storage full
		// control scopes, so I leave them here for now. They
		// predated the all-encompassing "cloud platform"
		// scope anyway.
		// TODO: remove compute and DevstorageFullControlScope once verified to work
		// without.
		compute.CloudPlatformScope,
		compute.ComputeScope,
		compute.DevstorageFullControlScope,

		// The coordinator needed the userinfo email scope for
		// reporting to the perf dashboard running on App
		// Engine at one point. The perf dashboard is down at
		// the moment, but when it's back up we'll need this,
		// and if we do other authenticated requests to App
		// Engine apps, this would be useful.
		oauth2api.UserinfoEmailScope,
	}
	creds, err := google.FindDefaultCredentials(ctx, scopes...)
	if err != nil {
		CheckUserCredentials()
		return nil, err
	}
	creds.TokenSource = diagnoseFailureTokenSource{creds.TokenSource}
	return creds, nil
}

// ByProjectID returns an Environment for the specified
// project ID. It is currently limited to the symbolic-datum-552
// and go-dashboard-dev projects.
// ByProjectID will panic if the project ID is not known.
func ByProjectID(projectID string) *Environment {
	var envKeys []string

	for k := range possibleEnvs {
		envKeys = append(envKeys, k)
	}

	var env *Environment
	env, ok := possibleEnvs[projectID]
	if !ok {
		panic(fmt.Sprintf("Can't get buildenv for unknown project %q. Possible envs are %s", projectID, envKeys))
	}

	return env
}

// Staging defines the environment that the coordinator and build
// infrastructure is deployed to before it is released to production.
// For local dev, override the project with the program's flag to set
// a custom project.
var Staging = &Environment{
	ProjectName:           "go-dashboard-dev",
	ProjectNumber:         302018677728,
	GoProjectName:         "go-dashboard-dev",
	IsProd:                true,
	ControlZone:           "us-central1-f",
	VMZones:               []string{"us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"},
	StaticIP:              "104.154.113.235",
	MachineType:           "n1-standard-1",
	PreferContainersOnCOS: true,
	KubeBuild: KubeConfig{
		MinNodes:    1,
		MaxNodes:    1, // auto-scaling disabled
		Name:        "buildlets",
		MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
	},
	KubeTools: KubeConfig{
		MinNodes:    3,
		MaxNodes:    3,
		Name:        "go",
		MachineType: "n1-standard-4",
	},
	DashURL:           "https://go-dashboard-dev.appspot.com/",
	PerfDataURL:       "https://perfdata.golang.org",
	CoordinatorName:   "farmer",
	BuildletBucket:    "dev-go-builder-data",
	LogBucket:         "dev-go-build-log",
	SnapBucket:        "dev-go-build-snap",
	COSServiceAccount: "linux-cos-builders@go-dashboard-dev.iam.gserviceaccount.com",
}

// Production defines the environment that the coordinator and build
// infrastructure is deployed to for production usage at build.golang.org.
var Production = &Environment{
	ProjectName:           "symbolic-datum-552",
	ProjectNumber:         872405196845,
	GoProjectName:         "golang-org",
	IsProd:                true,
	ControlZone:           "us-central1-f",
	VMZones:               []string{"us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"},
	VMEC2Zones:            []string{"us-east-2a", "us-east-2b"},
	StaticIP:              "107.178.219.46",
	MachineType:           "n1-standard-4",
	PreferContainersOnCOS: true,
	KubeBuild: KubeConfig{
		MinNodes:    2,
		MaxNodes:    2, // auto-scaling disabled
		Name:        "buildlets",
		MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
	},
	KubeTools: KubeConfig{
		MinNodes:    4,
		MaxNodes:    4,
		Name:        "go",
		MachineType: "n1-standard-4",
	},
	DashURL:             "https://build.golang.org/",
	PerfDataURL:         "https://perfdata.golang.org",
	CoordinatorName:     "farmer",
	BuildletBucket:      "go-builder-data",
	LogBucket:           "go-build-log",
	SnapBucket:          "go-build-snap",
	AutoCertCacheBucket: "farmer-golang-org-autocert-cache",
	COSServiceAccount:   "linux-cos-builders@symbolic-datum-552.iam.gserviceaccount.com",
}

var Development = &Environment{
	GoProjectName: "golang-org",
	IsProd:        false,
	StaticIP:      "127.0.0.1",
}

// possibleEnvs enumerate the known buildenv.Environment definitions.
var possibleEnvs = map[string]*Environment{
	"dev":                Development,
	"symbolic-datum-552": Production,
	"go-dashboard-dev":   Staging,
}

var (
	stagingFlag     bool
	localDevFlag    bool
	registeredFlags bool
)

// RegisterFlags registers the "staging" and "localdev" flags.
func RegisterFlags() {
	if registeredFlags {
		panic("duplicate call to RegisterFlags or RegisterStagingFlag")
	}
	flag.BoolVar(&localDevFlag, "localdev", false, "use the localhost in-development coordinator")
	RegisterStagingFlag()
	registeredFlags = true
}

// RegisterStagingFlag registers the "staging" flag.
func RegisterStagingFlag() {
	if registeredFlags {
		panic("duplicate call to RegisterFlags or RegisterStagingFlag")
	}
	flag.BoolVar(&stagingFlag, "staging", false, "use the staging build coordinator and buildlets")
	registeredFlags = true
}

// FromFlags returns the build environment specified from flags,
// as registered by RegisterFlags or RegisterStagingFlag.
// By default it returns the production environment.
func FromFlags() *Environment {
	if !registeredFlags {
		panic("FromFlags called without RegisterFlags")
	}
	if localDevFlag {
		return Development
	}
	if stagingFlag {
		return Staging
	}
	return Production
}

// warnCredsOnce guards CheckUserCredentials spamming stderr. Once is enough.
var warnCredsOnce sync.Once

// CheckUserCredentials warns if the gcloud Application Default Credentials file doesn't exist
// and says how to log in properly.
func CheckUserCredentials() {
	adcJSON := filepath.Join(os.Getenv("HOME"), ".config/gcloud/application_default_credentials.json")
	if _, err := os.Stat(adcJSON); os.IsNotExist(err) {
		warnCredsOnce.Do(func() {
			log.Printf("warning: file %s does not exist; did you run 'gcloud auth application-default login' ? (The 'application-default' part matters, confusingly.)", adcJSON)
		})
	}
}

// diagnoseFailureTokenSource is an oauth2.TokenSource wrapper that,
// upon failure, diagnoses why the token acquistion might've failed.
type diagnoseFailureTokenSource struct {
	ts oauth2.TokenSource
}

func (ts diagnoseFailureTokenSource) Token() (*oauth2.Token, error) {
	t, err := ts.ts.Token()
	if err != nil {
		CheckUserCredentials()
		return nil, err
	}
	return t, nil
}
<commit_msg>buildlet: add EC2 security groups to configuration<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package buildenv contains definitions for the
// environments the Go build system can run in.
package buildenv

import (
	"context"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
	oauth2api "google.golang.org/api/oauth2/v2"
)

const (
	prefix = "https://www.googleapis.com/compute/v1/projects/"
)

// KubeConfig describes the configuration of a Kubernetes cluster.
type KubeConfig struct {
	// MinNodes is the minimum number of nodes in the Kubernetes cluster.
	// The autoscaler will ensure that at least this many nodes is always
	// running despite any scale-down decision.
	MinNodes int64

	// MaxNodes is the maximum number of nodes that the autoscaler can
	// provision in the Kubernetes cluster.
	// If MaxNodes is 0, Kubernetes is not used.
	MaxNodes int64

	// MachineType is the GCE machine type to use for the Kubernetes cluster nodes.
	MachineType string

	// Name is the name of the Kubernetes cluster that will be created.
	Name string
}

// Environment describes the configuration of the infrastructure for a
// coordinator and its buildlet resources running on Google Cloud Platform.
// Staging and Production are the two common build environments.
type Environment struct {
	// The GCP project name that the build infrastructure will be provisioned in.
	// This field may be overridden as necessary without impacting other fields.
	ProjectName string

	// ProjectNumber is the GCP build infrastructure project's number, as visible
	// in the admin console. This is used for things such as constructing the
	// "email" of the default service account.
	ProjectNumber int64

	// The GCP project name for the Go project, where build status is stored.
	// This field may be overridden as necessary without impacting other fields.
	GoProjectName string

	// The IsProd flag indicates whether production functionality should be
	// enabled. When true, GCE and Kubernetes builders are enabled and the
	// coordinator serves on 443. Otherwise, GCE and Kubernetes builders are
	// disabled and the coordinator serves on 8119.
	IsProd bool

	// ControlZone is the GCE zone that the coordinator instance and Kubernetes cluster
	// will run in. This field may be overridden as necessary without impacting
	// other fields.
	ControlZone string

	// PreferredEC2Zone is the preffered AWS availability zone.
	PreferredEC2Zone string

	// VMZones are the GCE zones that the VMs will be deployed to. These
	// GCE zones will be periodically cleaned by deleting old VMs. The zones
	// should all exist within a single region.
	VMZones []string

	// VMEC2Zones are the AWS availability zones that the VMs will be deployed to.
	// The availability zones should all exist within a single region.
	VMEC2Zones []string

	// StaticIP is the public, static IP address that will be attached to the
	// coordinator instance. The zero value means the address will be looked
	// up by name. This field is optional.
	StaticIP string

	// MachineType is the GCE machine type to use for the coordinator.
	MachineType string

	// KubeBuild is the Kubernetes config for the buildlet cluster.
	KubeBuild KubeConfig
	// KubeTools is the Kubernetes config for the tools cluster.
	KubeTools KubeConfig

	// PreferContainersOnCOS controls whether we do most builds on
	// Google's Container-Optimized OS Linux image running on a VM
	// rather than using Kubernetes for builds. This does not
	// affect cross-compiled builds just running make.bash. Those
	// still use Kubernetes for now.
	// See https://golang.org/issue/25108.
	PreferContainersOnCOS bool

	// DashURL is the base URL of the build dashboard, ending in a slash.
	DashURL string

	// PerfDataURL is the base URL of the benchmark storage server.
	PerfDataURL string

	// CoordinatorName is the hostname of the coordinator instance.
	CoordinatorName string

	// BuildletBucket is the GCS bucket that stores buildlet binaries.
	// TODO: rename. this is not just for buildlets; also for bootstrap.
	BuildletBucket string

	// LogBucket is the GCS bucket to which logs are written.
	LogBucket string

	// SnapBucket is the GCS bucket to which snapshots of
	// completed builds (after make.bash, before tests) are
	// written.
	SnapBucket string

	// MaxBuilds is the maximum number of concurrent builds that
	// can run. Zero means unlimited. This is typically only used
	// in a development or staging environment.
	MaxBuilds int

	// AutoCertCacheBucket is the GCS bucket to use for the
	// golang.org/x/crypto/acme/autocert (LetsEncrypt) cache.
	// If empty, LetsEncrypt isn't used.
	AutoCertCacheBucket string

	// COSServiceAccount (Container Optimized OS) is the service
	// account that will be assigned to a VM instance that hosts
	// a container when the instance is created.
	COSServiceAccount string

	// AWSSecurityGroup is the security group that any VM instance
	// created on EC2 should contain. These security groups are
	// collections of firewall rules to be applied to the VM.
	AWSSecurityGroup string
}

// ComputePrefix returns the URI prefix for Compute Engine resources in a project.
func (e Environment) ComputePrefix() string {
	return prefix + e.ProjectName
}

// RandomVMZone returns a randomly selected zone from the zones in VMZones.
// The Zone value will be returned if VMZones is not set.
func (e Environment) RandomVMZone() string {
	if len(e.VMZones) == 0 {
		return e.ControlZone
	}
	return e.VMZones[rand.Intn(len(e.VMZones))]
}

// RandomEC2VMZone returns a randomly selected zone from the zones in
// VMAvailabilityZones. The PreferredAvailabilityZone value will be
// returned if VMAvailabilityZones is not set.
func (e Environment) RandomEC2VMZone() string {
	if len(e.VMEC2Zones) == 0 {
		return e.PreferredEC2Zone
	}
	return e.VMEC2Zones[rand.Intn(len(e.VMEC2Zones))]
}

// Region returns the GCE region, derived from its zone.
func (e Environment) Region() string {
	return e.ControlZone[:strings.LastIndex(e.ControlZone, "-")]
}

// SnapshotURL returns the absolute URL of the .tar.gz containing a
// built Go tree for the builderType and Go rev (40 character Git
// commit hash). The tarball is suitable for passing to
// (*buildlet.Client).PutTarFromURL.
func (e Environment) SnapshotURL(builderType, rev string) string {
	return fmt.Sprintf("https://storage.googleapis.com/%s/go/%s/%s.tar.gz", e.SnapBucket, builderType, rev)
}

// DashBase returns the base URL of the build dashboard, ending in a slash.
func (e Environment) DashBase() string {
	// TODO(quentin): Should we really default to production? That's what the old code did.
	if e.DashURL != "" {
		return e.DashURL
	}
	return Production.DashURL
}

// Credentials returns the credentials required to access the GCP environment
// with the necessary scopes.
func (e Environment) Credentials(ctx context.Context) (*google.Credentials, error) {
	// TODO: this method used to do much more. maybe remove it
	// when TODO below is addressed, pushing scopes to caller? Or
	// add a Scopes func/method somewhere instead?
	scopes := []string{
		// Cloud Platform should include all others, but the
		// old code duplicated compute and the storage full
		// control scopes, so I leave them here for now. They
		// predated the all-encompassing "cloud platform"
		// scope anyway.
		// TODO: remove compute and DevstorageFullControlScope once verified to work
		// without.
		compute.CloudPlatformScope,
		compute.ComputeScope,
		compute.DevstorageFullControlScope,

		// The coordinator needed the userinfo email scope for
		// reporting to the perf dashboard running on App
		// Engine at one point. The perf dashboard is down at
		// the moment, but when it's back up we'll need this,
		// and if we do other authenticated requests to App
		// Engine apps, this would be useful.
		oauth2api.UserinfoEmailScope,
	}
	creds, err := google.FindDefaultCredentials(ctx, scopes...)
	if err != nil {
		CheckUserCredentials()
		return nil, err
	}
	creds.TokenSource = diagnoseFailureTokenSource{creds.TokenSource}
	return creds, nil
}

// ByProjectID returns an Environment for the specified
// project ID. It is currently limited to the symbolic-datum-552
// and go-dashboard-dev projects.
// ByProjectID will panic if the project ID is not known.
func ByProjectID(projectID string) *Environment {
	var envKeys []string

	for k := range possibleEnvs {
		envKeys = append(envKeys, k)
	}

	var env *Environment
	env, ok := possibleEnvs[projectID]
	if !ok {
		panic(fmt.Sprintf("Can't get buildenv for unknown project %q. Possible envs are %s", projectID, envKeys))
	}

	return env
}

// Staging defines the environment that the coordinator and build
// infrastructure is deployed to before it is released to production.
// For local dev, override the project with the program's flag to set
// a custom project.
var Staging = &Environment{
	ProjectName:           "go-dashboard-dev",
	ProjectNumber:         302018677728,
	GoProjectName:         "go-dashboard-dev",
	IsProd:                true,
	ControlZone:           "us-central1-f",
	VMZones:               []string{"us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"},
	StaticIP:              "104.154.113.235",
	MachineType:           "n1-standard-1",
	PreferContainersOnCOS: true,
	KubeBuild: KubeConfig{
		MinNodes:    1,
		MaxNodes:    1, // auto-scaling disabled
		Name:        "buildlets",
		MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
	},
	KubeTools: KubeConfig{
		MinNodes:    3,
		MaxNodes:    3,
		Name:        "go",
		MachineType: "n1-standard-4",
	},
	DashURL:           "https://go-dashboard-dev.appspot.com/",
	PerfDataURL:       "https://perfdata.golang.org",
	CoordinatorName:   "farmer",
	BuildletBucket:    "dev-go-builder-data",
	LogBucket:         "dev-go-build-log",
	SnapBucket:        "dev-go-build-snap",
	COSServiceAccount: "linux-cos-builders@go-dashboard-dev.iam.gserviceaccount.com",
	AWSSecurityGroup:  "sg-02c2a75a3eed843bb",
}

// Production defines the environment that the coordinator and build
// infrastructure is deployed to for production usage at build.golang.org.
var Production = &Environment{
	ProjectName:           "symbolic-datum-552",
	ProjectNumber:         872405196845,
	GoProjectName:         "golang-org",
	IsProd:                true,
	ControlZone:           "us-central1-f",
	VMZones:               []string{"us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"},
	VMEC2Zones:            []string{"us-east-2a", "us-east-2b"},
	StaticIP:              "107.178.219.46",
	MachineType:           "n1-standard-4",
	PreferContainersOnCOS: true,
	KubeBuild: KubeConfig{
		MinNodes:    2,
		MaxNodes:    2, // auto-scaling disabled
		Name:        "buildlets",
		MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
	},
	KubeTools: KubeConfig{
		MinNodes:    4,
		MaxNodes:    4,
		Name:        "go",
		MachineType: "n1-standard-4",
	},
	DashURL:             "https://build.golang.org/",
	PerfDataURL:         "https://perfdata.golang.org",
	CoordinatorName:     "farmer",
	BuildletBucket:      "go-builder-data",
	LogBucket:           "go-build-log",
	SnapBucket:          "go-build-snap",
	AutoCertCacheBucket: "farmer-golang-org-autocert-cache",
	COSServiceAccount:   "linux-cos-builders@symbolic-datum-552.iam.gserviceaccount.com",
	AWSSecurityGroup:    "sg-02c2a75a3eed843bb",
}

var Development = &Environment{
	GoProjectName: "golang-org",
	IsProd:        false,
	StaticIP:      "127.0.0.1",
}

// possibleEnvs enumerate the known buildenv.Environment definitions.
var possibleEnvs = map[string]*Environment{
	"dev":                Development,
	"symbolic-datum-552": Production,
	"go-dashboard-dev":   Staging,
}

var (
	stagingFlag     bool
	localDevFlag    bool
	registeredFlags bool
)

// RegisterFlags registers the "staging" and "localdev" flags.
func RegisterFlags() {
	if registeredFlags {
		panic("duplicate call to RegisterFlags or RegisterStagingFlag")
	}
	flag.BoolVar(&localDevFlag, "localdev", false, "use the localhost in-development coordinator")
	RegisterStagingFlag()
	registeredFlags = true
}

// RegisterStagingFlag registers the "staging" flag.
func RegisterStagingFlag() {
	if registeredFlags {
		panic("duplicate call to RegisterFlags or RegisterStagingFlag")
	}
	flag.BoolVar(&stagingFlag, "staging", false, "use the staging build coordinator and buildlets")
	registeredFlags = true
}

// FromFlags returns the build environment specified from flags,
// as registered by RegisterFlags or RegisterStagingFlag.
// By default it returns the production environment.
func FromFlags() *Environment {
	if !registeredFlags {
		panic("FromFlags called without RegisterFlags")
	}
	if localDevFlag {
		return Development
	}
	if stagingFlag {
		return Staging
	}
	return Production
}

// warnCredsOnce guards CheckUserCredentials spamming stderr. Once is enough.
var warnCredsOnce sync.Once

// CheckUserCredentials warns if the gcloud Application Default Credentials file doesn't exist
// and says how to log in properly.
func CheckUserCredentials() {
	adcJSON := filepath.Join(os.Getenv("HOME"), ".config/gcloud/application_default_credentials.json")
	if _, err := os.Stat(adcJSON); os.IsNotExist(err) {
		warnCredsOnce.Do(func() {
			log.Printf("warning: file %s does not exist; did you run 'gcloud auth application-default login' ? (The 'application-default' part matters, confusingly.)", adcJSON)
		})
	}
}

// diagnoseFailureTokenSource is an oauth2.TokenSource wrapper that,
// upon failure, diagnoses why the token acquistion might've failed.
type diagnoseFailureTokenSource struct {
	ts oauth2.TokenSource
}

func (ts diagnoseFailureTokenSource) Token() (*oauth2.Token, error) {
	t, err := ts.ts.Token()
	if err != nil {
		CheckUserCredentials()
		return nil, err
	}
	return t, nil
}
<|endoftext|>


<commit_before>package builder

// nginx
const (
	NginxVersion           = "1.14.0"
	NginxDownloadURLPrefix = "https://nginx.org/download"
)

// pcre
const (
	PcreVersion           = "8.42"
	PcreDownloadURLPrefix = "https://ftp.pcre.org/pub/pcre"
)

// openssl
const (
	OpenSSLVersion           = "1.0.2o"
	OpenSSLDownloadURLPrefix = "https://www.openssl.org/source"
)

// libressl
const (
	LibreSSLVersion           = "2.7.2"
	LibreSSLDownloadURLPrefix = "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL"
)

// zlib
const (
	ZlibVersion           = "1.2.11"
	ZlibDownloadURLPrefix = "https://zlib.net/fossils"
)

// openResty
const (
	OpenRestyVersion           = "1.13.6.1"
	OpenRestyDownloadURLPrefix = "https://openresty.org/download"
)

// tengine
const (
	TengineVersion           = "2.2.2"
	TengineDownloadURLPrefix = "http://tengine.taobao.org/download"
)

// component enumerations
const (
	ComponentNginx = iota
	ComponentOpenResty
	ComponentTengine
	ComponentPcre
	ComponentOpenSSL
	ComponentLibreSSL
	ComponentZlib
	ComponentMax
)
<commit_msg>bumped default openresty version to 1.13.6.2.<commit_after>package builder

// nginx
const (
	NginxVersion           = "1.14.0"
	NginxDownloadURLPrefix = "https://nginx.org/download"
)

// pcre
const (
	PcreVersion           = "8.42"
	PcreDownloadURLPrefix = "https://ftp.pcre.org/pub/pcre"
)

// openssl
const (
	OpenSSLVersion           = "1.0.2o"
	OpenSSLDownloadURLPrefix = "https://www.openssl.org/source"
)

// libressl
const (
	LibreSSLVersion           = "2.7.2"
	LibreSSLDownloadURLPrefix = "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL"
)

// zlib
const (
	ZlibVersion           = "1.2.11"
	ZlibDownloadURLPrefix = "https://zlib.net/fossils"
)

// openResty
const (
	OpenRestyVersion           = "1.13.6.2"
	OpenRestyDownloadURLPrefix = "https://openresty.org/download"
)

// tengine
const (
	TengineVersion           = "2.2.2"
	TengineDownloadURLPrefix = "http://tengine.taobao.org/download"
)

// component enumerations
const (
	ComponentNginx = iota
	ComponentOpenResty
	ComponentTengine
	ComponentPcre
	ComponentOpenSSL
	ComponentLibreSSL
	ComponentZlib
	ComponentMax
)
<|endoftext|>


<commit_before>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/broadcast"
	"github.com/ivan1993spb/snake-server/game"
	"github.com/ivan1993spb/snake-server/player"
)

const (
	chanPlayerOutputMessageBuffer = 256
	chanPlayerEncodeMessageBuffer = 1024

	chanMergeBytesBuffer = 8192

	chanReadMessagesBuffer      = 32
	chanDecodeMessageBuffer     = 32
	chanProxyInputMessageBuffer = 32
	chanInputMessagesBuffer     = 32
	chanSnakeCommandsBuffer     = 32

	sendInputMessageTimeout  = time.Millisecond
	sendOutputMessageTimeout = time.Millisecond * 25
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger logrus.FieldLogger

	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
}

func NewConnectionWorker(conn *websocket.Conn, logger logrus.FieldLogger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(stop <-chan struct{}, game *game.Game, broadcast *broadcast.GroupBroadcast, gamePreparedMessages <-chan *websocket.PreparedMessage) error {
	if cw.flagStarted {
		return ErrStartConnectionWorker("connection worker already started")
	}

	cw.flagStarted = true

	broadcast.BroadcastMessage("user joined your game group")

	// Input
	chInputBytes, chStop := cw.read()
	chInputMessages := cw.decode(chInputBytes, chStop)
	cw.broadcastInputMessage(chInputMessages, chStop)
	chCommands := cw.listenSnakeCommands(chStop, cw.input(chStop, chanInputMessagesBuffer))
	cw.listenPlayerBroadcasts(chStop, cw.input(chStop, chanInputMessagesBuffer), broadcast)

	p := player.NewPlayer(cw.logger, game.World())

	// Output
	chPlayer := p.Start(chStop, chCommands)
	chOutputBytes := cw.encode(chStop, cw.listenPlayer(chStop, chPlayer))
	chPlayerPreparedMessages := cw.prepare(chStop, chOutputBytes)
	chPreparedMessages := cw.mergePreparedMessagesChs(chStop, chPlayerPreparedMessages, gamePreparedMessages)
	chPreparedMessagesTimeout := cw.chPreparedMessageTimeout(chPreparedMessages, chStop, sendOutputMessageTimeout)
	cw.write(chPreparedMessagesTimeout, chStop)

	select {
	case <-chStop:
		// On connection error
	case <-stop:
		// External stop
	}

	broadcast.BroadcastMessage("user left your game group")

	cw.stopInputs()

	return nil
}

func (cw *ConnectionWorker) stopInputs() {
	cw.chsInputMux.Lock()
	defer cw.chsInputMux.Unlock()

	for _, ch := range cw.chsInput {
		close(ch)
	}

	cw.chsInput = cw.chsInput[:0]
}

func (cw *ConnectionWorker) read() (<-chan []byte, <-chan struct{}) {
	chout := make(chan []byte, chanReadMessagesBuffer)
	chstop := make(chan struct{}, 0)

	go func() {
		defer close(chout)
		defer close(chstop)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					cw.logger.Errorln("read input message error:", err)
				}
				return
			}

			if websocket.TextMessage != messageType {
				cw.logger.Warning("unexpected input message type")
				continue
			}

			chout <- data
		}
	}()

	return chout, chstop
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				var inputMessage InputMessage
				if err := decoder.Decode(data, &inputMessage); err != nil {
					cw.logger.Errorln("decode input message error:", err)
				} else {
					select {
					case <-stop:
						return
					case chout <- inputMessage:
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage, ok := <-chin:
				if !ok {
					return
				}

				cw.doBroadcast(inputMessage, stop)
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) doBroadcast(message InputMessage, stop <-chan struct{}) {
	cw.chsInputMux.RLock()
	defer cw.chsInputMux.RUnlock()

	for _, ch := range cw.chsInput {
		select {
		case ch <- message:
		case <-stop:
			return
		}
	}
}

func (cw *ConnectionWorker) input(stop <-chan struct{}, buffer uint) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, buffer)

	go func() {
		defer close(chout)
		defer func() {
			go func() {
				for range chProxy {
				}
			}()

			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case inputMessage, ok := <-chProxy:
				if !ok {
					return
				}
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()

	select {
	case ch <- inputMessage:
	case <-stop:
	case <-timer.C:
		cw.logger.Warn("send input message time is out")
	}
}

func (cw *ConnectionWorker) mergePreparedMessagesChs(stop <-chan struct{}, chins ...<-chan *websocket.PreparedMessage) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, chanMergeBytesBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan *websocket.PreparedMessage) {
			defer wg.Done()

			for {
				select {
				case <-stop:
					return
				case data, ok := <-chin:
					if !ok {
						return
					}

					select {
					case chout <- data:
					case <-stop:
						return
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) chPreparedMessageTimeout(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}, timeout time.Duration) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				timer := time.NewTimer(timeout)

				select {
				case chout <- pm:
					timer.Stop()
				case <-timer.C:
					cw.logger.Warn("send message for writing time is out")
					timer.Stop()
					continue
				case <-stop:
					timer.Stop()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) write(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				if err := cw.conn.WritePreparedMessage(pm); err != nil {
					if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
						cw.logger.Errorln("write output message error:", err)
					}
					return
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) prepare(stop <-chan struct{}, chin <-chan []byte) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				if pm, err := websocket.NewPreparedMessage(websocket.TextMessage, data); err != nil {
					cw.logger.Errorln("prepare player output message error:", err)
				} else {
					select {
					case chout <- pm:
					case <-stop:
						return
					}
				}

			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) encode(stop <-chan struct{}, chins ...<-chan OutputMessage) <-chan []byte {
	chout := make(chan []byte, chanPlayerEncodeMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan OutputMessage) {
			defer wg.Done()
			for {
				select {
				case <-stop:
					return
				case message, ok := <-chin:
					if !ok {
						return
					}

					if data, err := ffjson.Marshal(message); err != nil {
						cw.logger.Errorln("encode output message error:", err)
					} else {
						select {
						case <-stop:
							return
						case chout <- data:
						}
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayer(stop <-chan struct{}, chin <-chan player.Message) <-chan OutputMessage {
	chout := make(chan OutputMessage, chanPlayerOutputMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case event, ok := <-chin:
				if !ok {
					return
				}

				outputMessage := OutputMessage{
					Type:    OutputMessageTypePlayer,
					Payload: event,
				}

				select {
				case chout <- outputMessage:
				case <-stop:
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenSnakeCommands(stop <-chan struct{}, chin <-chan InputMessage) <-chan string {
	chout := make(chan string, chanSnakeCommandsBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeSnakeCommand {
					select {
					case chout <- message.Payload:
					case <-stop:
						return
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayerBroadcasts(stop <-chan struct{}, chin <-chan InputMessage, b *broadcast.GroupBroadcast) {
	go func() {
		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeBroadcast {
					b.BroadcastMessage(broadcast.Message(message.Payload))
				}
			case <-stop:
				return
			}
		}
	}()
}
<commit_msg>In connections ConnectionWorker increase channel buffers<commit_after>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/broadcast"
	"github.com/ivan1993spb/snake-server/game"
	"github.com/ivan1993spb/snake-server/player"
)

const (
	chanPlayerOutputMessageBuffer = 256
	chanPlayerEncodeMessageBuffer = 1024

	chanMergeBytesBuffer = 8192

	chanReadMessagesBuffer           = 64
	chanDecodeMessageBuffer          = 64
	chanProxyInputMessageBuffer      = 64
	chanInputMessagesSnakeBuffer     = 64
	chanInputMessagesBroadcastBuffer = 64
	chanSnakeCommandsBuffer          = 64

	sendInputMessageTimeout  = time.Millisecond * 5
	sendOutputMessageTimeout = time.Millisecond * 25
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger logrus.FieldLogger

	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
}

func NewConnectionWorker(conn *websocket.Conn, logger logrus.FieldLogger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(stop <-chan struct{}, game *game.Game, broadcast *broadcast.GroupBroadcast, gamePreparedMessages <-chan *websocket.PreparedMessage) error {
	if cw.flagStarted {
		return ErrStartConnectionWorker("connection worker already started")
	}

	cw.flagStarted = true

	broadcast.BroadcastMessage("user joined your game group")

	// Input
	chInputBytes, chStop := cw.read()
	chInputMessages := cw.decode(chInputBytes, chStop)
	cw.broadcastInputMessage(chInputMessages, chStop)
	chCommands := cw.listenSnakeCommands(chStop, cw.input(chStop, chanInputMessagesSnakeBuffer))
	cw.listenPlayerBroadcasts(chStop, cw.input(chStop, chanInputMessagesBroadcastBuffer), broadcast)

	p := player.NewPlayer(cw.logger, game.World())

	// Output
	chPlayer := p.Start(chStop, chCommands)
	chOutputBytes := cw.encode(chStop, cw.listenPlayer(chStop, chPlayer))
	chPlayerPreparedMessages := cw.prepare(chStop, chOutputBytes)
	chPreparedMessages := cw.mergePreparedMessagesChs(chStop, chPlayerPreparedMessages, gamePreparedMessages)
	chPreparedMessagesTimeout := cw.chPreparedMessageTimeout(chPreparedMessages, chStop, sendOutputMessageTimeout)
	cw.write(chPreparedMessagesTimeout, chStop)

	select {
	case <-chStop:
		// On connection error
	case <-stop:
		// External stop
	}

	broadcast.BroadcastMessage("user left your game group")

	cw.stopInputs()

	return nil
}

func (cw *ConnectionWorker) stopInputs() {
	cw.chsInputMux.Lock()
	defer cw.chsInputMux.Unlock()

	for _, ch := range cw.chsInput {
		close(ch)
	}

	cw.chsInput = cw.chsInput[:0]
}

func (cw *ConnectionWorker) read() (<-chan []byte, <-chan struct{}) {
	chout := make(chan []byte, chanReadMessagesBuffer)
	chstop := make(chan struct{}, 0)

	go func() {
		defer close(chout)
		defer close(chstop)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					cw.logger.Errorln("read input message error:", err)
				}
				return
			}

			if websocket.TextMessage != messageType {
				cw.logger.Warning("unexpected input message type")
				continue
			}

			chout <- data
		}
	}()

	return chout, chstop
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				var inputMessage InputMessage
				if err := decoder.Decode(data, &inputMessage); err != nil {
					cw.logger.Errorln("decode input message error:", err)
				} else {
					select {
					case <-stop:
						return
					case chout <- inputMessage:
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage, ok := <-chin:
				if !ok {
					return
				}

				cw.doBroadcast(inputMessage, stop)
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) doBroadcast(message InputMessage, stop <-chan struct{}) {
	cw.chsInputMux.RLock()
	defer cw.chsInputMux.RUnlock()

	for _, ch := range cw.chsInput {
		select {
		case ch <- message:
		case <-stop:
			return
		}
	}
}

func (cw *ConnectionWorker) input(stop <-chan struct{}, buffer uint) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, buffer)

	go func() {
		defer close(chout)
		defer func() {
			go func() {
				for range chProxy {
				}
			}()

			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case inputMessage, ok := <-chProxy:
				if !ok {
					return
				}
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()

	select {
	case ch <- inputMessage:
	case <-stop:
	case <-timer.C:
		cw.logger.WithFields(logrus.Fields{
			"timeout":      timeout,
			"message_type": inputMessage.Type,
		}).Warn("send input message to processing: time is out")
	}
}

func (cw *ConnectionWorker) mergePreparedMessagesChs(stop <-chan struct{}, chins ...<-chan *websocket.PreparedMessage) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, chanMergeBytesBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan *websocket.PreparedMessage) {
			defer wg.Done()

			for {
				select {
				case <-stop:
					return
				case data, ok := <-chin:
					if !ok {
						return
					}

					select {
					case chout <- data:
					case <-stop:
						return
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) chPreparedMessageTimeout(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}, timeout time.Duration) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				timer := time.NewTimer(timeout)

				select {
				case chout <- pm:
					timer.Stop()
				case <-timer.C:
					cw.logger.Warn("send message for writing time is out")
					timer.Stop()
					continue
				case <-stop:
					timer.Stop()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) write(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				if err := cw.conn.WritePreparedMessage(pm); err != nil {
					if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
						cw.logger.Errorln("write output message error:", err)
					}
					return
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) prepare(stop <-chan struct{}, chin <-chan []byte) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				if pm, err := websocket.NewPreparedMessage(websocket.TextMessage, data); err != nil {
					cw.logger.Errorln("prepare player output message error:", err)
				} else {
					select {
					case chout <- pm:
					case <-stop:
						return
					}
				}

			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) encode(stop <-chan struct{}, chins ...<-chan OutputMessage) <-chan []byte {
	chout := make(chan []byte, chanPlayerEncodeMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan OutputMessage) {
			defer wg.Done()
			for {
				select {
				case <-stop:
					return
				case message, ok := <-chin:
					if !ok {
						return
					}

					if data, err := ffjson.Marshal(message); err != nil {
						cw.logger.Errorln("encode output message error:", err)
					} else {
						select {
						case <-stop:
							return
						case chout <- data:
						}
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayer(stop <-chan struct{}, chin <-chan player.Message) <-chan OutputMessage {
	chout := make(chan OutputMessage, chanPlayerOutputMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case event, ok := <-chin:
				if !ok {
					return
				}

				outputMessage := OutputMessage{
					Type:    OutputMessageTypePlayer,
					Payload: event,
				}

				select {
				case chout <- outputMessage:
				case <-stop:
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenSnakeCommands(stop <-chan struct{}, chin <-chan InputMessage) <-chan string {
	chout := make(chan string, chanSnakeCommandsBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeSnakeCommand {
					select {
					case chout <- message.Payload:
					case <-stop:
						return
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayerBroadcasts(stop <-chan struct{}, chin <-chan InputMessage, b *broadcast.GroupBroadcast) {
	go func() {
		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeBroadcast {
					b.BroadcastMessage(broadcast.Message(message.Payload))
				}
			case <-stop:
				return
			}
		}
	}()
}
<|endoftext|>


<commit_before>package snakepit

import (
	"net/http"
	"os"

	"github.com/pressly/chi"
	"golang.org/x/net/context"
)

type Swagger struct{}

func NewSwagger() func(next chi.Handler) chi.Handler {
	swagger := &Swagger{}
	return swagger.middleware
}

func (rec *Swagger) middleware(next chi.Handler) chi.Handler {
	return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/swagger" {
			w.Header().Add("Access-Control-Allow-Origin", "*")
			w.Header().Add("Access-Control-Allow-Methods", "GET")

			file := "./swagger.json"
			if _, err := os.Stat(file); os.IsNotExist(err) {
				file = "~/swagger.json"
			}

			http.ServeFile(w, r, file)
			return
		}

		next.ServeHTTPC(ctx, w, r)
	})
}
<commit_msg>Swagger middleware fixed.<commit_after>package snakepit

import (
	"net/http"
	"os"

	"github.com/pressly/chi"
	"golang.org/x/net/context"
)

type Swagger struct{}

func NewSwagger() func(next chi.Handler) chi.Handler {
	swagger := &Swagger{}
	return swagger.middleware
}

func (rec *Swagger) middleware(next chi.Handler) chi.Handler {
	return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/swagger" {
			w.Header().Add("Access-Control-Allow-Origin", "*")
			w.Header().Add("Access-Control-Allow-Methods", "GET")

			file := "./swagger.json"
			if _, err := os.Stat(file); os.IsNotExist(err) {
				file = "$HOME/swagger.json"
			}

			http.ServeFile(w, r, file)
			return
		}

		next.ServeHTTPC(ctx, w, r)
	})
}
<|endoftext|>


<commit_before>package astutil

import (
	"go/ast"
)

// IdentifiersInStatement returns all identifiers with their found in a statement.
func IdentifiersInStatement(stmt ast.Stmt) []ast.Expr {
	w := &identifierWalker{}

	ast.Walk(w, stmt)

	return w.identifiers
}

type identifierWalker struct {
	identifiers []ast.Expr
}

func (w *identifierWalker) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.Ident:
		w.identifiers = append(w.identifiers, n)

		return nil
	case *ast.SelectorExpr:
		w.identifiers = append(w.identifiers, n)

		return nil
	}

	return w
}

// Functions returns all found functions.
func Functions(n ast.Node) []*ast.FuncDecl {
	w := &functionWalker{}

	ast.Walk(w, n)

	return w.functions
}

type functionWalker struct {
	functions []*ast.FuncDecl
}

func (w *functionWalker) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.FuncDecl:
		w.functions = append(w.functions, n)

		return nil
	}

	return w
}
<commit_msg>Fix, ignore "nil" identifier<commit_after>package astutil

import (
	"go/ast"
)

// IdentifiersInStatement returns all identifiers with their found in a statement.
func IdentifiersInStatement(stmt ast.Stmt) []ast.Expr {
	w := &identifierWalker{}

	ast.Walk(w, stmt)

	return w.identifiers
}

type identifierWalker struct {
	identifiers []ast.Expr
}

func (w *identifierWalker) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.Ident:
		if n.Name != "nil" {
			w.identifiers = append(w.identifiers, n)
		}

		return nil
	case *ast.SelectorExpr:
		w.identifiers = append(w.identifiers, n)

		return nil
	}

	return w
}

// Functions returns all found functions.
func Functions(n ast.Node) []*ast.FuncDecl {
	w := &functionWalker{}

	ast.Walk(w, n)

	return w.functions
}

type functionWalker struct {
	functions []*ast.FuncDecl
}

func (w *functionWalker) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.FuncDecl:
		w.functions = append(w.functions, n)

		return nil
	}

	return w
}
<|endoftext|>


<commit_before>package sys

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	uuid "github.com/twinj/uuid"

	common "github.com/dvonthenen/goxplatform/common"
	fs "github.com/dvonthenen/goxplatform/fs"
	run "github.com/dvonthenen/goxplatform/run"
	str "github.com/dvonthenen/goxplatform/str"
)

const (
	//OsUnknown unknown OS
	OsUnknown = 0

	//OsRhel is RHEL
	OsRhel = 1

	//OsSuse is SuSE
	OsSuse = 2

	//OsUbuntu is Ubuntu
	OsUbuntu = 3

	//OsCoreOs is CoreOS
	OsCoreOs = 4

	//OsMac is OSX
	OsMac = 5
)

var (
	//ErrSrcNotExist src file doesnt exist
	ErrSrcNotExist = errors.New("Source file does not exist")

	//ErrSrcNotRegularFile src file is not a regular file
	ErrSrcNotRegularFile = errors.New("Source file is not a regular file")

	//ErrDstNotRegularFile dst file is not a regular file
	ErrDstNotRegularFile = errors.New("Destination file is not a regular file")

	//ErrUnknownOsVersion unable to determine OS version
	ErrUnknownOsVersion = errors.New("Unknown OS version")
)

//Sys is a static class that provides System related functions
type Sys struct {
	run *run.Run
	fs  *fs.Fs
	str *str.Str
}

//NewSys generates a Sys object
func NewSys() *Sys {
	myRun := run.NewRun()
	myFs := fs.NewFs()
	myStr := str.NewStr()
	mySys := &Sys{
		run: myRun,
		fs:  myFs,
		str: myStr,
	}
	return mySys
}

//GetUUID generates a UUID
func (sys *Sys) GetUUID() []byte {
	myUUID := uuid.NewV1()
	log.Debugln("UUID Generated:", myUUID.String())
	return myUUID.Bytes()
}

//GetUUIDStr generates a UUID
func (sys *Sys) GetUUIDStr() string {
	myUUID := uuid.NewV1()
	log.Debugln("UUID Generated:", myUUID.String())
	return myUUID.String()
}

//GetOsType gets the OS type
func (sys *Sys) GetOsType() int {
	log.Debugln("GetOsType ENTER")

	osType := OsUnknown
	if sys.fs.DoesFileExist("/etc/redhat-release") {
		osType = OsRhel
	} else if sys.fs.DoesFileExist("/etc/SuSE-release") {
		osType = OsSuse
	} else if sys.fs.DoesFileExist("/etc/lsb-release") {
		osType = OsUbuntu
		//	} else if sys.fs.DoesFileExist("/etc/release") {
		//		return OsCoreOs
	} else {
		out, err := sys.run.CommandOutput("uname -s")
		if err == nil && strings.EqualFold(out, "Darwin") {
			osType = OsMac
		} else {
			log.Warnln("Unable to determine OS type")
		}
	}

	log.Debugln("GetOsType =", osType)
	log.Debugln("GetOsType LEAVE")
	return osType
}

func parseVersionFromString(str string) (int, int, error) {
	strs := strings.Split(str, ".")
	imajor, errMajor := strconv.Atoi(strs[0])
	if errMajor != nil {
		return 0, 0, errMajor
	}
	iminor, errMinor := strconv.Atoi(strs[1])
	if errMinor != nil {
		return 0, 0, errMinor
	}
	return imajor, iminor, nil
}

//GetOsVersion returns the major minor version of the OS
func (sys *Sys) GetOsVersion() (int, int, error) {
	log.Debugln("GetOsVersion ENTER")

	itype := sys.GetOsType()
	switch itype {
	case OsRhel:
		data, errRead := ioutil.ReadFile("/etc/redhat-release")
		if errRead != nil {
			return 0, 0, errRead
		}
		log.Debugln(string(data))
		needles, errRegex := sys.str.RegexMatch(string(data), " ([0-9]+\\.[0-9]+[\\.]*[0-9]*) ")
		if errRegex != nil {
			return 0, 0, errRegex
		}
		return parseVersionFromString(needles[0])

	case OsSuse:
		return 0, 0, common.ErrNotImplemented //TODO

	case OsUbuntu:
		data, errRead := ioutil.ReadFile("/etc/lsb-release")
		if errRead != nil {
			return 0, 0, errRead
		}
		log.Debugln(string(data))
		needles, errRegex := sys.str.RegexMatch(string(data), "DISTRIB_RELEASE=([0-9]+\\.[0-9]+[\\.]*[0-9]*) ")
		if errRegex != nil {
			return 0, 0, errRegex
		}
		return parseVersionFromString(needles[0])

	case OsCoreOs:
		return 0, 0, common.ErrNotImplemented //TODO

	case OsMac:
		return 0, 0, common.ErrNotImplemented //TODO
	}

	log.Debugln("GetOsVersion LEAVE")

	return 0, 0, ErrUnknownOsVersion
}

//GetOsStrByType gets the OS string
func (sys *Sys) GetOsStrByType(iType int) string {
	log.Debugln("GetOsStrByType ENTER")

	osStr := "Unknown"
	switch iType {
	case OsRhel:
		osStr = "RHEL"
	case OsSuse:
		osStr = "SUSE"
	case OsUbuntu:
		osStr = "Ubuntu"
	case OsCoreOs:
		osStr = "CoreOS"
	case OsMac:
		osStr = "OSX"
	}

	log.Debugln("GetOsStrByType =", osStr)
	log.Debugln("GetOsStrByType LEAVE")
	return osStr
}

//GetRunningKernelVersion returns the running kernel version
func (sys *Sys) GetRunningKernelVersion() (string, error) {
	log.Debugln("GetRunningKernelVersion ENTER")

	cmdline := "uname -r"
	output, err := sys.run.CommandOutput(cmdline)
	if err != nil {
		log.Debugln("runCommandOutput Failed:", err)
		log.Debugln("GetRunningKernelVersion LEAVE")
		return "", err
	}

	version := output

	log.Debugln("GetRunningKernelVersion Kernel:", version)
	log.Debugln("GetRunningKernelVersion LEAVE")

	return version, nil
}

//GetDeviceList returns the list of all devices on the system
func (sys *Sys) GetDeviceList() ([]string, error) {
	log.Debugln("GetDeviceList ENTER")

	list := []string{}

	outputCmd := "fdisk -l | grep \\/dev\\/"
	output, err := sys.run.CommandOutput(outputCmd)
	if err != nil {
		log.Errorln("Failed to get device list. Err:", err)
		log.Debugln("GetDeviceList LEAVE")
		return list, err
	}

	buffer := bytes.NewBufferString(output)
	for {
		str, err := buffer.ReadString('\n')

		needles, errRegex := sys.str.RegexMatch(str, "Disk (/dev/.*): ")
		if errRegex != nil {
			log.Warnln("RegexMatch Failed. Err:", err)
			if err == io.EOF {
				break
			}
			continue
		}
		if len(needles) != 2 {
			fmt.Println("Incorrect output size:", len(needles))
			if err == io.EOF {
				break
			}
			continue
		}

		device := needles[0]
		log.Debugln("Device Found:", device)

		list = append(list, device)

		if err == io.EOF {
			break
		}
	}

	log.Debugln("GetDeviceList Succeeded. Device Count:", len(list))
	log.Debugln("GetDeviceList LEAVE")

	return list, nil
}

//GetInUseDeviceList returns the list of all devices on the system
func (sys *Sys) GetInUseDeviceList() ([]string, error) {
	log.Debugln("GetInUseDeviceList ENTER")

	list := []string{}

	outputCmd := "blkid"
	output, err := sys.run.CommandOutput(outputCmd)
	if err != nil {
		log.Errorln("Failed to get blkid list. Err:", err)
		log.Debugln("GetInUseDeviceList LEAVE")
		return list, err
	}

	buffer := bytes.NewBufferString(output)
	for {
		str, err := buffer.ReadString('\n')
		if err == io.EOF {
			break
		}

		needles, errRegex := sys.str.RegexMatch(str, "(/dev/.*):")
		if errRegex != nil {
			log.Errorln("RegexMatch Failed. Err:", err)
			log.Debugln("GetInUseDeviceList LEAVE")
			return list, err
		}
		device := needles[0]
		log.Debugln("Device Found:", device)

		list = append(list, device)
	}

	log.Debugln("GetInUseDeviceList Succeeded. Device Count:", len(list))
	log.Debugln("GetInUseDeviceList LEAVE")

	return list, nil
}
<commit_msg>Fix the index<commit_after>package sys

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	uuid "github.com/twinj/uuid"

	common "github.com/dvonthenen/goxplatform/common"
	fs "github.com/dvonthenen/goxplatform/fs"
	run "github.com/dvonthenen/goxplatform/run"
	str "github.com/dvonthenen/goxplatform/str"
)

const (
	//OsUnknown unknown OS
	OsUnknown = 0

	//OsRhel is RHEL
	OsRhel = 1

	//OsSuse is SuSE
	OsSuse = 2

	//OsUbuntu is Ubuntu
	OsUbuntu = 3

	//OsCoreOs is CoreOS
	OsCoreOs = 4

	//OsMac is OSX
	OsMac = 5
)

var (
	//ErrSrcNotExist src file doesnt exist
	ErrSrcNotExist = errors.New("Source file does not exist")

	//ErrSrcNotRegularFile src file is not a regular file
	ErrSrcNotRegularFile = errors.New("Source file is not a regular file")

	//ErrDstNotRegularFile dst file is not a regular file
	ErrDstNotRegularFile = errors.New("Destination file is not a regular file")

	//ErrUnknownOsVersion unable to determine OS version
	ErrUnknownOsVersion = errors.New("Unknown OS version")
)

//Sys is a static class that provides System related functions
type Sys struct {
	run *run.Run
	fs  *fs.Fs
	str *str.Str
}

//NewSys generates a Sys object
func NewSys() *Sys {
	myRun := run.NewRun()
	myFs := fs.NewFs()
	myStr := str.NewStr()
	mySys := &Sys{
		run: myRun,
		fs:  myFs,
		str: myStr,
	}
	return mySys
}

//GetUUID generates a UUID
func (sys *Sys) GetUUID() []byte {
	myUUID := uuid.NewV1()
	log.Debugln("UUID Generated:", myUUID.String())
	return myUUID.Bytes()
}

//GetUUIDStr generates a UUID
func (sys *Sys) GetUUIDStr() string {
	myUUID := uuid.NewV1()
	log.Debugln("UUID Generated:", myUUID.String())
	return myUUID.String()
}

//GetOsType gets the OS type
func (sys *Sys) GetOsType() int {
	log.Debugln("GetOsType ENTER")

	osType := OsUnknown
	if sys.fs.DoesFileExist("/etc/redhat-release") {
		osType = OsRhel
	} else if sys.fs.DoesFileExist("/etc/SuSE-release") {
		osType = OsSuse
	} else if sys.fs.DoesFileExist("/etc/lsb-release") {
		osType = OsUbuntu
		//	} else if sys.fs.DoesFileExist("/etc/release") {
		//		return OsCoreOs
	} else {
		out, err := sys.run.CommandOutput("uname -s")
		if err == nil && strings.EqualFold(out, "Darwin") {
			osType = OsMac
		} else {
			log.Warnln("Unable to determine OS type")
		}
	}

	log.Debugln("GetOsType =", osType)
	log.Debugln("GetOsType LEAVE")
	return osType
}

func parseVersionFromString(str string) (int, int, error) {
	strs := strings.Split(str, ".")
	imajor, errMajor := strconv.Atoi(strs[0])
	if errMajor != nil {
		return 0, 0, errMajor
	}
	iminor, errMinor := strconv.Atoi(strs[1])
	if errMinor != nil {
		return 0, 0, errMinor
	}
	return imajor, iminor, nil
}

//GetOsVersion returns the major minor version of the OS
func (sys *Sys) GetOsVersion() (int, int, error) {
	log.Debugln("GetOsVersion ENTER")

	itype := sys.GetOsType()
	switch itype {
	case OsRhel:
		data, errRead := ioutil.ReadFile("/etc/redhat-release")
		if errRead != nil {
			return 0, 0, errRead
		}
		log.Debugln(string(data))
		needles, errRegex := sys.str.RegexMatch(string(data), " ([0-9]+\\.[0-9]+[\\.]*[0-9]*) ")
		if errRegex != nil {
			return 0, 0, errRegex
		}
		return parseVersionFromString(needles[0])

	case OsSuse:
		return 0, 0, common.ErrNotImplemented //TODO

	case OsUbuntu:
		data, errRead := ioutil.ReadFile("/etc/lsb-release")
		if errRead != nil {
			return 0, 0, errRead
		}
		log.Debugln(string(data))
		needles, errRegex := sys.str.RegexMatch(string(data), "DISTRIB_RELEASE=([0-9]+\\.[0-9]+[\\.]*[0-9]*) ")
		if errRegex != nil {
			return 0, 0, errRegex
		}
		return parseVersionFromString(needles[0])

	case OsCoreOs:
		return 0, 0, common.ErrNotImplemented //TODO

	case OsMac:
		return 0, 0, common.ErrNotImplemented //TODO
	}

	log.Debugln("GetOsVersion LEAVE")

	return 0, 0, ErrUnknownOsVersion
}

//GetOsStrByType gets the OS string
func (sys *Sys) GetOsStrByType(iType int) string {
	log.Debugln("GetOsStrByType ENTER")

	osStr := "Unknown"
	switch iType {
	case OsRhel:
		osStr = "RHEL"
	case OsSuse:
		osStr = "SUSE"
	case OsUbuntu:
		osStr = "Ubuntu"
	case OsCoreOs:
		osStr = "CoreOS"
	case OsMac:
		osStr = "OSX"
	}

	log.Debugln("GetOsStrByType =", osStr)
	log.Debugln("GetOsStrByType LEAVE")
	return osStr
}

//GetRunningKernelVersion returns the running kernel version
func (sys *Sys) GetRunningKernelVersion() (string, error) {
	log.Debugln("GetRunningKernelVersion ENTER")

	cmdline := "uname -r"
	output, err := sys.run.CommandOutput(cmdline)
	if err != nil {
		log.Debugln("runCommandOutput Failed:", err)
		log.Debugln("GetRunningKernelVersion LEAVE")
		return "", err
	}

	version := output

	log.Debugln("GetRunningKernelVersion Kernel:", version)
	log.Debugln("GetRunningKernelVersion LEAVE")

	return version, nil
}

//GetDeviceList returns the list of all devices on the system
func (sys *Sys) GetDeviceList() ([]string, error) {
	log.Debugln("GetDeviceList ENTER")

	list := []string{}

	outputCmd := "fdisk -l | grep \\/dev\\/"
	output, err := sys.run.CommandOutput(outputCmd)
	if err != nil {
		log.Errorln("Failed to get device list. Err:", err)
		log.Debugln("GetDeviceList LEAVE")
		return list, err
	}

	buffer := bytes.NewBufferString(output)
	for {
		str, err := buffer.ReadString('\n')

		needles, errRegex := sys.str.RegexMatch(str, "Disk (/dev/.*): ")
		if errRegex != nil {
			log.Warnln("RegexMatch Failed. Err:", err)
			if err == io.EOF {
				break
			}
			continue
		}
		if len(needles) != 2 {
			fmt.Println("Incorrect output size:", len(needles))
			if err == io.EOF {
				break
			}
			continue
		}

		device := needles[1]
		log.Debugln("Device Found:", device)

		list = append(list, device)

		if err == io.EOF {
			break
		}
	}

	log.Debugln("GetDeviceList Succeeded. Device Count:", len(list))
	log.Debugln("GetDeviceList LEAVE")

	return list, nil
}

//GetInUseDeviceList returns the list of all devices on the system
func (sys *Sys) GetInUseDeviceList() ([]string, error) {
	log.Debugln("GetInUseDeviceList ENTER")

	list := []string{}

	outputCmd := "blkid"
	output, err := sys.run.CommandOutput(outputCmd)
	if err != nil {
		log.Errorln("Failed to get blkid list. Err:", err)
		log.Debugln("GetInUseDeviceList LEAVE")
		return list, err
	}

	buffer := bytes.NewBufferString(output)
	for {
		str, err := buffer.ReadString('\n')
		if err == io.EOF {
			break
		}

		needles, errRegex := sys.str.RegexMatch(str, "(/dev/.*):")
		if errRegex != nil {
			log.Errorln("RegexMatch Failed. Err:", err)
			log.Debugln("GetInUseDeviceList LEAVE")
			return list, err
		}
		device := needles[0]
		log.Debugln("Device Found:", device)

		list = append(list, device)
	}

	log.Debugln("GetInUseDeviceList Succeeded. Device Count:", len(list))
	log.Debugln("GetInUseDeviceList LEAVE")

	return list, nil
}
<|endoftext|>