<commit_before>// Copyright 2013 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 qopher

import (
	"html/template"
	"net/http"

	"appengine"
	"appengine/datastore"
	"appengine/user"

	"qopher/task"
)

type FrontPage struct {
	NTotal          int
	QueueEmail      string // showing queue for this email address
	QueueEmailShort string
	Yours           []*Task
	Other           []*Task
}

func serveFront(rw http.ResponseWriter, r *http.Request) {
	if !getMethod(r) {
		http.Error(rw, "bad method", http.StatusMethodNotAllowed)
		return
	}
	c := appengine.NewContext(r)
	u := user.Current(c)
	if u == nil {
		http.Error(rw, "login required", http.StatusUnauthorized)
		return
	}

	email := r.FormValue("q")
	if email == "" {
		email = u.Email
	}
	email = mapEmail(email)

	if email == "crash" {
		panic("fake crash")
	}

	q := datastore.NewQuery("Task").Filter("Closed = ", false)
	var tasks []*Task
	_, err := q.GetAll(c, &tasks)
	if err != nil {
		c.Errorf("GetAll: %v", err)
		http.Error(rw, err.Error(), 500)
		return
	}
	numIssues := len(tasks)

	var yours, other []*Task
	for _, t := range tasks {
		if t.Owner == email {
			yours = append(yours, t)
		} else {
			other = append(other, t)
		}
	}

	const maxOther = 50
	if len(other) > maxOther {
		other = other[:maxOther]
	}

	page := &FrontPage{
		NTotal:          numIssues,
		QueueEmail:      email,
		QueueEmailShort: emailToShort(email),
		Yours:           yours,
		Other:           other,
	}

	err = frontPage.ExecuteTemplate(rw, "front", page)
	if err != nil && r.Method != "HEAD" {
		c.Errorf("template error: %v", err)
	}
}

var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{
	"taskURL": func(taskType, id string) string {
		return task.TypeMap[taskType].TaskURL(id)
	},
	"shortOwner": func(t *Task) string {
		if t.Owner == "" {
			return ""
		}
		return emailToShort(t.Owner) + ": "
	},
}).Parse(`
<!doctype html>

    <title>qopher - Go queue</title>

qopher: the Gopher Queue

WORK IN PROGRESS

Total {{$.NTotal}} open tasks

Viewing queue for {{$.QueueEmail}} (change with ?q= nickname or email)

{{if $.Yours}}

Assigned to {{$.QueueEmailShort}} ({{len $.Yours}})

  {{range $i, $t := $.Yours}}

{{$t.Type}}.{{$t.ID}}: {{$t.Title}}

  {{end}}

{{else}}

Nothing assigned to {{$.QueueEmail}}

{{end}}

{{if $.Other}}

Some other open tasks

  {{range $i, $t := $.Other}}

{{$t.Type}}.{{$t.ID}}: {{shortOwner $t}} {{$t.Title}}

  {{end}}

{{end}}

`))
<commit_msg>Link to open assigned issue tracker issues.<commit_after>// Copyright 2013 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 qopher

import (
	"html/template"
	"net/http"

	"appengine"
	"appengine/datastore"
	"appengine/user"

	"qopher/task"
)

type FrontPage struct {
	NTotal          int
	QueueEmail      string // showing queue for this email address
	QueueEmailShort string
	Yours           []*Task
	Other           []*Task
}

func serveFront(rw http.ResponseWriter, r *http.Request) {
	if !getMethod(r) {
		http.Error(rw, "bad method", http.StatusMethodNotAllowed)
		return
	}
	c := appengine.NewContext(r)
	u := user.Current(c)
	if u == nil {
		http.Error(rw, "login required", http.StatusUnauthorized)
		return
	}

	email := r.FormValue("q")
	if email == "" {
		email = u.Email
	}
	email = mapEmail(email)

	if email == "crash" {
		panic("fake crash")
	}

	q := datastore.NewQuery("Task").Filter("Closed = ", false)
	var tasks []*Task
	_, err := q.GetAll(c, &tasks)
	if err != nil {
		c.Errorf("GetAll: %v", err)
		http.Error(rw, err.Error(), 500)
		return
	}
	numIssues := len(tasks)

	var yours, other []*Task
	for _, t := range tasks {
		if t.Owner == email {
			yours = append(yours, t)
		} else {
			other = append(other, t)
		}
	}

	const maxOther = 50
	if len(other) > maxOther {
		other = other[:maxOther]
	}

	page := &FrontPage{
		NTotal:          numIssues,
		QueueEmail:      email,
		QueueEmailShort: emailToShort(email),
		Yours:           yours,
		Other:           other,
	}

	err = frontPage.ExecuteTemplate(rw, "front", page)
	if err != nil && r.Method != "HEAD" {
		c.Errorf("template error: %v", err)
	}
}

var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{
	"taskURL": func(taskType, id string) string {
		return task.TypeMap[taskType].TaskURL(id)
	},
	"shortOwner": func(t *Task) string {
		if t.Owner == "" {
			return ""
		}
		return emailToShort(t.Owner) + ": "
	},
}).Parse(`
<!doctype html>

    <title>qopher - Go queue</title>

qopher: the Gopher Queue

WORK IN PROGRESS

Total {{$.NTotal}} open tasks

Viewing queue for {{$.QueueEmail}} (change with ?q= nickname or email)

{{if $.Yours}}

Assigned to {{$.QueueEmailShort}} ({{len $.Yours}})

This list doesn't include your open and assigned issues.

  {{range $i, $t := $.Yours}}

{{$t.Type}}.{{$t.ID}}: {{$t.Title}}

  {{end}}

{{else}}

Nothing assigned to {{$.QueueEmail}}

Also check your open and assigned issues.

{{end}}

{{if $.Other}}

Some other open tasks

  {{range $i, $t := $.Other}}

{{$t.Type}}.{{$t.ID}}: {{shortOwner $t}} {{$t.Title}}

  {{end}}

{{end}}

`))
<|endoftext|>


<commit_before>package mpawselasticsearch

import (
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
	KeyPrefix       string
	LabelPrefix     string
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p *ESPlugin) MetricKeyPrefix() string {
	if p.KeyPrefix == "" {
		return "es"
	}
	return p.KeyPrefix
}

// MetricLabelPrefix ...
func (p *ESPlugin) MetricLabelPrefix() string {
	if p.LabelPrefix == "" {
		return "AWS ES"
	}
	return p.LabelPrefix
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPointFromCloudWatch(metric metrics) (*cloudwatch.Datapoint, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return nil, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return nil, nil
	}

	latest := new(time.Time)
	var latestDp *cloudwatch.Datapoint
	for _, dp := range datapoints {
		if dp.Timestamp.Before(*latest) {
			continue
		}

		latest = dp.Timestamp
		latestDp = dp
	}

	return latestDp, nil
}

func mergeStatFromDatapoint(stat map[string]float64, dp *cloudwatch.Datapoint, metric metrics) map[string]float64 {
	if dp != nil {
		var value float64
		if metric.Type == metricsTypeAverage {
			value = *dp.Average
		} else if metric.Type == metricsTypeSum {
			value = *dp.Sum
		} else if metric.Type == metricsTypeMaximum {
			value = *dp.Maximum
		} else if metric.Type == metricsTypeMinimum {
			value = *dp.Minimum
		}
		if metric.Name == "ClusterUsedSpace" || metric.Name == "MasterFreeStorageSpace" || metric.Name == "FreeStorageSpace" {
			// MBytes -> Bytes
			value = value * 1024 * 1024
		}
		stat[metric.Name] = value
	}
	return stat
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "KibanaHealthyNodes", Type: metricsTypeMinimum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "MasterReachableFromNode", Type: metricsTypeMinimum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeAverage},
		{Name: "WriteThroughput", Type: metricsTypeAverage},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeAverage},
		{Name: "WriteIOPS", Type: metricsTypeAverage},
	} {
		v, err := p.getLastPointFromCloudWatch(met)
		if err == nil {
			stat = mergeStatFromDatapoint(stat, v, met)
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := p.MetricLabelPrefix()
	return map[string]mp.Graphs{
		"ClusterStatus": {
			Label: (labelPrefix + " ClusterStatus"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterStatus.green", Label: "green"},
				{Name: "ClusterStatus.yellow", Label: "yellow"},
				{Name: "ClusterStatus.red", Label: "red"},
			},
		},
		"Nodes": {
			Label: (labelPrefix + " Nodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "Nodes", Label: "Nodes"},
			},
		},
		"SearchableDocuments": {
			Label: (labelPrefix + " SearchableDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "SearchableDocuments", Label: "SearchableDocuments"},
			},
		},
		"DeletedDocuments": {
			Label: (labelPrefix + " DeletedDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DeletedDocuments", Label: "DeletedDocuments"},
			},
		},
		"CPUUtilization": {
			Label: (labelPrefix + " CPU Utilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "CPUUtilization", Label: "CPUUtilization"},
			},
		},
		"FreeStorageSpace": {
			Label: (labelPrefix + " Free Storage Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
			},
		},
		"ClusterUsedSpace": {
			Label: (labelPrefix + " Cluster Used Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
			},
		},
		"ClusterIndexWritesBlocked": {
			Label: (labelPrefix + " ClusterIndexWritesBlocked"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
			},
		},
		"JVMMemoryPressure": {
			Label: (labelPrefix + " JVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
			},
		},
		"AutomatedSnapshotFailure": {
			Label: (labelPrefix + " AutomatedSnapshotFailure"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
			},
		},
		"KibanaHealthyNodes": {
			Label: (labelPrefix + " KibanaHealthyNodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "KibanaHealthyNodes", Label: "KibanaHealthyNodes"},
			},
		},
		"MasterCPUUtilization": {
			Label: (labelPrefix + " MasterCPUUtilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
			},
		},
		"MasterFreeStorageSpace": {
			Label: (labelPrefix + " MasterFreeStorageSpace"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
			},
		},
		"MasterJVMMemoryPressure": {
			Label: (labelPrefix + " MasterJVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
			},
		},
		"MasterReachableFromNode": {
			Label: (labelPrefix + " MasterReachableFromNode"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterReachableFromNode", Label: "MasterReachableFromNode"},
			},
		},
		"Latency": {
			Label: (labelPrefix + " Latency"),
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "ReadLatency", Label: "ReadLatency"},
				{Name: "WriteLatency", Label: "WriteLatency"},
			},
		},
		"Throughput": {
			Label: (labelPrefix + " Throughput"),
			Unit:  "bytes/sec",
			Metrics: []mp.Metrics{
				{Name: "ReadThroughput", Label: "ReadThroughput"},
				{Name: "WriteThroughput", Label: "WriteThroughput"},
			},
		},
		"DiskQueueDepth": {
			Label: (labelPrefix + " DiskQueueDepth"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
			},
		},
		"IOPS": {
			Label: (labelPrefix + " IOPS"),
			Unit:  "iops",
			Metrics: []mp.Metrics{
				{Name: "ReadIOPS", Label: "ReadIOPS"},
				{Name: "WriteIOPS", Label: "WriteIOPS"},
			},
		},
	}
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	optKeyPrefix := flag.String("metric-key-prefix", "es", "Metric key prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "AWS ES", "Metric label prefix")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey
	es.KeyPrefix = *optKeyPrefix
	es.LabelPrefix = *optLabelPrefix

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<commit_msg>must not be a pointer receiver<commit_after>package mpawselasticsearch

import (
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
	KeyPrefix       string
	LabelPrefix     string
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p ESPlugin) MetricKeyPrefix() string {
	if p.KeyPrefix == "" {
		return "es"
	}
	return p.KeyPrefix
}

// MetricLabelPrefix ...
func (p *ESPlugin) MetricLabelPrefix() string {
	if p.LabelPrefix == "" {
		return "AWS ES"
	}
	return p.LabelPrefix
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPointFromCloudWatch(metric metrics) (*cloudwatch.Datapoint, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return nil, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return nil, nil
	}

	latest := new(time.Time)
	var latestDp *cloudwatch.Datapoint
	for _, dp := range datapoints {
		if dp.Timestamp.Before(*latest) {
			continue
		}

		latest = dp.Timestamp
		latestDp = dp
	}

	return latestDp, nil
}

func mergeStatFromDatapoint(stat map[string]float64, dp *cloudwatch.Datapoint, metric metrics) map[string]float64 {
	if dp != nil {
		var value float64
		if metric.Type == metricsTypeAverage {
			value = *dp.Average
		} else if metric.Type == metricsTypeSum {
			value = *dp.Sum
		} else if metric.Type == metricsTypeMaximum {
			value = *dp.Maximum
		} else if metric.Type == metricsTypeMinimum {
			value = *dp.Minimum
		}
		if metric.Name == "ClusterUsedSpace" || metric.Name == "MasterFreeStorageSpace" || metric.Name == "FreeStorageSpace" {
			// MBytes -> Bytes
			value = value * 1024 * 1024
		}
		stat[metric.Name] = value
	}
	return stat
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "KibanaHealthyNodes", Type: metricsTypeMinimum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "MasterReachableFromNode", Type: metricsTypeMinimum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeAverage},
		{Name: "WriteThroughput", Type: metricsTypeAverage},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeAverage},
		{Name: "WriteIOPS", Type: metricsTypeAverage},
	} {
		v, err := p.getLastPointFromCloudWatch(met)
		if err == nil {
			stat = mergeStatFromDatapoint(stat, v, met)
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := p.MetricLabelPrefix()
	return map[string]mp.Graphs{
		"ClusterStatus": {
			Label: (labelPrefix + " ClusterStatus"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterStatus.green", Label: "green"},
				{Name: "ClusterStatus.yellow", Label: "yellow"},
				{Name: "ClusterStatus.red", Label: "red"},
			},
		},
		"Nodes": {
			Label: (labelPrefix + " Nodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "Nodes", Label: "Nodes"},
			},
		},
		"SearchableDocuments": {
			Label: (labelPrefix + " SearchableDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "SearchableDocuments", Label: "SearchableDocuments"},
			},
		},
		"DeletedDocuments": {
			Label: (labelPrefix + " DeletedDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DeletedDocuments", Label: "DeletedDocuments"},
			},
		},
		"CPUUtilization": {
			Label: (labelPrefix + " CPU Utilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "CPUUtilization", Label: "CPUUtilization"},
			},
		},
		"FreeStorageSpace": {
			Label: (labelPrefix + " Free Storage Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
			},
		},
		"ClusterUsedSpace": {
			Label: (labelPrefix + " Cluster Used Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
			},
		},
		"ClusterIndexWritesBlocked": {
			Label: (labelPrefix + " ClusterIndexWritesBlocked"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
			},
		},
		"JVMMemoryPressure": {
			Label: (labelPrefix + " JVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
			},
		},
		"AutomatedSnapshotFailure": {
			Label: (labelPrefix + " AutomatedSnapshotFailure"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
			},
		},
		"KibanaHealthyNodes": {
			Label: (labelPrefix + " KibanaHealthyNodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "KibanaHealthyNodes", Label: "KibanaHealthyNodes"},
			},
		},
		"MasterCPUUtilization": {
			Label: (labelPrefix + " MasterCPUUtilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
			},
		},
		"MasterFreeStorageSpace": {
			Label: (labelPrefix + " MasterFreeStorageSpace"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
			},
		},
		"MasterJVMMemoryPressure": {
			Label: (labelPrefix + " MasterJVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
			},
		},
		"MasterReachableFromNode": {
			Label: (labelPrefix + " MasterReachableFromNode"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterReachableFromNode", Label: "MasterReachableFromNode"},
			},
		},
		"Latency": {
			Label: (labelPrefix + " Latency"),
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "ReadLatency", Label: "ReadLatency"},
				{Name: "WriteLatency", Label: "WriteLatency"},
			},
		},
		"Throughput": {
			Label: (labelPrefix + " Throughput"),
			Unit:  "bytes/sec",
			Metrics: []mp.Metrics{
				{Name: "ReadThroughput", Label: "ReadThroughput"},
				{Name: "WriteThroughput", Label: "WriteThroughput"},
			},
		},
		"DiskQueueDepth": {
			Label: (labelPrefix + " DiskQueueDepth"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
			},
		},
		"IOPS": {
			Label: (labelPrefix + " IOPS"),
			Unit:  "iops",
			Metrics: []mp.Metrics{
				{Name: "ReadIOPS", Label: "ReadIOPS"},
				{Name: "WriteIOPS", Label: "WriteIOPS"},
			},
		},
	}
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	optKeyPrefix := flag.String("metric-key-prefix", "es", "Metric key prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "AWS ES", "Metric label prefix")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey
	es.KeyPrefix = *optKeyPrefix
	es.LabelPrefix = *optLabelPrefix

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<|endoftext|>


<commit_before>package mpawselasticsearch

import (
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
	KeyPrefix       string
	LabelPrefix     string
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p ESPlugin) MetricKeyPrefix() string {
	if p.KeyPrefix == "" {
		return "es"
	}
	return p.KeyPrefix
}

// MetricLabelPrefix ...
func (p *ESPlugin) MetricLabelPrefix() string {
	if p.LabelPrefix == "" {
		return "AWS ES"
	}
	return p.LabelPrefix
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPointFromCloudWatch(metric metrics) (*cloudwatch.Datapoint, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return nil, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return nil, nil
	}

	latest := new(time.Time)
	var latestDp *cloudwatch.Datapoint
	for _, dp := range datapoints {
		if dp.Timestamp.Before(*latest) {
			continue
		}

		latest = dp.Timestamp
		latestDp = dp
	}

	return latestDp, nil
}

func mergeStatFromDatapoint(stat map[string]float64, dp *cloudwatch.Datapoint, metric metrics) map[string]float64 {
	if dp != nil {
		var value float64
		if metric.Type == metricsTypeAverage {
			value = *dp.Average
		} else if metric.Type == metricsTypeSum {
			value = *dp.Sum
		} else if metric.Type == metricsTypeMaximum {
			value = *dp.Maximum
		} else if metric.Type == metricsTypeMinimum {
			value = *dp.Minimum
		}
		if metric.Name == "ClusterUsedSpace" || metric.Name == "MasterFreeStorageSpace" || metric.Name == "FreeStorageSpace" {
			// MBytes -> Bytes
			value = value * 1024 * 1024
		}
		stat[metric.Name] = value
	}
	return stat
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "KibanaHealthyNodes", Type: metricsTypeMinimum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "MasterReachableFromNode", Type: metricsTypeMinimum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeAverage},
		{Name: "WriteThroughput", Type: metricsTypeAverage},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeAverage},
		{Name: "WriteIOPS", Type: metricsTypeAverage},
	} {
		v, err := p.getLastPointFromCloudWatch(met)
		if err == nil {
			stat = mergeStatFromDatapoint(stat, v, met)
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := p.MetricLabelPrefix()
	return map[string]mp.Graphs{
		"ClusterStatus": {
			Label: (labelPrefix + " ClusterStatus"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterStatus.green", Label: "green"},
				{Name: "ClusterStatus.yellow", Label: "yellow"},
				{Name: "ClusterStatus.red", Label: "red"},
			},
		},
		"Nodes": {
			Label: (labelPrefix + " Nodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "Nodes", Label: "Nodes"},
			},
		},
		"SearchableDocuments": {
			Label: (labelPrefix + " SearchableDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "SearchableDocuments", Label: "SearchableDocuments"},
			},
		},
		"DeletedDocuments": {
			Label: (labelPrefix + " DeletedDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DeletedDocuments", Label: "DeletedDocuments"},
			},
		},
		"CPUUtilization": {
			Label: (labelPrefix + " CPU Utilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "CPUUtilization", Label: "CPUUtilization"},
			},
		},
		"FreeStorageSpace": {
			Label: (labelPrefix + " Free Storage Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
			},
		},
		"ClusterUsedSpace": {
			Label: (labelPrefix + " Cluster Used Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
			},
		},
		"ClusterIndexWritesBlocked": {
			Label: (labelPrefix + " ClusterIndexWritesBlocked"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
			},
		},
		"JVMMemoryPressure": {
			Label: (labelPrefix + " JVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
			},
		},
		"AutomatedSnapshotFailure": {
			Label: (labelPrefix + " AutomatedSnapshotFailure"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
			},
		},
		"KibanaHealthyNodes": {
			Label: (labelPrefix + " KibanaHealthyNodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "KibanaHealthyNodes", Label: "KibanaHealthyNodes"},
			},
		},
		"MasterCPUUtilization": {
			Label: (labelPrefix + " MasterCPUUtilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
			},
		},
		"MasterFreeStorageSpace": {
			Label: (labelPrefix + " MasterFreeStorageSpace"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
			},
		},
		"MasterJVMMemoryPressure": {
			Label: (labelPrefix + " MasterJVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
			},
		},
		"MasterReachableFromNode": {
			Label: (labelPrefix + " MasterReachableFromNode"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterReachableFromNode", Label: "MasterReachableFromNode"},
			},
		},
		"Latency": {
			Label: (labelPrefix + " Latency"),
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "ReadLatency", Label: "ReadLatency"},
				{Name: "WriteLatency", Label: "WriteLatency"},
			},
		},
		"Throughput": {
			Label: (labelPrefix + " Throughput"),
			Unit:  "bytes/sec",
			Metrics: []mp.Metrics{
				{Name: "ReadThroughput", Label: "ReadThroughput"},
				{Name: "WriteThroughput", Label: "WriteThroughput"},
			},
		},
		"DiskQueueDepth": {
			Label: (labelPrefix + " DiskQueueDepth"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
			},
		},
		"IOPS": {
			Label: (labelPrefix + " IOPS"),
			Unit:  "iops",
			Metrics: []mp.Metrics{
				{Name: "ReadIOPS", Label: "ReadIOPS"},
				{Name: "WriteIOPS", Label: "WriteIOPS"},
			},
		},
	}
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	optKeyPrefix := flag.String("metric-key-prefix", "es", "Metric key prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "AWS ES", "Metric label prefix")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey
	es.KeyPrefix = *optKeyPrefix
	es.LabelPrefix = *optLabelPrefix

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<commit_msg>no pointer<commit_after>package mpawselasticsearch

import (
	"flag"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	mp "github.com/mackerelio/go-mackerel-plugin"
)

const (
	nameSpace          = "AWS/ES"
	metricsTypeAverage = "Average"
	metricsTypeSum     = "Sum"
	metricsTypeMaximum = "Maximum"
	metricsTypeMinimum = "Minimum"
)

type metrics struct {
	Name string
	Type string
}

// ESPlugin mackerel plugin for aws elasticsearch
type ESPlugin struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	Domain          string
	ClientID        string
	CloudWatch      *cloudwatch.CloudWatch
	KeyPrefix       string
	LabelPrefix     string
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p ESPlugin) MetricKeyPrefix() string {
	if p.KeyPrefix == "" {
		return "es"
	}
	return p.KeyPrefix
}

// MetricLabelPrefix ...
func (p ESPlugin) MetricLabelPrefix() string {
	if p.LabelPrefix == "" {
		return "AWS ES"
	}
	return p.LabelPrefix
}

func (p *ESPlugin) prepare() error {
	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	config := aws.NewConfig()
	if p.AccessKeyID != "" && p.SecretAccessKey != "" {
		config = config.WithCredentials(credentials.NewStaticCredentials(p.AccessKeyID, p.SecretAccessKey, ""))
	}
	if p.Region != "" {
		config = config.WithRegion(p.Region)
	}

	p.CloudWatch = cloudwatch.New(sess, config)
	return nil
}

func (p ESPlugin) getLastPointFromCloudWatch(metric metrics) (*cloudwatch.Datapoint, error) {
	now := time.Now()

	dimensions := []*cloudwatch.Dimension{
		{
			Name:  aws.String("DomainName"),
			Value: aws.String(p.Domain),
		},
		{
			Name:  aws.String("ClientId"),
			Value: aws.String(p.ClientID),
		},
	}

	response, err := p.CloudWatch.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{
		Dimensions: dimensions,
		StartTime:  aws.Time(now.Add(time.Duration(180) * time.Second * -1)),
		EndTime:    aws.Time(now),
		MetricName: aws.String(metric.Name),
		Period:     aws.Int64(60),
		Statistics: []*string{aws.String(metric.Type)},
		Namespace:  aws.String(nameSpace),
	})

	if err != nil {
		return nil, err
	}

	datapoints := response.Datapoints
	if len(datapoints) == 0 {
		return nil, nil
	}

	latest := new(time.Time)
	var latestDp *cloudwatch.Datapoint
	for _, dp := range datapoints {
		if dp.Timestamp.Before(*latest) {
			continue
		}

		latest = dp.Timestamp
		latestDp = dp
	}

	return latestDp, nil
}

func mergeStatFromDatapoint(stat map[string]float64, dp *cloudwatch.Datapoint, metric metrics) map[string]float64 {
	if dp != nil {
		var value float64
		if metric.Type == metricsTypeAverage {
			value = *dp.Average
		} else if metric.Type == metricsTypeSum {
			value = *dp.Sum
		} else if metric.Type == metricsTypeMaximum {
			value = *dp.Maximum
		} else if metric.Type == metricsTypeMinimum {
			value = *dp.Minimum
		}
		if metric.Name == "ClusterUsedSpace" || metric.Name == "MasterFreeStorageSpace" || metric.Name == "FreeStorageSpace" {
			// MBytes -> Bytes
			value = value * 1024 * 1024
		}
		stat[metric.Name] = value
	}
	return stat
}

// FetchMetrics interface for mackerelplugin
func (p ESPlugin) FetchMetrics() (map[string]float64, error) {
	stat := make(map[string]float64)

	for _, met := range [...]metrics{
		{Name: "ClusterStatus.green", Type: metricsTypeMinimum},
		{Name: "ClusterStatus.yellow", Type: metricsTypeMaximum},
		{Name: "ClusterStatus.red", Type: metricsTypeMaximum},
		{Name: "Nodes", Type: metricsTypeAverage},
		{Name: "SearchableDocuments", Type: metricsTypeAverage},
		{Name: "DeletedDocuments", Type: metricsTypeAverage},
		{Name: "CPUUtilization", Type: metricsTypeMaximum},
		{Name: "FreeStorageSpace", Type: metricsTypeMinimum},
		{Name: "ClusterUsedSpace", Type: metricsTypeMinimum},
		{Name: "ClusterIndexWritesBlocked", Type: metricsTypeMaximum},
		{Name: "JVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "AutomatedSnapshotFailure", Type: metricsTypeMaximum},
		{Name: "KibanaHealthyNodes", Type: metricsTypeMinimum},
		{Name: "MasterCPUUtilization", Type: metricsTypeMaximum},
		{Name: "MasterFreeStorageSpace", Type: metricsTypeSum},
		{Name: "MasterJVMMemoryPressure", Type: metricsTypeMaximum},
		{Name: "MasterReachableFromNode", Type: metricsTypeMinimum},
		{Name: "ReadLatency", Type: metricsTypeAverage},
		{Name: "WriteLatency", Type: metricsTypeAverage},
		{Name: "ReadThroughput", Type: metricsTypeAverage},
		{Name: "WriteThroughput", Type: metricsTypeAverage},
		{Name: "DiskQueueDepth", Type: metricsTypeAverage},
		{Name: "ReadIOPS", Type: metricsTypeAverage},
		{Name: "WriteIOPS", Type: metricsTypeAverage},
	} {
		v, err := p.getLastPointFromCloudWatch(met)
		if err == nil {
			stat = mergeStatFromDatapoint(stat, v, met)
		} else {
			log.Printf("%s: %s", met, err)
		}
	}

	return stat, nil
}

// GraphDefinition interface for mackerelplugin
func (p ESPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := p.MetricLabelPrefix()
	return map[string]mp.Graphs{
		"ClusterStatus": {
			Label: (labelPrefix + " ClusterStatus"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterStatus.green", Label: "green"},
				{Name: "ClusterStatus.yellow", Label: "yellow"},
				{Name: "ClusterStatus.red", Label: "red"},
			},
		},
		"Nodes": {
			Label: (labelPrefix + " Nodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "Nodes", Label: "Nodes"},
			},
		},
		"SearchableDocuments": {
			Label: (labelPrefix + " SearchableDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "SearchableDocuments", Label: "SearchableDocuments"},
			},
		},
		"DeletedDocuments": {
			Label: (labelPrefix + " DeletedDocuments"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DeletedDocuments", Label: "DeletedDocuments"},
			},
		},
		"CPUUtilization": {
			Label: (labelPrefix + " CPU Utilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "CPUUtilization", Label: "CPUUtilization"},
			},
		},
		"FreeStorageSpace": {
			Label: (labelPrefix + " Free Storage Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "FreeStorageSpace", Label: "FreeStorageSpace"},
			},
		},
		"ClusterUsedSpace": {
			Label: (labelPrefix + " Cluster Used Space"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "ClusterUsedSpace", Label: "ClusterUsedSpace"},
			},
		},
		"ClusterIndexWritesBlocked": {
			Label: (labelPrefix + " ClusterIndexWritesBlocked"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "ClusterIndexWritesBlocked", Label: "ClusterIndexWritesBlocked"},
			},
		},
		"JVMMemoryPressure": {
			Label: (labelPrefix + " JVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "JVMMemoryPressure", Label: "JVMMemoryPressure"},
			},
		},
		"AutomatedSnapshotFailure": {
			Label: (labelPrefix + " AutomatedSnapshotFailure"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "AutomatedSnapshotFailure", Label: "AutomatedSnapshotFailure"},
			},
		},
		"KibanaHealthyNodes": {
			Label: (labelPrefix + " KibanaHealthyNodes"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "KibanaHealthyNodes", Label: "KibanaHealthyNodes"},
			},
		},
		"MasterCPUUtilization": {
			Label: (labelPrefix + " MasterCPUUtilization"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterCPUUtilization", Label: "MasterCPUUtilization"},
			},
		},
		"MasterFreeStorageSpace": {
			Label: (labelPrefix + " MasterFreeStorageSpace"),
			Unit:  "bytes",
			Metrics: []mp.Metrics{
				{Name: "MasterFreeStorageSpace", Label: "MasterFreeStorageSpace"},
			},
		},
		"MasterJVMMemoryPressure": {
			Label: (labelPrefix + " MasterJVMMemoryPressure"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterJVMMemoryPressure", Label: "MasterJVMMemoryPressure"},
			},
		},
		"MasterReachableFromNode": {
			Label: (labelPrefix + " MasterReachableFromNode"),
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "MasterReachableFromNode", Label: "MasterReachableFromNode"},
			},
		},
		"Latency": {
			Label: (labelPrefix + " Latency"),
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "ReadLatency", Label: "ReadLatency"},
				{Name: "WriteLatency", Label: "WriteLatency"},
			},
		},
		"Throughput": {
			Label: (labelPrefix + " Throughput"),
			Unit:  "bytes/sec",
			Metrics: []mp.Metrics{
				{Name: "ReadThroughput", Label: "ReadThroughput"},
				{Name: "WriteThroughput", Label: "WriteThroughput"},
			},
		},
		"DiskQueueDepth": {
			Label: (labelPrefix + " DiskQueueDepth"),
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "DiskQueueDepth", Label: "DiskQueueDepth"},
			},
		},
		"IOPS": {
			Label: (labelPrefix + " IOPS"),
			Unit:  "iops",
			Metrics: []mp.Metrics{
				{Name: "ReadIOPS", Label: "ReadIOPS"},
				{Name: "WriteIOPS", Label: "WriteIOPS"},
			},
		},
	}
}

// Do the plugin
func Do() {
	optRegion := flag.String("region", "", "AWS Region")
	optAccessKeyID := flag.String("access-key-id", "", "AWS Access Key ID")
	optSecretAccessKey := flag.String("secret-access-key", "", "AWS Secret Access Key")
	optClientID := flag.String("client-id", "", "AWS Client ID")
	optDomain := flag.String("domain", "", "ES domain name")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	optKeyPrefix := flag.String("metric-key-prefix", "es", "Metric key prefix")
	optLabelPrefix := flag.String("metric-label-prefix", "AWS ES", "Metric label prefix")
	flag.Parse()

	var es ESPlugin

	if *optRegion == "" {
		ec2metadata := ec2metadata.New(session.New())
		if ec2metadata.Available() {
			es.Region, _ = ec2metadata.Region()
		}
	} else {
		es.Region = *optRegion
	}

	es.Region = *optRegion
	es.Domain = *optDomain
	es.ClientID = *optClientID
	es.AccessKeyID = *optAccessKeyID
	es.SecretAccessKey = *optSecretAccessKey
	es.KeyPrefix = *optKeyPrefix
	es.LabelPrefix = *optLabelPrefix

	err := es.prepare()
	if err != nil {
		log.Fatalln(err)
	}

	helper := mp.NewMackerelPlugin(es)
	helper.Tempfile = *optTempfile

	helper.Run()
}
<|endoftext|>


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

package awscloudwatchlogsexporter

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/model/pdata"
	"go.uber.org/zap"
)

type exporter struct {
	config *Config
	logger *zap.Logger

	startOnce sync.Once
	client    *cloudwatchlogs.CloudWatchLogs // available after startOnce

	seqTokenMu sync.Mutex
	seqToken   string
}

func (e *exporter) Start(ctx context.Context, host component.Host) error {
	var startErr error
	e.startOnce.Do(func() {
		awsConfig := &aws.Config{}
		if e.config.Region != "" {
			awsConfig.Region = aws.String(e.config.Region)
		}
		if e.config.Endpoint != "" {
			awsConfig.Endpoint = aws.String(e.config.Endpoint)
		}
		awsConfig.MaxRetries = aws.Int(1) // retry will be handled by the collector queue
		sess, err := session.NewSession(awsConfig)
		if err != nil {
			startErr = err
			return
		}
		e.client = cloudwatchlogs.New(sess)

		e.logger.Debug("Retrieving Cloud Watch sequence token")
		out, err := e.client.DescribeLogStreams(&cloudwatchlogs.DescribeLogStreamsInput{
			LogGroupName:        aws.String(e.config.LogGroupName),
			LogStreamNamePrefix: aws.String(e.config.LogStreamName),
		})
		if err != nil {
			startErr = err
			return
		}
		if len(out.LogStreams) == 0 {
			startErr = errors.New("cannot find log group and stream")
			return
		}
		stream := out.LogStreams[0]
		e.seqToken = *stream.UploadSequenceToken
	})
	return startErr
}

func (e *exporter) Shutdown(ctx context.Context) error {
	// TODO(jbd): Signal shutdown to flush the logs.
	return nil
}

func (e *exporter) PushLogs(ctx context.Context, ld pdata.Logs) (err error) {
	// TODO(jbd): Relax this once CW Logs support ingest
	// without sequence tokens.
	e.seqTokenMu.Lock()
	defer e.seqTokenMu.Unlock()

	logEvents, _ := logsToCWLogs(e.logger, ld)
	if len(logEvents) == 0 {
		return nil
	}

	e.logger.Debug("Putting log events", zap.Int("num_of_events", len(logEvents)))
	input := &cloudwatchlogs.PutLogEventsInput{
		LogGroupName:  aws.String(e.config.LogGroupName),
		LogStreamName: aws.String(e.config.LogStreamName),
		LogEvents:     logEvents,
		SequenceToken: aws.String(e.seqToken),
	}
	out, err := e.client.PutLogEvents(input)
	if err != nil {
		return err
	}
	if info := out.RejectedLogEventsInfo; info != nil {
		return fmt.Errorf("log event rejected")
	}
	e.logger.Debug("Log events are successfully put")

	e.seqToken = *out.NextSequenceToken
	return nil
}

func logsToCWLogs(logger *zap.Logger, ld pdata.Logs) ([]*cloudwatchlogs.InputLogEvent, int) {
	n := ld.ResourceLogs().Len()
	if n == 0 {
		return []*cloudwatchlogs.InputLogEvent{}, 0
	}

	var dropped int
	out := make([]*cloudwatchlogs.InputLogEvent, 0) // TODO(jbd): set a better capacity

	rls := ld.ResourceLogs()
	for i := 0; i < rls.Len(); i++ {
		rl := rls.At(i)
		resourceAttrs := attrsValue(rl.Resource().Attributes())

		ills := rl.InstrumentationLibraryLogs()
		for j := 0; j < ills.Len(); j++ {
			ils := ills.At(j)
			logs := ils.Logs()
			for k := 0; k < logs.Len(); k++ {
				log := logs.At(k)
				event, err := logToCWLog(resourceAttrs, log)
				if err != nil {
					logger.Debug("Failed to convert to CloudWatch Log", zap.Error(err))
					dropped++
				} else {
					out = append(out, event)
				}
			}
		}
	}
	return out, dropped
}

type cwLogBody struct {
	Name                   string                 `json:"name,omitempty"`
	Body                   interface{}            `json:"body,omitempty"`
	SeverityNumber         int32                  `json:"severity_number,omitempty"`
	SeverityText           string                 `json:"severity_text,omitempty"`
	DroppedAttributesCount uint32                 `json:"dropped_attributes_count,omitempty"`
	Flags                  uint32                 `json:"flags,omitempty"`
	TraceID                string                 `json:"trace_id,omitempty"`
	SpanID                 string                 `json:"span_id,omitempty"`
	Attributes             map[string]interface{} `json:"attributes,omitempty"`
	Resource               map[string]interface{} `json:"resource,omitempty"`
}

func logToCWLog(resourceAttrs map[string]interface{}, log pdata.LogRecord) (*cloudwatchlogs.InputLogEvent, error) {
	// TODO(jbd): Benchmark and improve the allocations.
	// Evaluate go.elastic.co/fastjson as a replacement for encoding/json.
	body := cwLogBody{
		Name:                   log.Name(),
		Body:                   attrValue(log.Body()),
		SeverityNumber:         int32(log.SeverityNumber()),
		SeverityText:           log.SeverityText(),
		DroppedAttributesCount: log.DroppedAttributesCount(),
		Flags:                  log.Flags(),
	}
	if traceID := log.TraceID(); !traceID.IsEmpty() {
		body.TraceID = traceID.HexString()
	}
	if spanID := log.SpanID(); !spanID.IsEmpty() {
		body.SpanID = spanID.HexString()
	}
	body.Attributes = attrsValue(log.Attributes())
	body.Resource = resourceAttrs

	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	return &cloudwatchlogs.InputLogEvent{
		Timestamp: aws.Int64(int64(log.Timestamp()) / int64(time.Millisecond)), // in milliseconds
		Message:   aws.String(string(bodyJSON)),
	}, nil
}

func attrsValue(attrs pdata.AttributeMap) map[string]interface{} {
	if attrs.Len() == 0 {
		return nil
	}
	out := make(map[string]interface{}, attrs.Len())
	attrs.Range(func(k string, v pdata.AttributeValue) bool {
		out[k] = attrValue(v)
		return true
	})
	return out
}

func attrValue(value pdata.AttributeValue) interface{} {
	switch value.Type() {
	case pdata.AttributeValueTypeInt:
		return value.IntVal()
	case pdata.AttributeValueTypeBool:
		return value.BoolVal()
	case pdata.AttributeValueTypeDouble:
		return value.DoubleVal()
	case pdata.AttributeValueTypeString:
		return value.StringVal()
	case pdata.AttributeValueTypeMap:
		values := map[string]interface{}{}
		value.MapVal().Range(func(k string, v pdata.AttributeValue) bool {
			values[k] = attrValue(v)
			return true
		})
		return values
	case pdata.AttributeValueTypeArray:
		arrayVal := value.ArrayVal()
		values := make([]interface{}, arrayVal.Len())
		for i := 0; i < arrayVal.Len(); i++ {
			values[i] = attrValue(arrayVal.At(i))
		}
		return values
	case pdata.AttributeValueTypeNull:
		return nil
	default:
		return nil
	}
}
<commit_msg>Fix AWS CloudWatch sequence token handling for new/empty log streams (#4717)<commit_after>// Copyright 2020, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package awscloudwatchlogsexporter

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/model/pdata"
	"go.uber.org/zap"
)

type exporter struct {
	config *Config
	logger *zap.Logger

	startOnce sync.Once
	client    *cloudwatchlogs.CloudWatchLogs // available after startOnce

	seqTokenMu sync.Mutex
	seqToken   string
}

func (e *exporter) Start(ctx context.Context, host component.Host) error {
	var startErr error
	e.startOnce.Do(func() {
		awsConfig := &aws.Config{}
		if e.config.Region != "" {
			awsConfig.Region = aws.String(e.config.Region)
		}
		if e.config.Endpoint != "" {
			awsConfig.Endpoint = aws.String(e.config.Endpoint)
		}
		awsConfig.MaxRetries = aws.Int(1) // retry will be handled by the collector queue
		sess, err := session.NewSession(awsConfig)
		if err != nil {
			startErr = err
			return
		}
		e.client = cloudwatchlogs.New(sess)

		e.logger.Debug("Retrieving CloudWatch sequence token")
		out, err := e.client.DescribeLogStreams(&cloudwatchlogs.DescribeLogStreamsInput{
			LogGroupName:        aws.String(e.config.LogGroupName),
			LogStreamNamePrefix: aws.String(e.config.LogStreamName),
		})
		if err != nil {
			startErr = err
			return
		}
		if len(out.LogStreams) == 0 {
			startErr = errors.New("cannot find log group and stream")
			return
		}
		stream := out.LogStreams[0]
		if stream.UploadSequenceToken == nil {
			e.logger.Debug("CloudWatch sequence token is nil, will assume empty")
			return
		}
		e.seqToken = *stream.UploadSequenceToken
	})
	return startErr
}

func (e *exporter) Shutdown(ctx context.Context) error {
	// TODO(jbd): Signal shutdown to flush the logs.
	return nil
}

func (e *exporter) PushLogs(ctx context.Context, ld pdata.Logs) (err error) {
	// TODO(jbd): Relax this once CW Logs support ingest
	// without sequence tokens.
	e.seqTokenMu.Lock()
	defer e.seqTokenMu.Unlock()

	logEvents, _ := logsToCWLogs(e.logger, ld)
	if len(logEvents) == 0 {
		return nil
	}

	e.logger.Debug("Putting log events", zap.Int("num_of_events", len(logEvents)))
	input := &cloudwatchlogs.PutLogEventsInput{
		LogGroupName:  aws.String(e.config.LogGroupName),
		LogStreamName: aws.String(e.config.LogStreamName),
		LogEvents:     logEvents,
	}
	if e.seqToken != "" {
		input.SequenceToken = aws.String(e.seqToken)
	} else {
		e.logger.Debug("Putting log events without a sequence token")
	}

	out, err := e.client.PutLogEvents(input)
	if err != nil {
		return err
	}
	if info := out.RejectedLogEventsInfo; info != nil {
		return fmt.Errorf("log event rejected: %s", info.String())
	}
	e.logger.Debug("Log events are successfully put")

	e.seqToken = *out.NextSequenceToken
	return nil
}

func logsToCWLogs(logger *zap.Logger, ld pdata.Logs) ([]*cloudwatchlogs.InputLogEvent, int) {
	n := ld.ResourceLogs().Len()
	if n == 0 {
		return []*cloudwatchlogs.InputLogEvent{}, 0
	}

	var dropped int
	out := make([]*cloudwatchlogs.InputLogEvent, 0) // TODO(jbd): set a better capacity

	rls := ld.ResourceLogs()
	for i := 0; i < rls.Len(); i++ {
		rl := rls.At(i)
		resourceAttrs := attrsValue(rl.Resource().Attributes())

		ills := rl.InstrumentationLibraryLogs()
		for j := 0; j < ills.Len(); j++ {
			ils := ills.At(j)
			logs := ils.Logs()
			for k := 0; k < logs.Len(); k++ {
				log := logs.At(k)
				event, err := logToCWLog(resourceAttrs, log)
				if err != nil {
					logger.Debug("Failed to convert to CloudWatch Log", zap.Error(err))
					dropped++
				} else {
					out = append(out, event)
				}
			}
		}
	}
	return out, dropped
}

type cwLogBody struct {
	Name                   string                 `json:"name,omitempty"`
	Body                   interface{}            `json:"body,omitempty"`
	SeverityNumber         int32                  `json:"severity_number,omitempty"`
	SeverityText           string                 `json:"severity_text,omitempty"`
	DroppedAttributesCount uint32                 `json:"dropped_attributes_count,omitempty"`
	Flags                  uint32                 `json:"flags,omitempty"`
	TraceID                string                 `json:"trace_id,omitempty"`
	SpanID                 string                 `json:"span_id,omitempty"`
	Attributes             map[string]interface{} `json:"attributes,omitempty"`
	Resource               map[string]interface{} `json:"resource,omitempty"`
}

func logToCWLog(resourceAttrs map[string]interface{}, log pdata.LogRecord) (*cloudwatchlogs.InputLogEvent, error) {
	// TODO(jbd): Benchmark and improve the allocations.
	// Evaluate go.elastic.co/fastjson as a replacement for encoding/json.
	body := cwLogBody{
		Name:                   log.Name(),
		Body:                   attrValue(log.Body()),
		SeverityNumber:         int32(log.SeverityNumber()),
		SeverityText:           log.SeverityText(),
		DroppedAttributesCount: log.DroppedAttributesCount(),
		Flags:                  log.Flags(),
	}
	if traceID := log.TraceID(); !traceID.IsEmpty() {
		body.TraceID = traceID.HexString()
	}
	if spanID := log.SpanID(); !spanID.IsEmpty() {
		body.SpanID = spanID.HexString()
	}
	body.Attributes = attrsValue(log.Attributes())
	body.Resource = resourceAttrs

	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	return &cloudwatchlogs.InputLogEvent{
		Timestamp: aws.Int64(int64(log.Timestamp()) / int64(time.Millisecond)), // in milliseconds
		Message:   aws.String(string(bodyJSON)),
	}, nil
}

func attrsValue(attrs pdata.AttributeMap) map[string]interface{} {
	if attrs.Len() == 0 {
		return nil
	}
	out := make(map[string]interface{}, attrs.Len())
	attrs.Range(func(k string, v pdata.AttributeValue) bool {
		out[k] = attrValue(v)
		return true
	})
	return out
}

func attrValue(value pdata.AttributeValue) interface{} {
	switch value.Type() {
	case pdata.AttributeValueTypeInt:
		return value.IntVal()
	case pdata.AttributeValueTypeBool:
		return value.BoolVal()
	case pdata.AttributeValueTypeDouble:
		return value.DoubleVal()
	case pdata.AttributeValueTypeString:
		return value.StringVal()
	case pdata.AttributeValueTypeMap:
		values := map[string]interface{}{}
		value.MapVal().Range(func(k string, v pdata.AttributeValue) bool {
			values[k] = attrValue(v)
			return true
		})
		return values
	case pdata.AttributeValueTypeArray:
		arrayVal := value.ArrayVal()
		values := make([]interface{}, arrayVal.Len())
		for i := 0; i < arrayVal.Len(); i++ {
			values[i] = attrValue(arrayVal.At(i))
		}
		return values
	case pdata.AttributeValueTypeNull:
		return nil
	default:
		return nil
	}
}
<|endoftext|>


<commit_before>package oci

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/containernetworking/cni/pkg/ns"
	"github.com/docker/docker/pkg/signal"
	specs "github.com/opencontainers/runtime-spec/specs-go"
	"k8s.io/apimachinery/pkg/fields"
	pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)

const (
	defaultStopSignal = "TERM"
)

// Container represents a runtime container.
type Container struct {
	id          string
	name        string
	logPath     string
	labels      fields.Set
	annotations fields.Set
	image       *pb.ImageSpec
	sandbox     string
	netns       ns.NetNS
	terminal    bool
	stdin       bool
	stdinOnce   bool
	privileged  bool
	trusted     bool
	state       *ContainerState
	metadata    *pb.ContainerMetadata
	opLock      sync.Mutex
	// this is the /var/run/storage/... directory, erased on reboot
	bundlePath string
	// this is the /var/lib/storage/... directory
	dir        string
	stopSignal string
}

// ContainerState represents the status of a container.
type ContainerState struct {
	specs.State
	Created   time.Time `json:"created"`
	Started   time.Time `json:"started,omitempty"`
	Finished  time.Time `json:"finished,omitempty"`
	ExitCode  int32     `json:"exitCode,omitempty"`
	OOMKilled bool      `json:"oomKilled,omitempty"`
	Error     string    `json:"error,omitempty"`
}

// NewContainer creates a container object.
func NewContainer(id string, name string, bundlePath string, logPath string, netns ns.NetNS, labels map[string]string, annotations map[string]string, image *pb.ImageSpec, metadata *pb.ContainerMetadata, sandbox string, terminal bool, stdin bool, stdinOnce bool, privileged bool, trusted bool, dir string, created time.Time, stopSignal string) (*Container, error) {
	state := &ContainerState{}
	state.Created = created
	c := &Container{
		id:          id,
		name:        name,
		bundlePath:  bundlePath,
		logPath:     logPath,
		labels:      labels,
		sandbox:     sandbox,
		netns:       netns,
		terminal:    terminal,
		stdin:       stdin,
		stdinOnce:   stdinOnce,
		privileged:  privileged,
		trusted:     trusted,
		metadata:    metadata,
		annotations: annotations,
		image:       image,
		dir:         dir,
		state:       state,
		stopSignal:  stopSignal,
	}
	return c, nil
}

// GetStopSignal returns the container's own stop signal configured from the
// image configuration or the default one.
func (c *Container) GetStopSignal() string {
	if c.stopSignal == "" {
		return defaultStopSignal
	}
	cleanSignal := strings.TrimPrefix(strings.ToUpper(c.stopSignal), "SIG")
	_, ok := signal.SignalMap[cleanSignal]
	if !ok {
		return defaultStopSignal
	}
	return cleanSignal
}

// FromDisk restores container's state from disk
func (c *Container) FromDisk() error {
	jsonSource, err := os.Open(c.StatePath())
	if err != nil {
		return err
	}
	defer jsonSource.Close()

	dec := json.NewDecoder(jsonSource)
	return dec.Decode(c.state)
}

// StatePath returns the containers state.json path
func (c *Container) StatePath() string {
	return filepath.Join(c.dir, "state.json")
}

// CreatedAt returns the container creation time
func (c *Container) CreatedAt() time.Time {
	return c.state.Created
}

// Name returns the name of the container.
func (c *Container) Name() string {
	return c.name
}

// ID returns the id of the container.
func (c *Container) ID() string {
	return c.id
}

// BundlePath returns the bundlePath of the container.
func (c *Container) BundlePath() string {
	return c.bundlePath
}

// LogPath returns the log path of the container.
func (c *Container) LogPath() string {
	return c.logPath
}

// Labels returns the labels of the container.
func (c *Container) Labels() map[string]string {
	return c.labels
}

// Annotations returns the annotations of the container.
func (c *Container) Annotations() map[string]string {
	return c.annotations
}

// Image returns the image of the container.
func (c *Container) Image() *pb.ImageSpec {
	return c.image
}

// Sandbox returns the sandbox name of the container.
func (c *Container) Sandbox() string {
	return c.sandbox
}

// NetNsPath returns the path to the network namespace of the container.
func (c *Container) NetNsPath() (string, error) {
	if c.state == nil {
		return "", fmt.Errorf("container state is not populated")
	}

	if c.netns == nil {
		return fmt.Sprintf("/proc/%d/ns/net", c.state.Pid), nil
	}

	return c.netns.Path(), nil
}

// Metadata returns the metadata of the container.
func (c *Container) Metadata() *pb.ContainerMetadata {
	return c.metadata
}
<commit_msg>Change opLock mutex for containers to sync.Locker<commit_after>package oci

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/containernetworking/cni/pkg/ns"
	"github.com/docker/docker/pkg/signal"
	specs "github.com/opencontainers/runtime-spec/specs-go"
	"k8s.io/apimachinery/pkg/fields"
	pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)

const (
	defaultStopSignal = "TERM"
)

// Container represents a runtime container.
type Container struct {
	id          string
	name        string
	logPath     string
	labels      fields.Set
	annotations fields.Set
	image       *pb.ImageSpec
	sandbox     string
	netns       ns.NetNS
	terminal    bool
	stdin       bool
	stdinOnce   bool
	privileged  bool
	trusted     bool
	state       *ContainerState
	metadata    *pb.ContainerMetadata
	opLock      sync.Locker
	// this is the /var/run/storage/... directory, erased on reboot
	bundlePath string
	// this is the /var/lib/storage/... directory
	dir        string
	stopSignal string
}

// ContainerState represents the status of a container.
type ContainerState struct {
	specs.State
	Created   time.Time `json:"created"`
	Started   time.Time `json:"started,omitempty"`
	Finished  time.Time `json:"finished,omitempty"`
	ExitCode  int32     `json:"exitCode,omitempty"`
	OOMKilled bool      `json:"oomKilled,omitempty"`
	Error     string    `json:"error,omitempty"`
}

// NewContainer creates a container object.
func NewContainer(id string, name string, bundlePath string, logPath string, netns ns.NetNS, labels map[string]string, annotations map[string]string, image *pb.ImageSpec, metadata *pb.ContainerMetadata, sandbox string, terminal bool, stdin bool, stdinOnce bool, privileged bool, trusted bool, dir string, created time.Time, stopSignal string) (*Container, error) {
	state := &ContainerState{}
	state.Created = created
	c := &Container{
		id:          id,
		name:        name,
		bundlePath:  bundlePath,
		logPath:     logPath,
		labels:      labels,
		sandbox:     sandbox,
		netns:       netns,
		terminal:    terminal,
		stdin:       stdin,
		stdinOnce:   stdinOnce,
		privileged:  privileged,
		trusted:     trusted,
		metadata:    metadata,
		annotations: annotations,
		image:       image,
		dir:         dir,
		state:       state,
		stopSignal:  stopSignal,
		opLock:      new(sync.Mutex),
	}
	return c, nil
}

// GetStopSignal returns the container's own stop signal configured from the
// image configuration or the default one.
func (c *Container) GetStopSignal() string {
	if c.stopSignal == "" {
		return defaultStopSignal
	}
	cleanSignal := strings.TrimPrefix(strings.ToUpper(c.stopSignal), "SIG")
	_, ok := signal.SignalMap[cleanSignal]
	if !ok {
		return defaultStopSignal
	}
	return cleanSignal
}

// FromDisk restores container's state from disk
func (c *Container) FromDisk() error {
	jsonSource, err := os.Open(c.StatePath())
	if err != nil {
		return err
	}
	defer jsonSource.Close()

	dec := json.NewDecoder(jsonSource)
	return dec.Decode(c.state)
}

// StatePath returns the containers state.json path
func (c *Container) StatePath() string {
	return filepath.Join(c.dir, "state.json")
}

// CreatedAt returns the container creation time
func (c *Container) CreatedAt() time.Time {
	return c.state.Created
}

// Name returns the name of the container.
func (c *Container) Name() string {
	return c.name
}

// ID returns the id of the container.
func (c *Container) ID() string {
	return c.id
}

// BundlePath returns the bundlePath of the container.
func (c *Container) BundlePath() string {
	return c.bundlePath
}

// LogPath returns the log path of the container.
func (c *Container) LogPath() string {
	return c.logPath
}

// Labels returns the labels of the container.
func (c *Container) Labels() map[string]string {
	return c.labels
}

// Annotations returns the annotations of the container.
func (c *Container) Annotations() map[string]string {
	return c.annotations
}

// Image returns the image of the container.
func (c *Container) Image() *pb.ImageSpec {
	return c.image
}

// Sandbox returns the sandbox name of the container.
func (c *Container) Sandbox() string {
	return c.sandbox
}

// NetNsPath returns the path to the network namespace of the container.
func (c *Container) NetNsPath() (string, error) {
	if c.state == nil {
		return "", fmt.Errorf("container state is not populated")
	}

	if c.netns == nil {
		return fmt.Sprintf("/proc/%d/ns/net", c.state.Pid), nil
	}

	return c.netns.Path(), nil
}

// Metadata returns the metadata of the container.
func (c *Container) Metadata() *pb.ContainerMetadata {
	return c.metadata
}
<|endoftext|>


<commit_before>package appsody

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
)

// Builder of images from function source using appsody.
type Builder struct {
	// Verbose logging flag.
	Verbose bool

	registry  string // registry domain (docker.io, quay.io, etc.)
	namespace string // namespace (username, org name, etc.)
}

// NewBuilder creates an instance of an appsody-backed image builder.
func NewBuilder(registry, namespace string) *Builder {
	return &Builder{
		registry:  registry,
		namespace: namespace}
}

// Build an image from the funciton source at path.
func (n *Builder) Build(name, path string) (image string, err error) {
	// Check for the appsody binary explicitly so that we can return
	// an extra-friendly error message.
	_, err = exec.LookPath("appsody")
	if err != nil {
		err = errors.New("please install 'appsody'")
		return
	}

	// Fully qualified image name.  Ex quay.io/user/www-example-com:20200102T1234
	// timestamp := time.Now().Format("20060102T150405")
	// image = fmt.Sprintf("%v/%v/%v:%v", n.registry, n.namespace, name, timestamp)

	// Simple image name, which uses :latest
	image = fmt.Sprintf("%v/%v/%v", n.registry, n.namespace, name)

	// set up the command, specifying a sanitized project name and connecting
	// standard output and error.
	cmd := exec.Command("appsody", "build", "-t", image)
	cmd.Dir = path

	// If verbose logging is enabled, echo appsody's chatty stdout.
	if n.Verbose {
		fmt.Println(cmd)
		cmd.Stdout = os.Stdout
	}

	// Capture stderr for echoing on failure.
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	// Run the command, echoing captured stderr as well ass the cmd internal error.
	err = cmd.Run()
	if err != nil {
		// TODO: sanitize stderr from appsody, or submit a PR to remove duplicates etc.
		err = errors.New(fmt.Sprintf("%v. %v", string(stderr.Bytes()), err.Error()))
	}
	return
}
<commit_msg>builder: remove superfluous appsody deploy yaml after build<commit_after>package appsody

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// Builder of images from function source using appsody.
type Builder struct {
	// Verbose logging flag.
	Verbose bool

	registry  string // registry domain (docker.io, quay.io, etc.)
	namespace string // namespace (username, org name, etc.)
}

// NewBuilder creates an instance of an appsody-backed image builder.
func NewBuilder(registry, namespace string) *Builder {
	return &Builder{
		registry:  registry,
		namespace: namespace}
}

// Build an image from the function source at path.
func (n *Builder) Build(name, path string) (image string, err error) {
	// Check for the appsody binary explicitly so that we can return
	// an extra-friendly error message.
	_, err = exec.LookPath("appsody")
	if err != nil {
		err = errors.New("please install 'appsody'")
		return
	}

	// Fully qualified image name.  Ex quay.io/user/www-example-com:20200102T1234
	// timestamp := time.Now().Format("20060102T150405")
	// image = fmt.Sprintf("%v/%v/%v:%v", n.registry, n.namespace, name, timestamp)

	// Simple image name, which uses :latest
	image = fmt.Sprintf("%v/%v/%v", n.registry, n.namespace, name)

	// set up the command, specifying a sanitized project name and connecting
	// standard output and error.
	cmd := exec.Command("appsody", "build", "-t", image)
	cmd.Dir = path

	// If verbose logging is enabled, echo appsody's chatty stdout.
	if n.Verbose {
		fmt.Println(cmd)
		cmd.Stdout = os.Stdout
	}

	// Capture stderr for echoing on failure.
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	// Run the command, echoing captured stderr as well ass the cmd internal error.
	err = cmd.Run()
	if err != nil {
		// TODO: sanitize stderr from appsody, or submit a PR to remove duplicates etc.
		err = errors.New(fmt.Sprintf("%v. %v", string(stderr.Bytes()), err.Error()))
	}

	// remove the superfluous app-deploy.yaml
	cfg := filepath.Join(path, "app-deploy.yaml")
	if _, err = os.Stat(cfg); err == nil {
		err = os.Remove(cfg)
		if err != nil {
			fmt.Fprintf(os.Stderr, fmt.Sprintf("unable to remove superfluous appsody config: %v\n", err))
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"UserManager"
	"SessionManager"
	"regexp"
	"Templates"
	"time"
)

const (
	// Routes
	rootURL = "/"
	docURL = rootURL + "doc/"
	basicAuthURL = rootURL + "download/"
	funcURL = rootURL + "ops/"

	// Paths
	pivatePath = "res/html/"
	publicPath = pivatePath + "/public/"

	// URLs
	mainPageURL = rootURL + "index.html"
	settingsPageURL = rootURL + "settings.html"
	loginPageURL = rootURL + "public/login.html"

	// MISC
	debugging = false; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	//Login,Logout
	requestMultiplexer.HandleFunc(funcURL + "login", authHandler)

	//Settings (Change Password)
	requestMultiplexer.HandleFunc(funcURL + "settings", settingsHandler)

	//Index Functions
	//DeleteData
	requestMultiplexer.HandleFunc(funcURL + "delete", Templates.DeleteDataHandler)

	//DownloadData
	requestMultiplexer.HandleFunc(funcURL + "download", Templates.DownloadDataHandler)

	//UploadData
	requestMultiplexer.HandleFunc(funcURL + "upload", Templates.UploadDataDataHandler)

	//NewFolder
	requestMultiplexer.HandleFunc(funcURL + "newFolder", Templates.NewFolderHandler)

	//Basic Auth
	requestMultiplexer.HandleFunc(basicAuthURL, basicAuthHandler(Templates.DownloadBasicAuthDataHandler))

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	cookie, err := r.Cookie("Session")
	if err != nil {
		Utils.LogDebug(err.Error())
		cookie := http.Cookie{Name: "Session", Value: "empty", Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, loginPageURL, 302)
		return
	} else {
		// Public Folder ?
		publicFolderRegex, _ := regexp.Compile("^public")

		if (SessionManager.ValidateSession(cookie.Value) || publicFolderRegex.MatchString(r.URL.EscapedPath()[1:]) || debugging) {
			rootHandler(w, r)
			return
		} else {
			Utils.LogDebug("Access denied for " + r.URL.EscapedPath() + " by Session Check")
			http.Redirect(w, r, loginPageURL, 302)
			return
		}
	}

}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string

	if r.URL.EscapedPath()[1:] == "" {
		url = mainPageURL
	} else {
		url = r.URL.EscapedPath()[1:]
	}

	path, err := filepath.Abs(pivatePath + url)
	Utils.HandlePrint(err)

	if (url[len(url) - 4:] == "html") {
		// Split string at / , switch case files

		switch url {
		case "/index.html":
			Templates.IndexHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		case "/settings.html":
			Templates.SettingHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		default:
			http.ServeFile(w, r, path)
			Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
		}

	} else {
		http.ServeFile(w, r, path)
		Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
	}

}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		Utils.LogDebug("Intent=Login")
		email := r.FormValue("email")
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {

			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)

		} else {
			http.Redirect(w, r, loginPageURL + "?status=failed", 302)
		}
	} else if (intent == "register") {
		Utils.LogDebug("Intent=Register")
		name := r.FormValue("name")
		email := r.FormValue("email")
		password := r.FormValue("password")
		password2 := r.FormValue("password2")

		if (password != password2) {
			//Passwords not equal
			Utils.LogDebug("Passwörter stimmen nicht überein. Registrierung fehlgeschlagen.")
			http.Redirect(w, r, loginPageURL + "?status=passwordsNotEqual", 302)
			return
		}

		registerOK := UserManager.RegisterUser(name, email, password)
		if (registerOK) {
			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)
			Utils.LogDebug("Registrierung erfolgreich.")
		} else {
			http.Redirect(w, r, loginPageURL + "?status=userAlreadyExists", 302)
			Utils.LogDebug("Benutzer mit angegebener E-Mail-Adresse existiert bereits. Registrierung fehlgeschlagen.")
		}
	} else if (r.URL.Query().Get("intent") == "logout") {
		Utils.LogDebug("Intent=Logout")
		cookie, err := r.Cookie("Session")
		Utils.HandlePrint(err)
		session, present := SessionManager.GetSessionRecord(cookie.Value)
		if present {
			err = SessionManager.InvalidateSession(session.Session)
		}
		if (err != nil) {
			http.Redirect(w, r, loginPageURL + "?status=error", 302)
			return
		}
		http.Redirect(w, r, loginPageURL + "?status=logout", 302)
		return
	} else {
		Utils.LogDebug("Intent=BadRequest")
		http.Redirect(w, r, loginPageURL + "?status=badrequest", 302)
		return
	}
}

func settingsHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	passwordOld := r.FormValue("passwordOld")
	passwordNew := r.FormValue("passwordNew")
	passwordNew2 := r.FormValue("passwordNew2")

	if (passwordNew != passwordNew2) {
		//Passwords not equal
		Utils.LogDebug("Neue Passwörter stimmen nicht überein. Änderung fehlgeschlagen.")
		http.Redirect(w, r, settingsPageURL + "?status=passwordsNotEqual", 302)
		return
	}

	cookie, err := r.Cookie("Session")
	Utils.HandlePrint(err)
	session, present := SessionManager.GetSessionRecord(cookie.Value)
	if(present) {
		email := session.Email

		Utils.LogDebug("EMail: " + email)

		if (UserManager.VerifyUser(email, passwordOld)) {
			UserManager.ChangePassword(email, passwordNew)
			http.Redirect(w, r, settingsPageURL, 302)
			Utils.LogDebug("Kennwort erfolgreich geändert.")
		} else {
			http.Redirect(w, r, settingsPageURL + "?status=oldPasswordNotValid", 302)
			Utils.LogDebug("Altes Kennwort nicht korrekt. Kennwortänderung fehlgeschlagen.")
		}
	}

}

//basicAuth - Checks submitted user credentials and grants access to handler
func basicAuthHandler(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		email, pass, ok := r.BasicAuth()

		if ok && UserManager.VerifyUser(email, pass) {
			handler(w, r)
		} else {
			w.Header().Set("WWW-Authenticate", `Basic realm="Fileserver: Bitte mit E-Mail-Adresse und Kennwort anmelden, um auf Dateien zuzugreifen."`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
		}

	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.EscapedPath()[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.EscapedPath()[1:] == "doc/" || r.URL.EscapedPath()[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 302)
	} else {
		http.ServeFile(w, r, path)
	}
}

<commit_msg>SettingsTemplate engine fix<commit_after>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"UserManager"
	"SessionManager"
	"regexp"
	"Templates"
	"time"
)

const (
	// Routes
	rootURL = "/"
	docURL = rootURL + "doc/"
	basicAuthURL = rootURL + "download/"
	funcURL = rootURL + "ops/"

	// Paths
	pivatePath = "res/html/"
	publicPath = pivatePath + "/public/"

	// URLs
	mainPageURL = rootURL + "index.html"
	settingsPageURL = rootURL + "settings.html"
	loginPageURL = rootURL + "public/login.html"

	// MISC
	debugging = false; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	//Login,Logout
	requestMultiplexer.HandleFunc(funcURL + "login", authHandler)

	//Settings (Change Password)
	requestMultiplexer.HandleFunc(funcURL + "settings", settingsHandler)

	//Index Functions
	//DeleteData
	requestMultiplexer.HandleFunc(funcURL + "delete", Templates.DeleteDataHandler)

	//DownloadData
	requestMultiplexer.HandleFunc(funcURL + "download", Templates.DownloadDataHandler)

	//UploadData
	requestMultiplexer.HandleFunc(funcURL + "upload", Templates.UploadDataDataHandler)

	//NewFolder
	requestMultiplexer.HandleFunc(funcURL + "newFolder", Templates.NewFolderHandler)

	//Basic Auth
	requestMultiplexer.HandleFunc(basicAuthURL, basicAuthHandler(Templates.DownloadBasicAuthDataHandler))

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	cookie, err := r.Cookie("Session")
	if err != nil {
		Utils.LogDebug(err.Error())
		cookie := http.Cookie{Name: "Session", Value: "empty", Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
		http.SetCookie(w, &cookie)
		http.Redirect(w, r, loginPageURL, 302)
		return
	} else {
		// Public Folder ?
		publicFolderRegex, _ := regexp.Compile("^public")

		if (SessionManager.ValidateSession(cookie.Value) || publicFolderRegex.MatchString(r.URL.EscapedPath()[1:]) || debugging) {
			rootHandler(w, r)
			return
		} else {
			Utils.LogDebug("Access denied for " + r.URL.EscapedPath() + " by Session Check")
			http.Redirect(w, r, loginPageURL, 302)
			return
		}
	}

}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string

	if r.URL.EscapedPath()[1:] == "" {
		url = mainPageURL
	} else {
		url = r.URL.EscapedPath()[1:]
	}

	path, err := filepath.Abs(pivatePath + url)
	Utils.HandlePrint(err)

	if (url[len(url) - 4:] == "html") {
		// Split string at / , switch case files

		switch url {
		case "/index.html":
			Templates.IndexHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		case "settings.html":
			Templates.SettingHandler(w, r, path)
			Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		default:
			http.ServeFile(w, r, path)
			Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
		}

	} else {
		http.ServeFile(w, r, path)
		Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
	}

}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		Utils.LogDebug("Intent=Login")
		email := r.FormValue("email")
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {

			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)

		} else {
			http.Redirect(w, r, loginPageURL + "?status=failed", 302)
		}
	} else if (intent == "register") {
		Utils.LogDebug("Intent=Register")
		name := r.FormValue("name")
		email := r.FormValue("email")
		password := r.FormValue("password")
		password2 := r.FormValue("password2")

		if (password != password2) {
			//Passwords not equal
			Utils.LogDebug("Passwörter stimmen nicht überein. Registrierung fehlgeschlagen.")
			http.Redirect(w, r, loginPageURL + "?status=passwordsNotEqual", 302)
			return
		}

		registerOK := UserManager.RegisterUser(name, email, password)
		if (registerOK) {
			session := Utils.RandString(128)
			SessionManager.NewSession(SessionManager.SessionRecord{Email:email, Session: session, SessionLast:time.Now()})
			cookie := http.Cookie{Name: "Session", Value: session, Expires: time.Now().Add(365 * 24 * time.Hour), Path: "/"}
			http.SetCookie(w, &cookie)

			http.Redirect(w, r, mainPageURL, 302)
			Utils.LogDebug("Registrierung erfolgreich.")
		} else {
			http.Redirect(w, r, loginPageURL + "?status=userAlreadyExists", 302)
			Utils.LogDebug("Benutzer mit angegebener E-Mail-Adresse existiert bereits. Registrierung fehlgeschlagen.")
		}
	} else if (r.URL.Query().Get("intent") == "logout") {
		Utils.LogDebug("Intent=Logout")
		cookie, err := r.Cookie("Session")
		Utils.HandlePrint(err)
		session, present := SessionManager.GetSessionRecord(cookie.Value)
		if present {
			err = SessionManager.InvalidateSession(session.Session)
		}
		if (err != nil) {
			http.Redirect(w, r, loginPageURL + "?status=error", 302)
			return
		}
		http.Redirect(w, r, loginPageURL + "?status=logout", 302)
		return
	} else {
		Utils.LogDebug("Intent=BadRequest")
		http.Redirect(w, r, loginPageURL + "?status=badrequest", 302)
		return
	}
}

func settingsHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	passwordOld := r.FormValue("passwordOld")
	passwordNew := r.FormValue("passwordNew")
	passwordNew2 := r.FormValue("passwordNew2")

	if (passwordNew != passwordNew2) {
		//Passwords not equal
		Utils.LogDebug("Neue Passwörter stimmen nicht überein. Änderung fehlgeschlagen.")
		http.Redirect(w, r, settingsPageURL + "?status=passwordsNotEqual", 302)
		return
	}

	cookie, err := r.Cookie("Session")
	Utils.HandlePrint(err)
	session, present := SessionManager.GetSessionRecord(cookie.Value)
	if(present) {
		email := session.Email

		Utils.LogDebug("EMail: " + email)

		if (UserManager.VerifyUser(email, passwordOld)) {
			UserManager.ChangePassword(email, passwordNew)
			http.Redirect(w, r, settingsPageURL, 302)
			Utils.LogDebug("Kennwort erfolgreich geändert.")
		} else {
			http.Redirect(w, r, settingsPageURL + "?status=oldPasswordNotValid", 302)
			Utils.LogDebug("Altes Kennwort nicht korrekt. Kennwortänderung fehlgeschlagen.")
		}
	}

}

//basicAuth - Checks submitted user credentials and grants access to handler
func basicAuthHandler(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		email, pass, ok := r.BasicAuth()

		if ok && UserManager.VerifyUser(email, pass) {
			handler(w, r)
		} else {
			w.Header().Set("WWW-Authenticate", `Basic realm="Fileserver: Bitte mit E-Mail-Adresse und Kennwort anmelden, um auf Dateien zuzugreifen."`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
		}

	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.EscapedPath()[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.EscapedPath()[1:] == "doc/" || r.URL.EscapedPath()[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 302)
	} else {
		http.ServeFile(w, r, path)
	}
}

<|endoftext|>