<commit_before>// Copyright (c) 2016, Ben Morgan. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"os"
	"text/template"

	"github.com/cassava/repoctl/conf"
	"github.com/spf13/cobra"
)

func init() {
	MainCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
	Use:               "version",
	Short:             "show version and date information",
	Long:              "Show the official version number of repoctl, as well as the release date.",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil },
	Run: func(cmd *cobra.Command, args []string) {
		var progInfo = struct {
			Name      string
			Author    string
			Email     string
			Version   string
			Date      string
			Homepage  string
			Copyright string
			License   string
			Conf      *conf.Configuration
		}{
			Name:      "repoctl",
			Author:    "Ben Morgan",
			Email:     "neembi@gmail.com",
			Version:   "0.15",
			Date:      "2 June, 2016",
			Copyright: "2016",
			Homepage:  "https://github.com/cassava/repoctl",
			License:   "MIT",
			Conf:      Conf,
		}
		versionTmpl.Execute(os.Stdout, progInfo)
	},
}

var versionTmpl = template.Must(template.New("version").Funcs(template.FuncMap{
	"printt": printt,
}).Parse(`{{.Name}} version {{.Version}} ({{.Date}})
Copyright {{.Copyright}}, {{.Author}} <{{.Email}}>

You may find {{.Name}} on the Internet at
    {{.Homepage}}
Please report any bugs you may encounter.

The source code of {{.Name}} is licensed under the {{.License}} license.

{{if .Conf.Unconfigured}}Default{{else}}Current{{end}} configuration:
    repo        = {{printt .Conf.Repository}}
    add_params  = {{printt .Conf.AddParameters}}
    rm_params   = {{printt .Conf.RemoveParameters}}
    ignore_aur  = {{printt .Conf.IgnoreAUR}}
    backup      = {{printt .Conf.Backup}}
    backup_dir  = {{printt .Conf.BackupDir}}
    interactive = {{printt .Conf.Interactive}}
    columnate   = {{printt .Conf.Columnate}}
    color       = {{printt .Conf.Color}}
    quiet       = {{printt .Conf.Quiet}}
    debug       = {{printt .Conf.Debug}}
    pre_action  = {{printt .Conf.PreAction}}
    post_action = {{printt .Conf.PostAction}}
`))

// printt returns a TOML representation of the value.
//
// This function is used in printing TOML values in the template.
//
// NOTE: Copied from ../../conf/config.go
func printt(v interface{}) string {
	switch obj := v.(type) {
	case string:
		return fmt.Sprintf("%q", obj)
	case []string:
		if len(obj) == 0 {
			return "[]"
		}

		var buf bytes.Buffer
		buf.WriteRune('[')
		for _, k := range obj[:len(obj)-1] {
			buf.WriteString(fmt.Sprintf("%q", k))
			buf.WriteString(", ")
		}
		buf.WriteString(fmt.Sprintf("%q", obj[len(obj)-1]))
		buf.WriteRune(']')
		return buf.String()
	default: // floats, ints, bools
		return fmt.Sprintf("%v", obj)
	}
}
<commit_msg>Version bump to 0.16beta<commit_after>// Copyright (c) 2016, Ben Morgan. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"os"
	"text/template"

	"github.com/cassava/repoctl/conf"
	"github.com/spf13/cobra"
)

func init() {
	MainCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
	Use:               "version",
	Short:             "show version and date information",
	Long:              "Show the official version number of repoctl, as well as the release date.",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil },
	Run: func(cmd *cobra.Command, args []string) {
		var progInfo = struct {
			Name      string
			Author    string
			Email     string
			Version   string
			Date      string
			Homepage  string
			Copyright string
			License   string
			Conf      *conf.Configuration
		}{
			Name:      "repoctl",
			Author:    "Ben Morgan",
			Email:     "neembi@gmail.com",
			Version:   "0.16beta",
			Date:      "6 July, 2016",
			Copyright: "2016",
			Homepage:  "https://github.com/cassava/repoctl",
			License:   "MIT",
			Conf:      Conf,
		}
		versionTmpl.Execute(os.Stdout, progInfo)
	},
}

var versionTmpl = template.Must(template.New("version").Funcs(template.FuncMap{
	"printt": printt,
}).Parse(`{{.Name}} version {{.Version}} ({{.Date}})
Copyright {{.Copyright}}, {{.Author}} <{{.Email}}>

You may find {{.Name}} on the Internet at
    {{.Homepage}}
Please report any bugs you may encounter.

The source code of {{.Name}} is licensed under the {{.License}} license.

{{if .Conf.Unconfigured}}Default{{else}}Current{{end}} configuration:
    repo        = {{printt .Conf.Repository}}
    add_params  = {{printt .Conf.AddParameters}}
    rm_params   = {{printt .Conf.RemoveParameters}}
    ignore_aur  = {{printt .Conf.IgnoreAUR}}
    backup      = {{printt .Conf.Backup}}
    backup_dir  = {{printt .Conf.BackupDir}}
    interactive = {{printt .Conf.Interactive}}
    columnate   = {{printt .Conf.Columnate}}
    color       = {{printt .Conf.Color}}
    quiet       = {{printt .Conf.Quiet}}
    debug       = {{printt .Conf.Debug}}
    pre_action  = {{printt .Conf.PreAction}}
    post_action = {{printt .Conf.PostAction}}
`))

// printt returns a TOML representation of the value.
//
// This function is used in printing TOML values in the template.
//
// NOTE: Copied from ../../conf/config.go
func printt(v interface{}) string {
	switch obj := v.(type) {
	case string:
		return fmt.Sprintf("%q", obj)
	case []string:
		if len(obj) == 0 {
			return "[]"
		}

		var buf bytes.Buffer
		buf.WriteRune('[')
		for _, k := range obj[:len(obj)-1] {
			buf.WriteString(fmt.Sprintf("%q", k))
			buf.WriteString(", ")
		}
		buf.WriteString(fmt.Sprintf("%q", obj[len(obj)-1]))
		buf.WriteRune(']')
		return buf.String()
	default: // floats, ints, bools
		return fmt.Sprintf("%v", obj)
	}
}
<|endoftext|>


<commit_before>package rdfxml

import (
	"fmt"
	"regexp"
	"strings"
)

// This file contains functionality for validating an XML name.
// based on https://www.w3.org/TR/xml/.

func checkXMLName(candidate string) error {
	if !xmlNameRegexp.MatchString(candidate) {
		return fmt.Errorf("%q is not a valid XML Name", candidate)
	}
	return nil
}

func checkXMLNCName(candidate string) error {
	if err := checkXMLName(candidate); err != nil {
		return err
	}
	if strings.Contains(candidate, ":") {
		return fmt.Errorf("%q is not a valid NC name bacue it contains a : character", candidate)
	}
	return nil
}

// xmlNCName	  =   	xmlName - (xmlChar* ':' xmlChar*)	/* An XML Name, minus the ":" */

const (
	// See https://www.w3.org/TR/xml/ 2.3
	xmlNameStartChar = (`[\:A-Za-z` +
		`\xC0-\xD6` +
		`\xD8-\xF6` +
		`\xF8-\x{2FF}` +
		`\x{370}-\x{37D}` +
		`\x{37F}-\x{1FFF}` +
		`\x{200C}-\x{200D}` +
		`\x{2070}-\x{218F}` +
		`\x{2C00}-\x{2FEF}` +
		`\x{3001}-\x{D7FF}` +
		`\x{F900}-\x{FDCF}` +
		`\x{FDF0}-\x{FFFD}` +
		`\x{10000}-\x{EFFFF}]`)
	xmlNameChar = (`(?:` +
		xmlNameStartChar + `|` +
		`[\-\.0-9\xB7` + `\x{0300}-\x{036F}` + `\x{203F}-\x{2040}])`)
	xmlName     = xmlNameStartChar + xmlNameChar + `*`
	xmlNames    = xmlName + `(?:\x20` + xmlName + `)*`
	xmlNMToken  = `(?:` + xmlNameChar + `)+`
	xmlNMTokens = xmlNMToken + `(?:\x20` + xmlNMToken + `)*`
)

var xmlNameRegexp = regexp.MustCompile("^" + xmlName + "$")
<commit_msg>More instructive error message<commit_after>package rdfxml

import (
	"fmt"
	"regexp"
	"strings"
)

// This file contains functionality for validating an XML name.
// based on https://www.w3.org/TR/xml/.

func checkXMLName(candidate string) error {
	if !xmlNameRegexp.MatchString(candidate) {
		return fmt.Errorf("%q is not a valid XML Name per https://www.w3.org/TR/REC-xml/#sec-common-syn", candidate)
	}
	return nil
}

func checkXMLNCName(candidate string) error {
	if err := checkXMLName(candidate); err != nil {
		return err
	}
	if strings.Contains(candidate, ":") {
		return fmt.Errorf("%q is not a valid NC name bacue it contains a : character - see NCName in https://www.w3.org/TR/REC-xml/#sec-common-syn", candidate)
	}
	return nil
}

// xmlNCName	  =   	xmlName - (xmlChar* ':' xmlChar*)	/* An XML Name, minus the ":" */

const (
	// See https://www.w3.org/TR/xml/ 2.3
	xmlNameStartChar = (`[\:A-Za-z` +
		`\xC0-\xD6` +
		`\xD8-\xF6` +
		`\xF8-\x{2FF}` +
		`\x{370}-\x{37D}` +
		`\x{37F}-\x{1FFF}` +
		`\x{200C}-\x{200D}` +
		`\x{2070}-\x{218F}` +
		`\x{2C00}-\x{2FEF}` +
		`\x{3001}-\x{D7FF}` +
		`\x{F900}-\x{FDCF}` +
		`\x{FDF0}-\x{FFFD}` +
		`\x{10000}-\x{EFFFF}]`)
	xmlNameChar = (`(?:` +
		xmlNameStartChar + `|` +
		`[\-\.0-9\xB7` + `\x{0300}-\x{036F}` + `\x{203F}-\x{2040}])`)
	xmlName     = xmlNameStartChar + xmlNameChar + `*`
	xmlNames    = xmlName + `(?:\x20` + xmlName + `)*`
	xmlNMToken  = `(?:` + xmlNameChar + `)+`
	xmlNMTokens = xmlNMToken + `(?:\x20` + xmlNMToken + `)*`
)

var xmlNameRegexp = regexp.MustCompile("^" + xmlName + "$")
<|endoftext|>


<commit_before>package collector

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"path"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
)

type snapshotMetric struct {
	Type   prometheus.ValueType
	Desc   *prometheus.Desc
	Value  func(snapshotStats SnapshotStatDataResponse) float64
	Labels func(repositoryName string, snapshotStats SnapshotStatDataResponse) []string
}

type repositoryMetric struct {
	Type   prometheus.ValueType
	Desc   *prometheus.Desc
	Value  func(snapshotsStats SnapshotStatsResponse) float64
	Labels func(repositoryName string) []string
}

var (
	defaultSnapshotLabels      = []string{"repository", "state", "version"}
	defaultSnapshotLabelValues = func(repositoryName string, snapshotStats SnapshotStatDataResponse) []string {
		return []string{repositoryName, snapshotStats.State, snapshotStats.Version}
	}
	defaultSnapshotRepositoryLabels      = []string{"repository"}
	defaultSnapshotRepositoryLabelValues = func(repositoryName string) []string {
		return []string{repositoryName}
	}
)

// Snapshots information struct
type Snapshots struct {
	logger log.Logger
	client *http.Client
	url    *url.URL

	up                              prometheus.Gauge
	totalScrapes, jsonParseFailures prometheus.Counter

	snapshotMetrics   []*snapshotMetric
	repositoryMetrics []*repositoryMetric
}

// NewSnapshots defines Snapshots Prometheus metrics
func NewSnapshots(logger log.Logger, client *http.Client, url *url.URL) *Snapshots {
	return &Snapshots{
		logger: logger,
		client: client,
		url:    url,

		up: prometheus.NewGauge(prometheus.GaugeOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "up"),
			Help: "Was the last scrape of the ElasticSearch snapshots endpoint successful.",
		}),
		totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "total_scrapes"),
			Help: "Current total ElasticSearch snapshots scrapes.",
		}),
		jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "json_parse_failures"),
			Help: "Number of errors while parsing JSON.",
		}),
		snapshotMetrics: []*snapshotMetric{
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_number_of_indices"),
					"Number of indices in the last snapshot",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(len(snapshotStats.Indices))
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_start_time_timestamp"),
					"Last snapshot start timestamp",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.StartTimeInMillis / 1000)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_end_time_timestamp"),
					"Last snapshot end timestamp",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.EndTimeInMillis / 1000)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_number_of_failures"),
					"Last snapshot number of failures",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(len(snapshotStats.Failures))
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_total_shards"),
					"Last snapshot total shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Total)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_failed_shards"),
					"Last snapshot failed shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Failed)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_successful_shards"),
					"Last snapshot successful shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Successful)
				},
				Labels: defaultSnapshotLabelValues,
			},
		},
		repositoryMetrics: []*repositoryMetric{
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "number_of_snapshots"),
					"Number of snapshots in a repository",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					return float64(len(snapshotsStats.Snapshots))
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "oldest_snapshot_timestamp"),
					"Timestamp of the oldest snapshot",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					if len(snapshotsStats.Snapshots) == 0 {
						return 0
					}
					return float64(snapshotsStats.Snapshots[0].StartTimeInMillis / 1000)
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "latest_snapshot_seconds"),
					"Timestamp of the latest SUCCESS or PARTIAL snapshot",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					for i := len(snapshotsStats.Snapshots) - 1; i >= 0; i-- {
						var snap = snapshotsStats.Snapshots[i]
						if snap.State == "SUCCESS" || snap.State == "PARTIAL" {
							return float64(snap.StartTimeInMillis / 1000)
						}
					}
					return 0
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
		},
	}
}

// Describe add Snapshots metrics descriptions
func (s *Snapshots) Describe(ch chan<- *prometheus.Desc) {
	for _, metric := range s.snapshotMetrics {
		ch <- metric.Desc
	}
	ch <- s.up.Desc()
	ch <- s.totalScrapes.Desc()
	ch <- s.jsonParseFailures.Desc()
}

func (s *Snapshots) getAndParseURL(u *url.URL, data interface{}) error {
	res, err := s.client.Get(u.String())
	if err != nil {
		return fmt.Errorf("failed to get from %s://%s:%s%s: %s",
			u.Scheme, u.Hostname(), u.Port(), u.Path, err)
	}

	defer func() {
		err = res.Body.Close()
		if err != nil {
			_ = level.Warn(s.logger).Log(
				"msg", "failed to close http.Client",
				"err", err,
			)
		}
	}()

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
	}

	if err := json.NewDecoder(res.Body).Decode(data); err != nil {
		s.jsonParseFailures.Inc()
		return err
	}
	return nil
}

func (s *Snapshots) fetchAndDecodeSnapshotsStats() (map[string]SnapshotStatsResponse, error) {
	mssr := make(map[string]SnapshotStatsResponse)

	u := *s.url
	u.Path = path.Join(u.Path, "/_snapshot")
	var srr SnapshotRepositoriesResponse
	err := s.getAndParseURL(&u, &srr)
	if err != nil {
		return nil, err
	}
	for repository := range srr {
		u := *s.url
		u.Path = path.Join(u.Path, "/_snapshot", repository, "/_all")
		var ssr SnapshotStatsResponse
		err := s.getAndParseURL(&u, &ssr)
		if err != nil {
			continue
		}
		mssr[repository] = ssr
	}

	return mssr, nil
}

// Collect gets Snapshots metric values
func (s *Snapshots) Collect(ch chan<- prometheus.Metric) {
	s.totalScrapes.Inc()
	defer func() {
		ch <- s.up
		ch <- s.totalScrapes
		ch <- s.jsonParseFailures
	}()

	// indices
	snapshotsStatsResp, err := s.fetchAndDecodeSnapshotsStats()
	if err != nil {
		s.up.Set(0)
		_ = level.Warn(s.logger).Log(
			"msg", "failed to fetch and decode snapshot stats",
			"err", err,
		)
		return
	}
	s.up.Set(1)

	// Snapshots stats
	for repositoryName, snapshotStats := range snapshotsStatsResp {
		for _, metric := range s.repositoryMetrics {
			ch <- prometheus.MustNewConstMetric(
				metric.Desc,
				metric.Type,
				metric.Value(snapshotStats),
				metric.Labels(repositoryName)...,
			)
		}
		if len(snapshotStats.Snapshots) == 0 {
			continue
		}

		lastSnapshot := snapshotStats.Snapshots[len(snapshotStats.Snapshots)-1]
		for _, metric := range s.snapshotMetrics {
			ch <- prometheus.MustNewConstMetric(
				metric.Desc,
				metric.Type,
				metric.Value(lastSnapshot),
				metric.Labels(repositoryName, lastSnapshot)...,
			)
		}
	}
}
<commit_msg>fix: metric name<commit_after>package collector

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"path"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
)

type snapshotMetric struct {
	Type   prometheus.ValueType
	Desc   *prometheus.Desc
	Value  func(snapshotStats SnapshotStatDataResponse) float64
	Labels func(repositoryName string, snapshotStats SnapshotStatDataResponse) []string
}

type repositoryMetric struct {
	Type   prometheus.ValueType
	Desc   *prometheus.Desc
	Value  func(snapshotsStats SnapshotStatsResponse) float64
	Labels func(repositoryName string) []string
}

var (
	defaultSnapshotLabels      = []string{"repository", "state", "version"}
	defaultSnapshotLabelValues = func(repositoryName string, snapshotStats SnapshotStatDataResponse) []string {
		return []string{repositoryName, snapshotStats.State, snapshotStats.Version}
	}
	defaultSnapshotRepositoryLabels      = []string{"repository"}
	defaultSnapshotRepositoryLabelValues = func(repositoryName string) []string {
		return []string{repositoryName}
	}
)

// Snapshots information struct
type Snapshots struct {
	logger log.Logger
	client *http.Client
	url    *url.URL

	up                              prometheus.Gauge
	totalScrapes, jsonParseFailures prometheus.Counter

	snapshotMetrics   []*snapshotMetric
	repositoryMetrics []*repositoryMetric
}

// NewSnapshots defines Snapshots Prometheus metrics
func NewSnapshots(logger log.Logger, client *http.Client, url *url.URL) *Snapshots {
	return &Snapshots{
		logger: logger,
		client: client,
		url:    url,

		up: prometheus.NewGauge(prometheus.GaugeOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "up"),
			Help: "Was the last scrape of the ElasticSearch snapshots endpoint successful.",
		}),
		totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "total_scrapes"),
			Help: "Current total ElasticSearch snapshots scrapes.",
		}),
		jsonParseFailures: prometheus.NewCounter(prometheus.CounterOpts{
			Name: prometheus.BuildFQName(namespace, "snapshot_stats", "json_parse_failures"),
			Help: "Number of errors while parsing JSON.",
		}),
		snapshotMetrics: []*snapshotMetric{
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_number_of_indices"),
					"Number of indices in the last snapshot",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(len(snapshotStats.Indices))
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_start_time_timestamp"),
					"Last snapshot start timestamp",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.StartTimeInMillis / 1000)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_end_time_timestamp"),
					"Last snapshot end timestamp",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.EndTimeInMillis / 1000)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_number_of_failures"),
					"Last snapshot number of failures",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(len(snapshotStats.Failures))
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_total_shards"),
					"Last snapshot total shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Total)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_failed_shards"),
					"Last snapshot failed shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Failed)
				},
				Labels: defaultSnapshotLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "snapshot_successful_shards"),
					"Last snapshot successful shards",
					defaultSnapshotLabels, nil,
				),
				Value: func(snapshotStats SnapshotStatDataResponse) float64 {
					return float64(snapshotStats.Shards.Successful)
				},
				Labels: defaultSnapshotLabelValues,
			},
		},
		repositoryMetrics: []*repositoryMetric{
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "number_of_snapshots"),
					"Number of snapshots in a repository",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					return float64(len(snapshotsStats.Snapshots))
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "oldest_snapshot_timestamp"),
					"Timestamp of the oldest snapshot",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					if len(snapshotsStats.Snapshots) == 0 {
						return 0
					}
					return float64(snapshotsStats.Snapshots[0].StartTimeInMillis / 1000)
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
			{
				Type: prometheus.GaugeValue,
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, "snapshot_stats", "latest_snapshot_timestamp_seconds"),
					"Timestamp of the latest SUCCESS or PARTIAL snapshot",
					defaultSnapshotRepositoryLabels, nil,
				),
				Value: func(snapshotsStats SnapshotStatsResponse) float64 {
					for i := len(snapshotsStats.Snapshots) - 1; i >= 0; i-- {
						var snap = snapshotsStats.Snapshots[i]
						if snap.State == "SUCCESS" || snap.State == "PARTIAL" {
							return float64(snap.StartTimeInMillis / 1000)
						}
					}
					return 0
				},
				Labels: defaultSnapshotRepositoryLabelValues,
			},
		},
	}
}

// Describe add Snapshots metrics descriptions
func (s *Snapshots) Describe(ch chan<- *prometheus.Desc) {
	for _, metric := range s.snapshotMetrics {
		ch <- metric.Desc
	}
	ch <- s.up.Desc()
	ch <- s.totalScrapes.Desc()
	ch <- s.jsonParseFailures.Desc()
}

func (s *Snapshots) getAndParseURL(u *url.URL, data interface{}) error {
	res, err := s.client.Get(u.String())
	if err != nil {
		return fmt.Errorf("failed to get from %s://%s:%s%s: %s",
			u.Scheme, u.Hostname(), u.Port(), u.Path, err)
	}

	defer func() {
		err = res.Body.Close()
		if err != nil {
			_ = level.Warn(s.logger).Log(
				"msg", "failed to close http.Client",
				"err", err,
			)
		}
	}()

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP Request failed with code %d", res.StatusCode)
	}

	if err := json.NewDecoder(res.Body).Decode(data); err != nil {
		s.jsonParseFailures.Inc()
		return err
	}
	return nil
}

func (s *Snapshots) fetchAndDecodeSnapshotsStats() (map[string]SnapshotStatsResponse, error) {
	mssr := make(map[string]SnapshotStatsResponse)

	u := *s.url
	u.Path = path.Join(u.Path, "/_snapshot")
	var srr SnapshotRepositoriesResponse
	err := s.getAndParseURL(&u, &srr)
	if err != nil {
		return nil, err
	}
	for repository := range srr {
		u := *s.url
		u.Path = path.Join(u.Path, "/_snapshot", repository, "/_all")
		var ssr SnapshotStatsResponse
		err := s.getAndParseURL(&u, &ssr)
		if err != nil {
			continue
		}
		mssr[repository] = ssr
	}

	return mssr, nil
}

// Collect gets Snapshots metric values
func (s *Snapshots) Collect(ch chan<- prometheus.Metric) {
	s.totalScrapes.Inc()
	defer func() {
		ch <- s.up
		ch <- s.totalScrapes
		ch <- s.jsonParseFailures
	}()

	// indices
	snapshotsStatsResp, err := s.fetchAndDecodeSnapshotsStats()
	if err != nil {
		s.up.Set(0)
		_ = level.Warn(s.logger).Log(
			"msg", "failed to fetch and decode snapshot stats",
			"err", err,
		)
		return
	}
	s.up.Set(1)

	// Snapshots stats
	for repositoryName, snapshotStats := range snapshotsStatsResp {
		for _, metric := range s.repositoryMetrics {
			ch <- prometheus.MustNewConstMetric(
				metric.Desc,
				metric.Type,
				metric.Value(snapshotStats),
				metric.Labels(repositoryName)...,
			)
		}
		if len(snapshotStats.Snapshots) == 0 {
			continue
		}

		lastSnapshot := snapshotStats.Snapshots[len(snapshotStats.Snapshots)-1]
		for _, metric := range s.snapshotMetrics {
			ch <- prometheus.MustNewConstMetric(
				metric.Desc,
				metric.Type,
				metric.Value(lastSnapshot),
				metric.Labels(repositoryName, lastSnapshot)...,
			)
		}
	}
}
<|endoftext|>


<commit_before>package walletunlocker

import (
	"crypto/rand"
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/lightningnetwork/lnd/aezeed"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcwallet/wallet"
	"golang.org/x/net/context"
	"github.com/lightningnetwork/lnd/keychain"
)

// WalletInitMsg is a message sent by the UnlockerService when a user wishes to
// set up the internal wallet for the first time. The user MUST provide a
// passphrase, but is also able to provide their own source of entropy. If
// provided, then this source of entropy will be used to generate the wallet's
// HD seed. Otherwise, the wallet will generate one itself.
type WalletInitMsg struct {
	// Passphrase is the passphrase that will be used to encrypt the wallet
	// itself. This MUST be at least 8 characters.
	Passphrase []byte

	// WalletSeed is the deciphered cipher seed that the wallet should use
	// to initialize itself.
	WalletSeed *aezeed.CipherSeed

	// RecoveryWindow is the address look-ahead used when restoring a seed
	// with existing funds. A recovery window zero indicates that no
	// recovery should be attempted, such as after the wallet's initial
	// creation.
	RecoveryWindow uint32
}

// WalletUnlockMsg is a message sent by the UnlockerService when a user wishes
// to unlock the internal wallet after initial setup. The user can optionally
// specify a recovery window, which will resume an interrupted rescan for used
// addresses.
type WalletUnlockMsg struct {
	// Passphrase is the passphrase that will be used to encrypt the wallet
	// itself. This MUST be at least 8 characters.
	Passphrase []byte

	// RecoveryWindow is the address look-ahead used when restoring a seed
	// with existing funds. A recovery window zero indicates that no
	// recovery should be attempted, such as after the wallet's initial
	// creation, but before any addresses have been created.
	RecoveryWindow uint32
}

// UnlockerService implements the WalletUnlocker service used to provide lnd
// with a password for wallet encryption at startup. Additionally, during
// initial setup, users can provide their own source of entropy which will be
// used to generate the seed that's ultimately used within the wallet.
type UnlockerService struct {
	// InitMsgs is a channel that carries all wallet init messages.
	InitMsgs chan *WalletInitMsg

	// UnlockMsgs is a channel where unlock parameters provided by the rpc
	// client to be used to unlock and decrypt an existing wallet will be
	// sent.
	UnlockMsgs chan *WalletUnlockMsg

	chainDir      string
	netParams     *chaincfg.Params
	macaroonFiles []string
}

// New creates and returns a new UnlockerService.
func New(chainDir string, params *chaincfg.Params,
	macaroonFiles []string) *UnlockerService {

	return &UnlockerService{
		InitMsgs:      make(chan *WalletInitMsg, 1),
		UnlockMsgs:    make(chan *WalletUnlockMsg, 1),
		chainDir:      chainDir,
		netParams:     params,
		macaroonFiles: macaroonFiles,
	}
}

// GenSeed is the first method that should be used to instantiate a new lnd
// instance. This method allows a caller to generate a new aezeed cipher seed
// given an optional passphrase. If provided, the passphrase will be necessary
// to decrypt the cipherseed to expose the internal wallet seed.
//
// Once the cipherseed is obtained and verified by the user, the InitWallet
// method should be used to commit the newly generated seed, and create the
// wallet.
func (u *UnlockerService) GenSeed(ctx context.Context,
	in *lnrpc.GenSeedRequest) (*lnrpc.GenSeedResponse, error) {

	// Before we start, we'll ensure that the wallet hasn't already created
	// so we don't show a *new* seed to the user if one already exists.
	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, 0)
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}
	if walletExists {
		return nil, fmt.Errorf("wallet already exists")
	}

	var entropy [aezeed.EntropySize]byte

	switch {
	// If the user provided any entropy, then we'll make sure it's sized
	// properly.
	case len(in.SeedEntropy) != 0 && len(in.SeedEntropy) != aezeed.EntropySize:
		return nil, fmt.Errorf("incorrect entropy length: expected "+
			"16 bytes, instead got %v bytes", len(in.SeedEntropy))

	// If the user provided the correct number of bytes, then we'll copy it
	// over into our buffer for usage.
	case len(in.SeedEntropy) == aezeed.EntropySize:
		copy(entropy[:], in.SeedEntropy[:])

	// Otherwise, we'll generate a fresh new set of bytes to use as entropy
	// to generate the seed.
	default:
		if _, err := rand.Read(entropy[:]); err != nil {
			return nil, err
		}
	}

	// Now that we have our set of entropy, we'll create a new cipher seed
	// instance.
	//
	cipherSeed, err := aezeed.New(
		keychain.KeyDerivationVersion, &entropy, time.Now(),
	)
	if err != nil {
		return nil, err
	}

	// With our raw cipher seed obtained, we'll convert it into an encoded
	// mnemonic using the user specified pass phrase.
	mnemonic, err := cipherSeed.ToMnemonic(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	// Additionally, we'll also obtain the raw enciphered cipher seed as
	// well to return to the user.
	encipheredSeed, err := cipherSeed.Encipher(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	return &lnrpc.GenSeedResponse{
		CipherSeedMnemonic: []string(mnemonic[:]),
		EncipheredSeed:     encipheredSeed[:],
	}, nil
}

// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
// on disk.
//
// In the case of a recovery scenario, the user can also specify their aezeed
// mnemonic and passphrase. If set, then the daemon will use this prior state
// to initialize its internal wallet.
//
// Alternatively, this can be used along with the GenSeed RPC to obtain a
// seed, then present it to the user. Once it has been verified by the user,
// the seed can be fed into this RPC in order to commit the new wallet.
func (u *UnlockerService) InitWallet(ctx context.Context,
	in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {

	// Make sure the password meets our constraints.
	password := in.WalletPassword
	if err := validatePassword(password); err != nil {
		return nil, err
	}

	// Require that the recovery window be non-negative.
	recoveryWindow := in.RecoveryWindow
	if recoveryWindow < 0 {
		return nil, fmt.Errorf("recovery window %d must be "+
			"non-negative", recoveryWindow)
	}

	// We'll then open up the directory that will be used to store the
	// wallet's files so we can check if the wallet already exists.
	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, uint32(recoveryWindow))

	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	// If the wallet already exists, then we'll exit early as we can't
	// create the wallet if it already exists!
	if walletExists {
		return nil, fmt.Errorf("wallet already exists")
	}

	// At this point, we know that the wallet doesn't already exist. So
	// we'll map the user provided aezeed and passphrase into a decoded
	// cipher seed instance.
	var mnemonic aezeed.Mnemonic
	copy(mnemonic[:], in.CipherSeedMnemonic[:])

	// If we're unable to map it back into the ciphertext, then either the
	// mnemonic is wrong, or the passphrase is wrong.
	cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	// With the cipher seed deciphered, and the auth service created, we'll
	// now send over the wallet password and the seed. This will allow the
	// daemon to initialize itself and startup.
	initMsg := &WalletInitMsg{
		Passphrase:     password,
		WalletSeed:     cipherSeed,
		RecoveryWindow: uint32(recoveryWindow),
	}

	u.InitMsgs <- initMsg

	return &lnrpc.InitWalletResponse{}, nil
}

// UnlockWallet sends the password provided by the incoming UnlockWalletRequest
// over the UnlockMsgs channel in case it successfully decrypts an existing
// wallet found in the chain's wallet database directory.
func (u *UnlockerService) UnlockWallet(ctx context.Context,
	in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) {

	password := in.WalletPassword
	recoveryWindow := uint32(in.RecoveryWindow)

	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, recoveryWindow)

	// Check if wallet already exists.
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	if !walletExists {
		// Cannot unlock a wallet that does not exist!
		return nil, fmt.Errorf("wallet not found")
	}

	// Try opening the existing wallet with the provided password.
	_, err = loader.OpenExistingWallet(password, false)
	if err != nil {
		// Could not open wallet, most likely this means that provided
		// password was incorrect.
		return nil, err
	}

	// We successfully opened the wallet, but we'll need to unload it to
	// make sure lnd can open it later.
	if err := loader.UnloadWallet(); err != nil {
		// TODO: not return error here?
		return nil, err
	}

	walletUnlockMsg := &WalletUnlockMsg{
		Passphrase:     password,
		RecoveryWindow: recoveryWindow,
	}

	// At this point we was able to open the existing wallet with the
	// provided password. We send the password over the UnlockMsgs
	// channel, such that it can be used by lnd to open the wallet.
	u.UnlockMsgs <- walletUnlockMsg

	return &lnrpc.UnlockWalletResponse{}, nil
}

// ChangePassword changes the password of the wallet and sends the new password
// across the UnlockPasswords channel to automatically unlock the wallet if
// successful.
func (u *UnlockerService) ChangePassword(ctx context.Context,
	in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) {

	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, 0)

	// First, we'll make sure the wallet exists for the specific chain and
	// network.
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	if !walletExists {
		return nil, errors.New("wallet not found")
	}

	publicPw := in.CurrentPassword
	privatePw := in.CurrentPassword

	// If the current password is blank, we'll assume the user is coming
	// from a --noencryptwallet state, so we'll use the default passwords.
	if len(in.CurrentPassword) == 0 {
		publicPw = lnwallet.DefaultPublicPassphrase
		privatePw = lnwallet.DefaultPrivatePassphrase
	}

	// Make sure the new password meets our constraints.
	if err := validatePassword(in.NewPassword); err != nil {
		return nil, err
	}

	// Load the existing wallet in order to proceed with the password change.
	w, err := loader.OpenExistingWallet(publicPw, false)
	if err != nil {
		return nil, err
	}
	// Unload the wallet to allow lnd to open it later on.
	defer loader.UnloadWallet()

	// Since the macaroon database is also encrypted with the wallet's
	// password, we'll remove all of the macaroon files so that they're
	// re-generated at startup using the new password. We'll make sure to do
	// this after unlocking the wallet to ensure macaroon files don't get
	// deleted with incorrect password attempts.
	for _, file := range u.macaroonFiles {
		if err := os.Remove(file); err != nil {
			return nil, err
		}
	}

	// Attempt to change both the public and private passphrases for the
	// wallet. This will be done atomically in order to prevent one
	// passphrase change from being successful and not the other.
	err = w.ChangePassphrases(
		publicPw, in.NewPassword, privatePw, in.NewPassword,
	)
	if err != nil {
		return nil, fmt.Errorf("unable to change wallet passphrase: "+
			"%v", err)
	}

	// Finally, send the new password across the UnlockPasswords channel to
	// automatically unlock the wallet.
	u.UnlockMsgs <- &WalletUnlockMsg{Passphrase: in.NewPassword}

	return &lnrpc.ChangePasswordResponse{}, nil
}

// validatePassword assures the password meets all of our constraints.
func validatePassword(password []byte) error {
	// Passwords should have a length of at least 8 characters.
	if len(password) < 8 {
		return errors.New("password must have at least 8 characters")
	}

	return nil
}
<commit_msg>walletunlocker: pass unlocked wallet back to lnd to avoid double unlock<commit_after>package walletunlocker

import (
	"crypto/rand"
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/lightningnetwork/lnd/aezeed"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcwallet/wallet"
	"golang.org/x/net/context"
	"github.com/lightningnetwork/lnd/keychain"
)

// WalletInitMsg is a message sent by the UnlockerService when a user wishes to
// set up the internal wallet for the first time. The user MUST provide a
// passphrase, but is also able to provide their own source of entropy. If
// provided, then this source of entropy will be used to generate the wallet's
// HD seed. Otherwise, the wallet will generate one itself.
type WalletInitMsg struct {
	// Passphrase is the passphrase that will be used to encrypt the wallet
	// itself. This MUST be at least 8 characters.
	Passphrase []byte

	// WalletSeed is the deciphered cipher seed that the wallet should use
	// to initialize itself.
	WalletSeed *aezeed.CipherSeed

	// RecoveryWindow is the address look-ahead used when restoring a seed
	// with existing funds. A recovery window zero indicates that no
	// recovery should be attempted, such as after the wallet's initial
	// creation.
	RecoveryWindow uint32
}

// WalletUnlockMsg is a message sent by the UnlockerService when a user wishes
// to unlock the internal wallet after initial setup. The user can optionally
// specify a recovery window, which will resume an interrupted rescan for used
// addresses.
type WalletUnlockMsg struct {
	// Passphrase is the passphrase that will be used to encrypt the wallet
	// itself. This MUST be at least 8 characters.
	Passphrase []byte

	// RecoveryWindow is the address look-ahead used when restoring a seed
	// with existing funds. A recovery window zero indicates that no
	// recovery should be attempted, such as after the wallet's initial
	// creation, but before any addresses have been created.
	RecoveryWindow uint32

	// Wallet is the loaded and unlocked Wallet. This is returned
	// through the channel to avoid it being unlocked twice (once to check
	// if the password is correct, here in the WalletUnlocker and again
	// later when lnd actually uses it). Because unlocking involves scrypt
	// which is resource intensive, we want to avoid doing it twice.
	Wallet *wallet.Wallet
}

// UnlockerService implements the WalletUnlocker service used to provide lnd
// with a password for wallet encryption at startup. Additionally, during
// initial setup, users can provide their own source of entropy which will be
// used to generate the seed that's ultimately used within the wallet.
type UnlockerService struct {
	// InitMsgs is a channel that carries all wallet init messages.
	InitMsgs chan *WalletInitMsg

	// UnlockMsgs is a channel where unlock parameters provided by the rpc
	// client to be used to unlock and decrypt an existing wallet will be
	// sent.
	UnlockMsgs chan *WalletUnlockMsg

	chainDir      string
	netParams     *chaincfg.Params
	macaroonFiles []string
}

// New creates and returns a new UnlockerService.
func New(chainDir string, params *chaincfg.Params,
	macaroonFiles []string) *UnlockerService {

	return &UnlockerService{
		InitMsgs:      make(chan *WalletInitMsg, 1),
		UnlockMsgs:    make(chan *WalletUnlockMsg, 1),
		chainDir:      chainDir,
		netParams:     params,
		macaroonFiles: macaroonFiles,
	}
}

// GenSeed is the first method that should be used to instantiate a new lnd
// instance. This method allows a caller to generate a new aezeed cipher seed
// given an optional passphrase. If provided, the passphrase will be necessary
// to decrypt the cipherseed to expose the internal wallet seed.
//
// Once the cipherseed is obtained and verified by the user, the InitWallet
// method should be used to commit the newly generated seed, and create the
// wallet.
func (u *UnlockerService) GenSeed(ctx context.Context,
	in *lnrpc.GenSeedRequest) (*lnrpc.GenSeedResponse, error) {

	// Before we start, we'll ensure that the wallet hasn't already created
	// so we don't show a *new* seed to the user if one already exists.
	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, 0)
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}
	if walletExists {
		return nil, fmt.Errorf("wallet already exists")
	}

	var entropy [aezeed.EntropySize]byte

	switch {
	// If the user provided any entropy, then we'll make sure it's sized
	// properly.
	case len(in.SeedEntropy) != 0 && len(in.SeedEntropy) != aezeed.EntropySize:
		return nil, fmt.Errorf("incorrect entropy length: expected "+
			"16 bytes, instead got %v bytes", len(in.SeedEntropy))

	// If the user provided the correct number of bytes, then we'll copy it
	// over into our buffer for usage.
	case len(in.SeedEntropy) == aezeed.EntropySize:
		copy(entropy[:], in.SeedEntropy[:])

	// Otherwise, we'll generate a fresh new set of bytes to use as entropy
	// to generate the seed.
	default:
		if _, err := rand.Read(entropy[:]); err != nil {
			return nil, err
		}
	}

	// Now that we have our set of entropy, we'll create a new cipher seed
	// instance.
	//
	cipherSeed, err := aezeed.New(
		keychain.KeyDerivationVersion, &entropy, time.Now(),
	)
	if err != nil {
		return nil, err
	}

	// With our raw cipher seed obtained, we'll convert it into an encoded
	// mnemonic using the user specified pass phrase.
	mnemonic, err := cipherSeed.ToMnemonic(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	// Additionally, we'll also obtain the raw enciphered cipher seed as
	// well to return to the user.
	encipheredSeed, err := cipherSeed.Encipher(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	return &lnrpc.GenSeedResponse{
		CipherSeedMnemonic: []string(mnemonic[:]),
		EncipheredSeed:     encipheredSeed[:],
	}, nil
}

// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
// on disk.
//
// In the case of a recovery scenario, the user can also specify their aezeed
// mnemonic and passphrase. If set, then the daemon will use this prior state
// to initialize its internal wallet.
//
// Alternatively, this can be used along with the GenSeed RPC to obtain a
// seed, then present it to the user. Once it has been verified by the user,
// the seed can be fed into this RPC in order to commit the new wallet.
func (u *UnlockerService) InitWallet(ctx context.Context,
	in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {

	// Make sure the password meets our constraints.
	password := in.WalletPassword
	if err := validatePassword(password); err != nil {
		return nil, err
	}

	// Require that the recovery window be non-negative.
	recoveryWindow := in.RecoveryWindow
	if recoveryWindow < 0 {
		return nil, fmt.Errorf("recovery window %d must be "+
			"non-negative", recoveryWindow)
	}

	// We'll then open up the directory that will be used to store the
	// wallet's files so we can check if the wallet already exists.
	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, uint32(recoveryWindow))

	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	// If the wallet already exists, then we'll exit early as we can't
	// create the wallet if it already exists!
	if walletExists {
		return nil, fmt.Errorf("wallet already exists")
	}

	// At this point, we know that the wallet doesn't already exist. So
	// we'll map the user provided aezeed and passphrase into a decoded
	// cipher seed instance.
	var mnemonic aezeed.Mnemonic
	copy(mnemonic[:], in.CipherSeedMnemonic[:])

	// If we're unable to map it back into the ciphertext, then either the
	// mnemonic is wrong, or the passphrase is wrong.
	cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
	if err != nil {
		return nil, err
	}

	// With the cipher seed deciphered, and the auth service created, we'll
	// now send over the wallet password and the seed. This will allow the
	// daemon to initialize itself and startup.
	initMsg := &WalletInitMsg{
		Passphrase:     password,
		WalletSeed:     cipherSeed,
		RecoveryWindow: uint32(recoveryWindow),
	}

	u.InitMsgs <- initMsg

	return &lnrpc.InitWalletResponse{}, nil
}

// UnlockWallet sends the password provided by the incoming UnlockWalletRequest
// over the UnlockMsgs channel in case it successfully decrypts an existing
// wallet found in the chain's wallet database directory.
func (u *UnlockerService) UnlockWallet(ctx context.Context,
	in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) {

	password := in.WalletPassword
	recoveryWindow := uint32(in.RecoveryWindow)

	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, recoveryWindow)

	// Check if wallet already exists.
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	if !walletExists {
		// Cannot unlock a wallet that does not exist!
		return nil, fmt.Errorf("wallet not found")
	}

	// Try opening the existing wallet with the provided password.
	unlockedWallet, err := loader.OpenExistingWallet(password, false)
	if err != nil {
		// Could not open wallet, most likely this means that provided
		// password was incorrect.
		return nil, err
	}

	// We successfully opened the wallet and pass the instance back to
	// avoid it needing to be unlocked again.
	walletUnlockMsg := &WalletUnlockMsg{
		Passphrase:     password,
		RecoveryWindow: recoveryWindow,
		Wallet:         unlockedWallet,
	}

	// At this point we was able to open the existing wallet with the
	// provided password. We send the password over the UnlockMsgs
	// channel, such that it can be used by lnd to open the wallet.
	u.UnlockMsgs <- walletUnlockMsg

	return &lnrpc.UnlockWalletResponse{}, nil
}

// ChangePassword changes the password of the wallet and sends the new password
// across the UnlockPasswords channel to automatically unlock the wallet if
// successful.
func (u *UnlockerService) ChangePassword(ctx context.Context,
	in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) {

	netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
	loader := wallet.NewLoader(u.netParams, netDir, 0)

	// First, we'll make sure the wallet exists for the specific chain and
	// network.
	walletExists, err := loader.WalletExists()
	if err != nil {
		return nil, err
	}

	if !walletExists {
		return nil, errors.New("wallet not found")
	}

	publicPw := in.CurrentPassword
	privatePw := in.CurrentPassword

	// If the current password is blank, we'll assume the user is coming
	// from a --noencryptwallet state, so we'll use the default passwords.
	if len(in.CurrentPassword) == 0 {
		publicPw = lnwallet.DefaultPublicPassphrase
		privatePw = lnwallet.DefaultPrivatePassphrase
	}

	// Make sure the new password meets our constraints.
	if err := validatePassword(in.NewPassword); err != nil {
		return nil, err
	}

	// Load the existing wallet in order to proceed with the password change.
	w, err := loader.OpenExistingWallet(publicPw, false)
	if err != nil {
		return nil, err
	}
	// Unload the wallet to allow lnd to open it later on.
	defer loader.UnloadWallet()

	// Since the macaroon database is also encrypted with the wallet's
	// password, we'll remove all of the macaroon files so that they're
	// re-generated at startup using the new password. We'll make sure to do
	// this after unlocking the wallet to ensure macaroon files don't get
	// deleted with incorrect password attempts.
	for _, file := range u.macaroonFiles {
		if err := os.Remove(file); err != nil {
			return nil, err
		}
	}

	// Attempt to change both the public and private passphrases for the
	// wallet. This will be done atomically in order to prevent one
	// passphrase change from being successful and not the other.
	err = w.ChangePassphrases(
		publicPw, in.NewPassword, privatePw, in.NewPassword,
	)
	if err != nil {
		return nil, fmt.Errorf("unable to change wallet passphrase: "+
			"%v", err)
	}

	// Finally, send the new password across the UnlockPasswords channel to
	// automatically unlock the wallet.
	u.UnlockMsgs <- &WalletUnlockMsg{Passphrase: in.NewPassword}

	return &lnrpc.ChangePasswordResponse{}, nil
}

// validatePassword assures the password meets all of our constraints.
func validatePassword(password []byte) error {
	// Passwords should have a length of at least 8 characters.
	if len(password) < 8 {
		return errors.New("password must have at least 8 characters")
	}

	return nil
}
<|endoftext|>


<commit_before>/*
   Copyright (C) 2017 Enrico Bianchi (enrico.bianchi@gmail.com)
   Project       Syncimap
   Description   A rsync like IMAP syncronization tool
   License       Apache License 2.0 (see LICENSE for details)
*/

package main

import (
	"gopkg.in/alecthomas/kingpin.v2"
	"log"
	"net/url"
	"syncimap/imap"
)

type Data struct {
	Source      *url.URL
	Destination *url.URL
}

func parse(data *Data) {
	var err error

	source := kingpin.Arg("source", "Set source URI").Required().String()
	destination := kingpin.Arg("destination", "Set destination URI").Required().String()

	kingpin.CommandLine.HelpFlag.Short('h')
	kingpin.Parse()

	data.Source, err = url.Parse(*source)
	if err != nil {
		log.Fatalf("Source URI is not valid")
	}

	data.Destination, err = url.Parse(*destination)
	if err != nil {
		log.Fatalf("Destination URI is not valid")
	}
}

func main() {
	var data Data
	var srcconn, dstconn imap.Connection
	var err error

	parse(&data)

	err = srcconn.Dial(data.Source)
	if err != nil {
		log.Fatalf("Failed to open connection for %s: %s", data.Source.Hostname(), err.Error())
	}
	defer srcconn.Client.Close()
	defer srcconn.Client.Logout()

	err = dstconn.Dial(data.Destination)
	if err != nil {
		log.Fatalf("Failed to open connection for %s: %s", data.Destination.Hostname(), err.Error())
	}
	defer dstconn.Client.Close()
	defer dstconn.Client.Logout()

	folders := srcconn.Folders("*")

	for _, folder := range folders {
		if err := dstconn.CreateFolder(folder); err != nil {
			log.Println("Cannot create folder", folder, ": ", err.Error())
		}
	}
}
<commit_msg>Checked if mailbox exists<commit_after>/*
   Copyright (C) 2017 Enrico Bianchi (enrico.bianchi@gmail.com)
   Project       Syncimap
   Description   A rsync like IMAP syncronization tool
   License       Apache License 2.0 (see LICENSE for details)
*/

package main

import (
	"gopkg.in/alecthomas/kingpin.v2"
	"log"
	"net/url"
	"syncimap/imap"
)

type Data struct {
	Source      *url.URL
	Destination *url.URL
}

func parse(data *Data) {
	var err error

	source := kingpin.Arg("source", "Set source URI").Required().String()
	destination := kingpin.Arg("destination", "Set destination URI").Required().String()

	kingpin.CommandLine.HelpFlag.Short('h')
	kingpin.Parse()

	data.Source, err = url.Parse(*source)
	if err != nil {
		log.Fatalf("Source URI is not valid")
	}

	data.Destination, err = url.Parse(*destination)
	if err != nil {
		log.Fatalf("Destination URI is not valid")
	}
}

func main() {
	var data Data
	var srcconn, dstconn imap.Connection
	var err error

	parse(&data)

	err = srcconn.Dial(data.Source)
	if err != nil {
		log.Fatalf("Failed to open connection for %s: %s", data.Source.Hostname(), err.Error())
	}
	defer srcconn.Client.Close()
	defer srcconn.Client.Logout()

	err = dstconn.Dial(data.Destination)
	if err != nil {
		log.Fatalf("Failed to open connection for %s: %s", data.Destination.Hostname(), err.Error())
	}
	defer dstconn.Client.Close()
	defer dstconn.Client.Logout()

	folders := srcconn.Folders("*")

	for _, folder := range folders {
		if !dstconn.FolderExist(folder) {
			if err := dstconn.CreateFolder(folder); err != nil {
				log.Println("Cannot create folder", folder, ": ", err.Error())
			}
		}
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/docdb"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSDocDBClusterParameterGroup_basic(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig(parameterGroupName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestMatchResourceAttr(
						"aws_docdb_cluster_parameter_group.bar", "arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:rds:[^:]+:\\d{12}:cluster-pg:%s", parameterGroupName))),
					resource.TestCheckResourceAttr(
						"aws_docdb_cluster_parameter_group.bar", "name", parameterGroupName),
					resource.TestCheckResourceAttr(
						"aws_docdb_cluster_parameter_group.bar", "family", "docdb3.6"),
					resource.TestCheckResourceAttr(
						"aws_docdb_cluster_parameter_group.bar", "description", "Managed by Terraform"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.#", "0"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.%", "0"),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.bar",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_namePrefix(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_namePrefix,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.test", &v),
					resource.TestMatchResourceAttr(
						"aws_docdb_cluster_parameter_group.test", "name", regexp.MustCompile("^tf-test-")),
				),
			},
			{
				ResourceName:            "aws_docdb_cluster_parameter_group.test",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"name_prefix"},
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_generatedName(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_generatedName,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.test", &v),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.test",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Description(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Description(parameterGroupName, "custom description"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "description", "custom description"),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.bar",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_disappears(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig(parameterGroupName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupDisappears(&v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Parameter(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Parameter(parameterGroupName, "tls", "disabled"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.#", "1"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.3297634353.apply_method", "pending-reboot"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.3297634353.name", "tls"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.3297634353.value", "disabled"),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.bar",
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Parameter(parameterGroupName, "tls", "enabled"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.#", "1"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.4005179180.apply_method", "pending-reboot"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.4005179180.name", "tls"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "parameter.4005179180.value", "enabled"),
				),
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Tags(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.%", "1"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.key1", "value1"),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.bar",
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key1", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.%", "1"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.key1", "value2"),
				),
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.bar", &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.%", "1"),
					resource.TestCheckResourceAttr("aws_docdb_cluster_parameter_group.bar", "tags.key2", "value2"),
				),
			},
		},
	})
}

func testAccCheckAWSDocDBClusterParameterGroupDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).docdbconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_docdb_cluster_parameter_group" {
			continue
		}

		resp, err := conn.DescribeDBClusterParameterGroups(
			&docdb.DescribeDBClusterParameterGroupsInput{
				DBClusterParameterGroupName: aws.String(rs.Primary.ID),
			})

		if err == nil {
			if len(resp.DBClusterParameterGroups) != 0 &&
				aws.StringValue(resp.DBClusterParameterGroups[0].DBClusterParameterGroupName) == rs.Primary.ID {
				return errors.New("DocDB Cluster Parameter Group still exists")
			}
		}

		if err != nil {
			if isAWSErr(err, docdb.ErrCodeDBParameterGroupNotFoundFault, "") {
				return nil
			}
			return err
		}
	}

	return nil
}

func testAccCheckAWSDocDBClusterParameterGroupDisappears(group *docdb.DBClusterParameterGroup) resource.TestCheckFunc {

	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).docdbconn

		params := &docdb.DeleteDBClusterParameterGroupInput{
			DBClusterParameterGroupName: group.DBClusterParameterGroupName,
		}

		_, err := conn.DeleteDBClusterParameterGroup(params)
		if err != nil {
			return err
		}

		return waitForDocDBClusterParameterGroupDeletion(conn, *group.DBClusterParameterGroupName)
	}
}

func testAccCheckAWSDocDBClusterParameterGroupAttributes(v *docdb.DBClusterParameterGroup, name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {

		if *v.DBClusterParameterGroupName != name {
			return fmt.Errorf("bad name: %#v expected: %v", *v.DBClusterParameterGroupName, name)
		}

		if *v.DBParameterGroupFamily != "docdb3.6" {
			return fmt.Errorf("bad family: %#v", *v.DBParameterGroupFamily)
		}

		return nil
	}
}

func testAccCheckAWSDocDBClusterParameterGroupExists(n string, v *docdb.DBClusterParameterGroup) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return errors.New("No DocDB Cluster Parameter Group ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).docdbconn

		opts := docdb.DescribeDBClusterParameterGroupsInput{
			DBClusterParameterGroupName: aws.String(rs.Primary.ID),
		}

		resp, err := conn.DescribeDBClusterParameterGroups(&opts)

		if err != nil {
			return err
		}

		if len(resp.DBClusterParameterGroups) != 1 ||
			aws.StringValue(resp.DBClusterParameterGroups[0].DBClusterParameterGroupName) != rs.Primary.ID {
			return fmt.Errorf("DocDB Cluster Parameter Group not found: %s", rs.Primary.ID)
		}

		*v = *resp.DBClusterParameterGroups[0]

		return nil
	}
}

func testAccAWSDocDBClusterParameterGroupConfig(name string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  family = "docdb3.6"
  name   = "%s"
}
`, name)
}

func testAccAWSDocDBClusterParameterGroupConfig_Description(name, description string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  family      = "docdb3.6"
  description = "%s"
  name        = "%s"
}
`, description, name)
}

func testAccAWSDocDBClusterParameterGroupConfig_Parameter(name, pName, pValue string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  name   = "%s"
  family = "docdb3.6"

  parameter {
    name  = "%s"
    value = "%s"
  }
}
`, name, pName, pValue)
}

func testAccAWSDocDBClusterParameterGroupConfig_Tags(name, tKey, tValue string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  name   = "%s"
  family = "docdb3.6"

  tags = {
    %s = "%s"
  }
}
`, name, tKey, tValue)
}

const testAccAWSDocDBClusterParameterGroupConfig_namePrefix = `
resource "aws_docdb_cluster_parameter_group" "test" {
  name_prefix = "tf-test-"
  family = "docdb3.6"
}
`
const testAccAWSDocDBClusterParameterGroupConfig_generatedName = `
resource "aws_docdb_cluster_parameter_group" "test" {
	family = "docdb3.6"
}
`
<commit_msg>Updates docdb acceptance tests to use ARN testing check functions<commit_after>package aws

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/docdb"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSDocDBClusterParameterGroup_basic(t *testing.T) {
	var v docdb.DBClusterParameterGroup
	resourceName := "aws_docdb_cluster_parameter_group.bar"

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig(parameterGroupName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					testAccMatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(fmt.Sprintf("cluster-pg:%s$", parameterGroupName))),
					resource.TestCheckResourceAttr(resourceName, "name", parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "family", "docdb3.6"),
					resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"),
					resource.TestCheckResourceAttr(resourceName, "parameter.#", "0"),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_namePrefix(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_namePrefix,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.test", &v),
					resource.TestMatchResourceAttr("aws_docdb_cluster_parameter_group.test", "name", regexp.MustCompile("^tf-test-")),
				),
			},
			{
				ResourceName:            "aws_docdb_cluster_parameter_group.test",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"name_prefix"},
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_generatedName(t *testing.T) {
	var v docdb.DBClusterParameterGroup

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_generatedName,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists("aws_docdb_cluster_parameter_group.test", &v),
				),
			},
			{
				ResourceName:      "aws_docdb_cluster_parameter_group.test",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Description(t *testing.T) {
	var v docdb.DBClusterParameterGroup
	resourceName := "aws_docdb_cluster_parameter_group.bar"

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Description(parameterGroupName, "custom description"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "description", "custom description"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_disappears(t *testing.T) {
	var v docdb.DBClusterParameterGroup
	resourceName := "aws_docdb_cluster_parameter_group.bar"

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig(parameterGroupName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupDisappears(&v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Parameter(t *testing.T) {
	var v docdb.DBClusterParameterGroup
	resourceName := "aws_docdb_cluster_parameter_group.bar"

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Parameter(parameterGroupName, "tls", "disabled"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "parameter.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameter.3297634353.apply_method", "pending-reboot"),
					resource.TestCheckResourceAttr(resourceName, "parameter.3297634353.name", "tls"),
					resource.TestCheckResourceAttr(resourceName, "parameter.3297634353.value", "disabled"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Parameter(parameterGroupName, "tls", "enabled"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "parameter.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "parameter.4005179180.apply_method", "pending-reboot"),
					resource.TestCheckResourceAttr(resourceName, "parameter.4005179180.name", "tls"),
					resource.TestCheckResourceAttr(resourceName, "parameter.4005179180.value", "enabled"),
				),
			},
		},
	})
}

func TestAccAWSDocDBClusterParameterGroup_Tags(t *testing.T) {
	var v docdb.DBClusterParameterGroup
	resourceName := "aws_docdb_cluster_parameter_group.bar"

	parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", acctest.RandInt())

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSDocDBClusterParameterGroupDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key1", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value2"),
				),
			},
			{
				Config: testAccAWSDocDBClusterParameterGroupConfig_Tags(parameterGroupName, "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSDocDBClusterParameterGroupExists(resourceName, &v),
					testAccCheckAWSDocDBClusterParameterGroupAttributes(&v, parameterGroupName),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
		},
	})
}

func testAccCheckAWSDocDBClusterParameterGroupDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).docdbconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_docdb_cluster_parameter_group" {
			continue
		}

		resp, err := conn.DescribeDBClusterParameterGroups(
			&docdb.DescribeDBClusterParameterGroupsInput{
				DBClusterParameterGroupName: aws.String(rs.Primary.ID),
			})

		if err == nil {
			if len(resp.DBClusterParameterGroups) != 0 &&
				aws.StringValue(resp.DBClusterParameterGroups[0].DBClusterParameterGroupName) == rs.Primary.ID {
				return errors.New("DocDB Cluster Parameter Group still exists")
			}
		}

		if err != nil {
			if isAWSErr(err, docdb.ErrCodeDBParameterGroupNotFoundFault, "") {
				return nil
			}
			return err
		}
	}

	return nil
}

func testAccCheckAWSDocDBClusterParameterGroupDisappears(group *docdb.DBClusterParameterGroup) resource.TestCheckFunc {

	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).docdbconn

		params := &docdb.DeleteDBClusterParameterGroupInput{
			DBClusterParameterGroupName: group.DBClusterParameterGroupName,
		}

		_, err := conn.DeleteDBClusterParameterGroup(params)
		if err != nil {
			return err
		}

		return waitForDocDBClusterParameterGroupDeletion(conn, *group.DBClusterParameterGroupName)
	}
}

func testAccCheckAWSDocDBClusterParameterGroupAttributes(v *docdb.DBClusterParameterGroup, name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {

		if *v.DBClusterParameterGroupName != name {
			return fmt.Errorf("bad name: %#v expected: %v", *v.DBClusterParameterGroupName, name)
		}

		if *v.DBParameterGroupFamily != "docdb3.6" {
			return fmt.Errorf("bad family: %#v", *v.DBParameterGroupFamily)
		}

		return nil
	}
}

func testAccCheckAWSDocDBClusterParameterGroupExists(n string, v *docdb.DBClusterParameterGroup) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return errors.New("No DocDB Cluster Parameter Group ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).docdbconn

		opts := docdb.DescribeDBClusterParameterGroupsInput{
			DBClusterParameterGroupName: aws.String(rs.Primary.ID),
		}

		resp, err := conn.DescribeDBClusterParameterGroups(&opts)

		if err != nil {
			return err
		}

		if len(resp.DBClusterParameterGroups) != 1 ||
			aws.StringValue(resp.DBClusterParameterGroups[0].DBClusterParameterGroupName) != rs.Primary.ID {
			return fmt.Errorf("DocDB Cluster Parameter Group not found: %s", rs.Primary.ID)
		}

		*v = *resp.DBClusterParameterGroups[0]

		return nil
	}
}

func testAccAWSDocDBClusterParameterGroupConfig(name string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  family = "docdb3.6"
  name   = "%s"
}
`, name)
}

func testAccAWSDocDBClusterParameterGroupConfig_Description(name, description string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  family      = "docdb3.6"
  description = "%s"
  name        = "%s"
}
`, description, name)
}

func testAccAWSDocDBClusterParameterGroupConfig_Parameter(name, pName, pValue string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  name   = "%s"
  family = "docdb3.6"

  parameter {
    name  = "%s"
    value = "%s"
  }
}
`, name, pName, pValue)
}

func testAccAWSDocDBClusterParameterGroupConfig_Tags(name, tKey, tValue string) string {
	return fmt.Sprintf(`
resource "aws_docdb_cluster_parameter_group" "bar" {
  name   = "%s"
  family = "docdb3.6"

  tags = {
    %s = "%s"
  }
}
`, name, tKey, tValue)
}

const testAccAWSDocDBClusterParameterGroupConfig_namePrefix = `
resource "aws_docdb_cluster_parameter_group" "test" {
  name_prefix = "tf-test-"
  family = "docdb3.6"
}
`
const testAccAWSDocDBClusterParameterGroupConfig_generatedName = `
resource "aws_docdb_cluster_parameter_group" "test" {
	family = "docdb3.6"
}
`
<|endoftext|>