<commit_before>package proxyquerier

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/jacksontj/promxy/promclient"
	"github.com/jacksontj/promxy/promhttputil"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

type ProxyQuerier struct {
	ServerGroups [][]string
	// TODO: support limits to the hosts we query
	// Configurable -- N hosts to query M required to complete
}

// Close closes the querier. Behavior for subsequent calls to Querier methods
// is undefined.
func (h *ProxyQuerier) Close() error { return nil }

// QueryRange returns a list of series iterators for the selected
// time range and label matchers. The iterators need to be closed
// after usage.
func (h *ProxyQuerier) QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryRange: from=%v through=%v matchers=%v\n", from, through, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())
	// TODO: how do we pick this? configurable or something based on time range?
	// if we want to calculate irate for example, then we'd want more, right?
	if step := ctx.Value("step"); step != nil {
		stepInterval := step.(time.Duration)
		values.Add("step", (stepInterval / 2).String())
	} else {
		values.Add("step", "1")
	}

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query_range", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil

}

// QueryInstant returns a list of series iterators for the selected
// instant and label matchers. The iterators need to be closed after usage.
func (h *ProxyQuerier) QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryInstant: ts=%v stalenessDelta=%v matchers=%v\n", ts, stalenessDelta, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("time", ts.String())
	values.Add("_", ts.Add(-stalenessDelta).String())

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil
}

// MetricsForLabelMatchers returns the metrics from storage that satisfy
// the given sets of label matchers. Each set of matchers must contain at
// least one label matcher that does not match the empty string. Otherwise,
// an empty list is returned. Within one set of matchers, the intersection
// of matching series is computed. The final return value will be the union
// of the per-set results. The times from and through are hints for the
// storage to optimize the search. The storage MAY exclude metrics that
// have no samples in the specified interval from the returned map. In
// doubt, specify model.Earliest for from and model.Latest for through.
func (h *ProxyQuerier) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matcherSets ...metric.LabelMatchers) ([]metric.Metric, error) {
	fmt.Printf("MetricsForLabelMatchers: from=%v through=%v matcherSets=%v\n", from, through, matcherSets)
	// http://10.0.1.115:8082/api/v1/series?match[]=scrape_samples_scraped&start=1507432802&end=1507433102

	// TODO: check on this? For now the assumption is that we can merge all of the lists
	matchers := make([]*metric.LabelMatcher, 0, len(matcherSets))
	for _, matcherList := range matcherSets {
		matchers = append(matchers, matcherList...)
	}

	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	values := url.Values{}
	values.Add("match[]", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())

	result := &promclient.SeriesResult{}

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/series", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetSeries(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}

			// TODO check status
			if err := result.Merge(serverResult); err != nil {
				return nil, err
			}
		}
	}

	metrics := make([]metric.Metric, len(result.Data))
	for i, labelSet := range result.Data {
		metrics[i] = metric.Metric{
			Copied: true,
			Metric: model.Metric(labelSet),
		}
	}
	return metrics, nil
}

// LastSampleForLabelMatchers returns the last samples that have been
// ingested for the time series matching the given set of label matchers.
// The label matching behavior is the same as in MetricsForLabelMatchers.
// All returned samples are between the specified cutoff time and now.
func (h *ProxyQuerier) LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error) {
	fmt.Printf("LastSampleForLabelMatchers: cutoff=%v matcherSets=%v\n", cutoff, matcherSets)
	return nil, fmt.Errorf("Not implemented")
}

// Get all of the label values that are associated with a given label name.
func (h *ProxyQuerier) LabelValuesForLabelName(ctx context.Context, name model.LabelName) (model.LabelValues, error) {
	fmt.Printf("LabelValuesForLabelName: name=%v\n", name)
	return nil, fmt.Errorf("Not implemented")
}
<commit_msg>Parallelize all fetching<commit_after>package proxyquerier

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/jacksontj/promxy/promclient"
	"github.com/jacksontj/promxy/promhttputil"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

type ProxyQuerier struct {
	ServerGroups [][]string
	// TODO: support limits to the hosts we query
	// Configurable -- N hosts to query M required to complete
}

// Close closes the querier. Behavior for subsequent calls to Querier methods
// is undefined.
func (h *ProxyQuerier) Close() error { return nil }

// QueryRange returns a list of series iterators for the selected
// time range and label matchers. The iterators need to be closed
// after usage.
func (h *ProxyQuerier) QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryRange: from=%v through=%v matchers=%v\n", from, through, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())
	// TODO: how do we pick this? configurable or something based on time range?
	// if we want to calculate irate for example, then we'd want more, right?
	if step := ctx.Value("step"); step != nil {
		stepInterval := step.(time.Duration)
		values.Add("step", (stepInterval / 2).String())
	} else {
		values.Add("step", "1")
	}

	var result model.Value

	retChan := make(chan interface{})
	retCount := 0
	childContext, childContextCancel := context.WithCancel(ctx)
	defer childContextCancel()

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			retCount++

			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query_range", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			go func(ctx context.Context, retChan chan interface{}) {
				serverResult, err := promclient.GetData(ctx, parsedUrl.String())
				var ret interface{}
				if err != nil {
					ret = err
				} else {
					ret = serverResult
				}
				select {
				case retChan <- ret:
					return
				case <-ctx.Done():
					return
				}
			}(childContext, retChan)
		}
	}

	for i := 0; i < retCount; i++ {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case ret := <-retChan:
			switch retTyped := ret.(type) {
			case error:
				return nil, err
			case *promhttputil.Response:
				// TODO: check response code, how do we want to handle it?
				if retTyped.Status != promhttputil.StatusSuccess {
					continue
				}

				// TODO: what to do in failure
				qData, ok := retTyped.Data.(*promhttputil.QueryData)
				if !ok {
					continue
				}

				// TODO: check qData.ResultType

				if result == nil {
					result = qData.Result
				} else {
					result, err = promhttputil.MergeValues(result, qData.Result)
					if err != nil {
						return nil, err
					}
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil

}

// QueryInstant returns a list of series iterators for the selected
// instant and label matchers. The iterators need to be closed after usage.
func (h *ProxyQuerier) QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryInstant: ts=%v stalenessDelta=%v matchers=%v\n", ts, stalenessDelta, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("time", ts.String())
	values.Add("_", ts.Add(-stalenessDelta).String())

	var result model.Value

	retChan := make(chan interface{})
	retCount := 0
	childContext, childContextCancel := context.WithCancel(ctx)
	defer childContextCancel()

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			retCount++

			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			go func(ctx context.Context, retChan chan interface{}) {
				serverResult, err := promclient.GetData(ctx, parsedUrl.String())
				var ret interface{}
				if err != nil {
					ret = err
				} else {
					ret = serverResult
				}
				select {
				case retChan <- ret:
					return
				case <-ctx.Done():
					return
				}
			}(childContext, retChan)
		}
	}

	for i := 0; i < retCount; i++ {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case ret := <-retChan:
			switch retTyped := ret.(type) {
			case error:
				return nil, err
			case *promhttputil.Response:
				// TODO: check response code, how do we want to handle it?
				if retTyped.Status != promhttputil.StatusSuccess {
					continue
				}

				// TODO: what to do in failure
				qData, ok := retTyped.Data.(*promhttputil.QueryData)
				if !ok {
					continue
				}

				// TODO: check qData.ResultType

				if result == nil {
					result = qData.Result
				} else {
					result, err = promhttputil.MergeValues(result, qData.Result)
					if err != nil {
						return nil, err
					}
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil
}

// MetricsForLabelMatchers returns the metrics from storage that satisfy
// the given sets of label matchers. Each set of matchers must contain at
// least one label matcher that does not match the empty string. Otherwise,
// an empty list is returned. Within one set of matchers, the intersection
// of matching series is computed. The final return value will be the union
// of the per-set results. The times from and through are hints for the
// storage to optimize the search. The storage MAY exclude metrics that
// have no samples in the specified interval from the returned map. In
// doubt, specify model.Earliest for from and model.Latest for through.
func (h *ProxyQuerier) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matcherSets ...metric.LabelMatchers) ([]metric.Metric, error) {
	fmt.Printf("MetricsForLabelMatchers: from=%v through=%v matcherSets=%v\n", from, through, matcherSets)
	// http://10.0.1.115:8082/api/v1/series?match[]=scrape_samples_scraped&start=1507432802&end=1507433102

	// TODO: check on this? For now the assumption is that we can merge all of the lists
	matchers := make([]*metric.LabelMatcher, 0, len(matcherSets))
	for _, matcherList := range matcherSets {
		matchers = append(matchers, matcherList...)
	}

	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	values := url.Values{}
	values.Add("match[]", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())

	result := &promclient.SeriesResult{}

	retChan := make(chan interface{})
	retCount := 0
	childContext, childContextCancel := context.WithCancel(ctx)
	defer childContextCancel()

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			retCount++

			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/series", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			go func(ctx context.Context, retChan chan interface{}) {
				serverResult, err := promclient.GetSeries(ctx, parsedUrl.String())
				var ret interface{}
				if err != nil {
					ret = err
				} else {
					ret = serverResult
				}
				select {
				case retChan <- ret:
					return
				case <-ctx.Done():
					return
				}
			}(childContext, retChan)
		}
	}

	for i := 0; i < retCount; i++ {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case ret := <-retChan:
			switch retTyped := ret.(type) {
			case error:
				return nil, err
			case *promclient.SeriesResult:
				// TODO check status
				if err := result.Merge(retTyped); err != nil {
					return nil, err
				}
			}
		}
	}

	metrics := make([]metric.Metric, len(result.Data))
	for i, labelSet := range result.Data {
		metrics[i] = metric.Metric{
			Copied: true,
			Metric: model.Metric(labelSet),
		}
	}
	return metrics, nil
}

// LastSampleForLabelMatchers returns the last samples that have been
// ingested for the time series matching the given set of label matchers.
// The label matching behavior is the same as in MetricsForLabelMatchers.
// All returned samples are between the specified cutoff time and now.
func (h *ProxyQuerier) LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error) {
	fmt.Printf("LastSampleForLabelMatchers: cutoff=%v matcherSets=%v\n", cutoff, matcherSets)
	return nil, fmt.Errorf("Not implemented")
}

// Get all of the label values that are associated with a given label name.
func (h *ProxyQuerier) LabelValuesForLabelName(ctx context.Context, name model.LabelName) (model.LabelValues, error) {
	fmt.Printf("LabelValuesForLabelName: name=%v\n", name)
	return nil, fmt.Errorf("Not implemented")
}
<|endoftext|>


<commit_before>package proxyquerier

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/jacksontj/promxy/promclient"
	"github.com/jacksontj/promxy/promhttputil"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

type ProxyQuerier struct {
	ServerGroups [][]string
}

// Close closes the querier. Behavior for subsequent calls to Querier methods
// is undefined.
func (h *ProxyQuerier) Close() error { return nil }

// QueryRange returns a list of series iterators for the selected
// time range and label matchers. The iterators need to be closed
// after usage.
func (h *ProxyQuerier) QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryRange: from=%v through=%v matchers=%v\n", from, through, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())
	// TODO: how do we pick this? configurable or something based on time range?
	// if we want to calculate irate for example, then we'd want more, right?
	if step := ctx.Value("step"); step != nil {
		stepInterval := step.(time.Duration)
		values.Add("step", (stepInterval / 2).String())
	} else {
		values.Add("step", "1")
	}

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query_range", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil

}

// QueryInstant returns a list of series iterators for the selected
// instant and label matchers. The iterators need to be closed after usage.
func (h *ProxyQuerier) QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryInstant: ts=%v stalenessDelta=%v matchers=%v\n", ts, stalenessDelta, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("time", ts.String())
	values.Add("_", ts.Add(-stalenessDelta).String())

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil
}

// MetricsForLabelMatchers returns the metrics from storage that satisfy
// the given sets of label matchers. Each set of matchers must contain at
// least one label matcher that does not match the empty string. Otherwise,
// an empty list is returned. Within one set of matchers, the intersection
// of matching series is computed. The final return value will be the union
// of the per-set results. The times from and through are hints for the
// storage to optimize the search. The storage MAY exclude metrics that
// have no samples in the specified interval from the returned map. In
// doubt, specify model.Earliest for from and model.Latest for through.
func (h *ProxyQuerier) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matcherSets ...metric.LabelMatchers) ([]metric.Metric, error) {
	fmt.Printf("MetricsForLabelMatchers: from=%v through=%v matcherSets=%v\n", from, through, matcherSets)
	// http://10.0.1.115:8082/api/v1/series?match[]=scrape_samples_scraped&start=1507432802&end=1507433102

	// TODO: check on this? For now the assumption is that we can merge all of the lists
	matchers := make([]*metric.LabelMatcher, 0, len(matcherSets))
	for _, matcherList := range matcherSets {
		matchers = append(matchers, matcherList...)
	}

	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	values := url.Values{}
	values.Add("match[]", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())

	result := &promclient.SeriesResult{}

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/series", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetSeries(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}

			// TODO check status
			if err := result.Merge(serverResult); err != nil {
				return nil, err
			}
		}
	}

	metrics := make([]metric.Metric, len(result.Data))
	for i, labelSet := range result.Data {
		metrics[i] = metric.Metric{
			Copied: true,
			Metric: model.Metric(labelSet),
		}
	}
	return metrics, nil
}

// LastSampleForLabelMatchers returns the last samples that have been
// ingested for the time series matching the given set of label matchers.
// The label matching behavior is the same as in MetricsForLabelMatchers.
// All returned samples are between the specified cutoff time and now.
func (h *ProxyQuerier) LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error) {
	fmt.Printf("LastSampleForLabelMatchers: cutoff=%v matcherSets=%v\n", cutoff, matcherSets)
	return nil, fmt.Errorf("Not implemented")
}

// Get all of the label values that are associated with a given label name.
func (h *ProxyQuerier) LabelValuesForLabelName(ctx context.Context, name model.LabelName) (model.LabelValues, error) {
	fmt.Printf("LabelValuesForLabelName: name=%v\n", name)
	return nil, fmt.Errorf("Not implemented")
}
<commit_msg>Add TODO notes<commit_after>package proxyquerier

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"github.com/jacksontj/promxy/promclient"
	"github.com/jacksontj/promxy/promhttputil"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/local"
	"github.com/prometheus/prometheus/storage/metric"
)

type ProxyQuerier struct {
	ServerGroups [][]string
	// TODO: support limits to the hosts we query
	// Configurable -- N hosts to query M required to complete
}

// Close closes the querier. Behavior for subsequent calls to Querier methods
// is undefined.
func (h *ProxyQuerier) Close() error { return nil }

// QueryRange returns a list of series iterators for the selected
// time range and label matchers. The iterators need to be closed
// after usage.
func (h *ProxyQuerier) QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryRange: from=%v through=%v matchers=%v\n", from, through, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())
	// TODO: how do we pick this? configurable or something based on time range?
	// if we want to calculate irate for example, then we'd want more, right?
	if step := ctx.Value("step"); step != nil {
		stepInterval := step.(time.Duration)
		values.Add("step", (stepInterval / 2).String())
	} else {
		values.Add("step", "1")
	}

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query_range", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil

}

// QueryInstant returns a list of series iterators for the selected
// instant and label matchers. The iterators need to be closed after usage.
func (h *ProxyQuerier) QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]local.SeriesIterator, error) {
	// TODO: move to logging
	fmt.Printf("QueryInstant: ts=%v stalenessDelta=%v matchers=%v\n", ts, stalenessDelta, matchers)

	// http://localhost:8080/api/v1/query?query=scrape_duration_seconds%7Bjob%3D%22prometheus%22%7D&time=1507412244.663&_=1507412096887
	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	// Create the query params
	values := url.Values{}
	values.Add("query", pql)
	values.Add("time", ts.String())
	values.Add("_", ts.Add(-stalenessDelta).String())

	var result model.Value

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/query", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetData(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}
			// TODO: check response code, how do we want to handle it?
			if serverResult.Status != promhttputil.StatusSuccess {
				continue
			}

			// TODO: what to do in failure
			qData, ok := serverResult.Data.(*promhttputil.QueryData)
			if !ok {
				continue
			}

			// TODO: check qData.ResultType

			if result == nil {
				result = qData.Result
			} else {
				result, err = promhttputil.MergeValues(result, qData.Result)
				if err != nil {
					return nil, err
				}
			}
		}
	}

	iterators := promclient.IteratorsForValue(result)
	returnIterators := make([]local.SeriesIterator, len(iterators))
	for i, item := range iterators {
		returnIterators[i] = item
	}
	return returnIterators, nil
}

// MetricsForLabelMatchers returns the metrics from storage that satisfy
// the given sets of label matchers. Each set of matchers must contain at
// least one label matcher that does not match the empty string. Otherwise,
// an empty list is returned. Within one set of matchers, the intersection
// of matching series is computed. The final return value will be the union
// of the per-set results. The times from and through are hints for the
// storage to optimize the search. The storage MAY exclude metrics that
// have no samples in the specified interval from the returned map. In
// doubt, specify model.Earliest for from and model.Latest for through.
func (h *ProxyQuerier) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matcherSets ...metric.LabelMatchers) ([]metric.Metric, error) {
	fmt.Printf("MetricsForLabelMatchers: from=%v through=%v matcherSets=%v\n", from, through, matcherSets)
	// http://10.0.1.115:8082/api/v1/series?match[]=scrape_samples_scraped&start=1507432802&end=1507433102

	// TODO: check on this? For now the assumption is that we can merge all of the lists
	matchers := make([]*metric.LabelMatcher, 0, len(matcherSets))
	for _, matcherList := range matcherSets {
		matchers = append(matchers, matcherList...)
	}

	pql, err := MatcherToString(matchers)
	if err != nil {
		return nil, err
	}

	values := url.Values{}
	values.Add("match[]", pql)
	values.Add("start", from.String())
	values.Add("end", through.String())

	result := &promclient.SeriesResult{}

	// Query each in the groups and get data
	for _, serverGroup := range h.ServerGroups {
		for _, server := range serverGroup {
			parsedUrl, err := url.Parse(fmt.Sprintf("%s/api/v1/series", server))
			if err != nil {
				return nil, err
			}
			parsedUrl.RawQuery = values.Encode()

			serverResult, err := promclient.GetSeries(ctx, parsedUrl.String())
			if err != nil {
				return nil, err
			}

			// TODO check status
			if err := result.Merge(serverResult); err != nil {
				return nil, err
			}
		}
	}

	metrics := make([]metric.Metric, len(result.Data))
	for i, labelSet := range result.Data {
		metrics[i] = metric.Metric{
			Copied: true,
			Metric: model.Metric(labelSet),
		}
	}
	return metrics, nil
}

// LastSampleForLabelMatchers returns the last samples that have been
// ingested for the time series matching the given set of label matchers.
// The label matching behavior is the same as in MetricsForLabelMatchers.
// All returned samples are between the specified cutoff time and now.
func (h *ProxyQuerier) LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error) {
	fmt.Printf("LastSampleForLabelMatchers: cutoff=%v matcherSets=%v\n", cutoff, matcherSets)
	return nil, fmt.Errorf("Not implemented")
}

// Get all of the label values that are associated with a given label name.
func (h *ProxyQuerier) LabelValuesForLabelName(ctx context.Context, name model.LabelName) (model.LabelValues, error) {
	fmt.Printf("LabelValuesForLabelName: name=%v\n", name)
	return nil, fmt.Errorf("Not implemented")
}
<|endoftext|>


<commit_before>package model

import (
	"github.com/ViBiOh/funds/db"
)

// AlertsByIsin retrieve Alerts by isin
func AlertsByIsin(isin string) ([]Alert, error) {
	rows, err := db.DB.Query(`SELECT type FROM alerts WHERE isin=$1`, isin)
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	var (
		alerts    []Alert
		alertType string
	)

	for rows.Next() {
		if err := rows.Scan(&alertType); err != nil {
			return nil, err
		}

		alerts = append(alerts, Alert{Isin: isin, AlertType: alertType})
	}

	return alerts, nil
}
<commit_msg>Update alert_db.go<commit_after>package model

import (
	"github.com/ViBiOh/funds/db"
)

// AlertsByIsin retrieve Alerts by isin
func AlertsByIsin(isin string) ([]Alert, error) {
	rows, err := db.DB.Query(`SELECT type FROM alerts WHERE isin=$1 ORDER BY creation_date DESC`, isin)
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	var (
		alerts    []Alert
		alertType string
	)

	for rows.Next() {
		if err := rows.Scan(&alertType); err != nil {
			return nil, err
		}

		alerts = append(alerts, Alert{Isin: isin, AlertType: alertType})
	}

	return alerts, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
)

func init() {
	flag.Usage = usage
	flag.Parse()
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] ROOT\n", os.Args[0])
	flag.PrintDefaults()
	os.Exit(1)
}

// Pages maps slugs to responses.
var pages = map[string]*Resp{}

func main() {
	// We need a root folder.
	if flag.NArg() < 1 {
		usage()
	}

	// Recieve port from heruko.
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatalln("[!] $PORT environmental variable is not set.")
	}

	// Our root to read in markdown files.
	root := flag.Arg(0)
	log.Printf("[o] Root directory: `%s`\n", root)
	// We'll parse and store the responses ahead of time.
	err := loadRoot(root)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("[o] Pages: %#v\n", pages)

	log.Println("[o] Starting server.")
	log.Printf("[o] Listening on port: %s\n", port)

	// Our request handler.
	http.HandleFunc("/", handler)

	// Listen on port and serve with our handler.
	err = http.ListenAndServe(":"+port, nil)
	if err != nil {
		panic(err)
	}
}

func loadRoot(root string) (err error) {
	var dirs []string
	err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		// We only search for article directories.
		if !fi.IsDir() {
			return nil
		}
		dirs = append(dirs, path)
		return nil
	})
	if err != nil {
		return err
	}
	return parseDirs(dirs)
}

func stripRoot(dir string) string {
	i := strings.IndexRune(dir, '/')
	if i == -1 {
		return "/"
	}
	return dir[i:]
}

func parseDirs(dirs []string) (err error) {
	for _, dir := range dirs {
		r, err := parseDir(dir)
		if err != nil {
			return err
		}
		pages[stripRoot(dir)] = r
		fmt.Printf("[o] Resp: %#v\n", r)
	}
	return nil
}

func readMarkdown(filename string) (string, error) {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", err
	}
	renderer := blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML, "", "")
	return string(blackfriday.MarkdownOptions(buf, renderer, blackfriday.Options{
		Extensions: blackfriday.EXTENSION_AUTO_HEADER_IDS,
	})), nil
}

func parseDir(dir string) (*Resp, error) {
	bodyPath := filepath.Join(dir, "body.md")
	log.Println("[o] Body path:", bodyPath)

	sidebarPath := filepath.Join(dir, "sidebar.md")
	log.Println("[o] Sidebar path:", sidebarPath)

	body, err := readMarkdown(bodyPath)
	if err != nil {
		return nil, err
	}
	log.Printf("[o] Body html: %#v\n", body)

	sidebar, err := readMarkdown(sidebarPath)
	if err != nil {
		return nil, err
	}
	log.Printf("[o] Sidebar html: %#v\n", sidebar)
	fi, err := os.Stat(bodyPath)
	if err != nil {
		return nil, err
	}
	anchs, err := anchors(body)
	if err != nil {
		return nil, err
	}
	return &Resp{
		Title:     "unimplemented",
		Slug:      "unimplemented",
		UpdatedAt: fi.ModTime().Format(ISO8601DateTime),
		Image:     "unimplemented",
		Body:      body,
		Sidebar:   sidebar,
		Anchors:   anchs,
	}, nil
}

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

func anchors(body string) (anchs []Anchor, err error) {
	node, err := html.Parse(strings.NewReader(body))
	if err != nil {
		return nil, err
	}
	anchs = make([]Anchor, 0)
	var f func(*html.Node)
	f = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "h1" {
			log.Println("[o] Add anchor:", n)
			id := ""
			for _, attr := range n.Attr {
				if attr.Key == "id" {
					id = attr.Val
					break
				}
				return
			}
			anchs = append(anchs, Anchor{
				ID:    id,
				Value: n.FirstChild.Data,
			})
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}
	}
	f(node)
	return anchs, nil
}

// Resp is the response we serve for file queries.
type Resp struct {
	Title     string   `json:"title"`      // Human-readable title.
	Slug      string   `json:"slug"`       // URL-slug.
	UpdatedAt string   `json:"updated_at"` // Body update time.
	Image     string   `json:"image"`      // Path/URL/Placeholder to image.
	Body      string   `json:"body"`
	Sidebar   string   `json:"sidebar"`
	Anchors   []Anchor `json:"anchors"`
}

// Anchor is a html anchor tag with an id attribute and a value. Represents: Value
type Anchor struct {
	ID    string `json:"id"`    // Id of h2 element.
	Value string `json:"value"` // Value inside the anchor tag.
}

// handler parses and serves responses to our file queries.
func handler(res http.ResponseWriter, req *http.Request) {
	// Requested URL. We extract the path.
	query := req.URL.Path
	log.Printf("[o] Got query: `%s`\n", query)

	clean := filepath.Clean(query)
	log.Printf("[o] Sanitized path: `%s`\n", clean)

	r, ok := pages[clean]
	if !ok {
		log.Printf("[!] Page doesn't exist: `%s`\n", clean)
		res.WriteHeader(404)
		return
	}
	log.Println("[o] Marshal the response.")
	buf, err := json.Marshal(r)
	if err != nil {
		log.Println("[!] Unexpected error:", err)
		res.WriteHeader(500)
		return
	}
	log.Println("[o] Serve the response.")
	fmt.Fprintln(res, string(buf))
}
<commit_msg>fix #1<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
)

func init() {
	flag.Usage = usage
	flag.Parse()
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] ROOT\n", os.Args[0])
	flag.PrintDefaults()
	os.Exit(1)
}

// Pages maps slugs to responses.
var pages = map[string]*Resp{}

func main() {
	// We need a root folder.
	if flag.NArg() < 1 {
		usage()
	}

	// Recieve port from heruko.
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatalln("[!] $PORT environmental variable is not set.")
	}

	// Our root to read in markdown files.
	root := flag.Arg(0)
	log.Printf("[o] Root directory: `%s`\n", root)
	// We'll parse and store the responses ahead of time.
	err := loadRoot(root)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("[o] Pages: %#v\n", pages)

	log.Println("[o] Starting server.")
	log.Printf("[o] Listening on port: %s\n", port)

	// Our request handler.
	http.HandleFunc("/", handler)

	// Listen on port and serve with our handler.
	err = http.ListenAndServe(":"+port, nil)
	if err != nil {
		panic(err)
	}
}

func loadRoot(root string) (err error) {
	var dirs []string
	err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		// We only search for article directories.
		if !fi.IsDir() {
			return nil
		}
		dirs = append(dirs, path)
		return nil
	})
	if err != nil {
		return err
	}
	return parseDirs(dirs)
}

func stripRoot(dir string) string {
	i := strings.IndexRune(dir, '/')
	if i == -1 {
		return "/"
	}
	return dir[i:]
}

func parseDirs(dirs []string) (err error) {
	for _, dir := range dirs {
		r, err := parseDir(dir)
		if err != nil {
			return err
		}
		pages[stripRoot(dir)] = r
		fmt.Printf("[o] Resp: %#v\n", r)
	}
	return nil
}

func readMarkdown(filename string) (string, error) {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", err
	}
	renderer := blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML, "", "")
	return string(blackfriday.MarkdownOptions(buf, renderer, blackfriday.Options{
		Extensions: blackfriday.EXTENSION_AUTO_HEADER_IDS,
	})), nil
}

func parseDir(dir string) (*Resp, error) {
	log.Println("[o] dir:", dir)

	bodyPath := filepath.Join(dir, "body.md")
	log.Println("[o] Body path:", bodyPath)

	sidebarPath := filepath.Join(dir, "sidebar.md")
	log.Println("[o] Sidebar path:", sidebarPath)

	body, err := readMarkdown(bodyPath)
	if err != nil {
		return nil, err
	}
	log.Printf("[o] Body html: %#v\n", body)

	sidebar, err := readMarkdown(sidebarPath)
	if err != nil {
		return nil, err
	}
	log.Printf("[o] Sidebar html: %#v\n", sidebar)
	fi, err := os.Stat(bodyPath)
	if err != nil {
		return nil, err
	}
	anchs, err := anchors(body)
	if err != nil {
		return nil, err
	}
	return &Resp{
		Title:     "unimplemented",
		Slug:      filepath.Base(stripRoot(dir)),
		UpdatedAt: fi.ModTime().Format(ISO8601DateTime),
		Image:     "unimplemented",
		Body:      body,
		Sidebar:   sidebar,
		Anchors:   anchs,
	}, nil
}

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

func anchors(body string) (anchs []Anchor, err error) {
	node, err := html.Parse(strings.NewReader(body))
	if err != nil {
		return nil, err
	}
	anchs = make([]Anchor, 0)
	var f func(*html.Node)
	f = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "h1" {
			log.Println("[o] Add anchor:", n)
			id := ""
			for _, attr := range n.Attr {
				if attr.Key == "id" {
					id = attr.Val
					break
				}
				return
			}
			anchs = append(anchs, Anchor{
				ID:    id,
				Value: n.FirstChild.Data,
			})
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}
	}
	f(node)
	return anchs, nil
}

// Resp is the response we serve for file queries.
type Resp struct {
	Title     string   `json:"title"`      // Human-readable title.
	Slug      string   `json:"slug"`       // URL-slug.
	UpdatedAt string   `json:"updated_at"` // Body update time.
	Image     string   `json:"image"`      // Path/URL/Placeholder to image.
	Body      string   `json:"body"`
	Sidebar   string   `json:"sidebar"`
	Anchors   []Anchor `json:"anchors"`
}

// Anchor is a html anchor tag with an id attribute and a value. Represents: Value
type Anchor struct {
	ID    string `json:"id"`    // Id of h2 element.
	Value string `json:"value"` // Value inside the anchor tag.
}

// handler parses and serves responses to our file queries.
func handler(res http.ResponseWriter, req *http.Request) {
	// Requested URL. We extract the path.
	query := req.URL.Path
	log.Printf("[o] Got query: `%s`\n", query)

	clean := filepath.Clean(query)
	log.Printf("[o] Sanitized path: `%s`\n", clean)

	r, ok := pages[clean]
	if !ok {
		log.Printf("[!] Page doesn't exist: `%s`\n", clean)
		res.WriteHeader(404)
		return
	}
	log.Println("[o] Marshal the response.")
	buf, err := json.Marshal(r)
	if err != nil {
		log.Println("[!] Unexpected error:", err)
		res.WriteHeader(500)
		return
	}
	log.Println("[o] Serve the response.")
	fmt.Fprintln(res, string(buf))
}
<|endoftext|>


<commit_before>// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
// Copyright (C) 2015 Douglas Hall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package decode

import (
	"bytes"
	"fmt"
	"log"
	"math"
)

// PacketConfig specifies packet-specific radio configuration.
type PacketConfig struct {
	DataRate int

	BlockSize, BlockSize2       int
	SymbolLength, SymbolLength2 int
	SampleRate                  int

	PreambleSymbols, PacketSymbols int
	PreambleLength, PacketLength   int
	Preamble                       string

	BufferLength int

	CenterFreq uint32
}

func (cfg PacketConfig) Decimate(decimation int) PacketConfig {
	cfg.BlockSize /= decimation
	cfg.BlockSize2 /= decimation
	cfg.SymbolLength /= decimation
	cfg.SymbolLength2 /= decimation
	cfg.SampleRate /= decimation

	cfg.PreambleLength /= decimation
	cfg.PacketLength /= decimation

	cfg.BufferLength /= decimation

	return cfg
}

func (cfg PacketConfig) Log() {
	log.Println("BlockSize:", cfg.BlockSize)
	log.Println("CenterFreq:", cfg.CenterFreq)
	log.Println("SampleRate:", cfg.SampleRate)
	log.Println("DataRate:", cfg.DataRate)
	log.Println("SymbolLength:", cfg.SymbolLength)
	log.Println("PreambleSymbols:", cfg.PreambleSymbols)
	log.Println("PreambleLength:", cfg.PreambleLength)
	log.Println("PacketSymbols:", cfg.PacketSymbols)
	log.Println("PacketLength:", cfg.PacketLength)
	log.Println("Preamble:", cfg.Preamble)
}

// Decoder contains buffers and radio configuration.
type Decoder struct {
	Cfg PacketConfig

	Decimation int
	DecCfg     PacketConfig

	IQ        []byte
	Signal    []float64
	Filtered  []float64
	Quantized []byte

	csum  []float64
	demod Demodulator

	preamble []byte
	slices   [][]byte

	pkt []byte
}

// Create a new decoder with the given packet configuration.
func NewDecoder(cfg PacketConfig, decimation int, fastMag bool) (d Decoder) {
	d.Cfg = cfg

	d.Cfg.SymbolLength2 = d.Cfg.SymbolLength << 1
	d.Cfg.SampleRate = d.Cfg.DataRate * d.Cfg.SymbolLength

	d.Cfg.PreambleLength = d.Cfg.PreambleSymbols * d.Cfg.SymbolLength2
	d.Cfg.PacketLength = d.Cfg.PacketSymbols * d.Cfg.SymbolLength2

	d.Cfg.BlockSize = NextPowerOf2(d.Cfg.PreambleLength)
	d.Cfg.BlockSize2 = d.Cfg.BlockSize << 1

	d.Cfg.BufferLength = d.Cfg.PacketLength + d.Cfg.BlockSize

	d.Decimation = decimation
	d.DecCfg = d.Cfg.Decimate(d.Decimation)
	fmt.Printf("%+v\n", d.Cfg)
	fmt.Printf("%+v\n", d.DecCfg)

	// Allocate necessary buffers.
	d.IQ = make([]byte, d.Cfg.BufferLength<<1)
	d.Signal = make([]float64, d.Cfg.BufferLength)
	d.Filtered = make([]float64, d.DecCfg.BufferLength)
	d.Quantized = make([]byte, d.DecCfg.BufferLength)

	d.csum = make([]float64, (d.Cfg.BlockSize + d.Cfg.SymbolLength2 + 1))

	// Calculate magnitude lookup table specified by -fastmag flag.
	if fastMag {
		d.demod = NewAlphaMaxBetaMinLUT()
	} else {
		d.demod = NewSqrtMagLUT()
	}

	// Pre-calculate a byte-slice version of the preamble for searching.
	d.preamble = make([]byte, len(d.Cfg.Preamble))
	for idx := range d.Cfg.Preamble {
		if d.Cfg.Preamble[idx] == '1' {
			d.preamble[idx] = 1
		}
	}

	// Slice quantized sample buffer to make searching for the preamble more
	// memory local. Pre-allocate a flat buffer so memory is contiguous and
	// assign slices to the buffer.
	d.slices = make([][]byte, d.DecCfg.SymbolLength2)
	flat := make([]byte, d.DecCfg.BlockSize2-(d.DecCfg.BlockSize2%d.DecCfg.SymbolLength2))

	symbolsPerBlock := d.DecCfg.BlockSize2 / d.DecCfg.SymbolLength2
	for symbolOffset := range d.slices {
		lower := symbolOffset * symbolsPerBlock
		upper := (symbolOffset + 1) * symbolsPerBlock
		d.slices[symbolOffset] = flat[lower:upper]
	}

	// Signal up to the final stage is 1-bit per byte. Allocate a buffer to
	// store packed version 8-bits per byte.
	d.pkt = make([]byte, (d.DecCfg.PacketSymbols+7)>>3)

	return
}

// Decode accepts a sample block and performs various DSP techniques to extract a packet.
func (d Decoder) Decode(input []byte) []int {
	// Shift buffers to append new block.
	copy(d.IQ, d.IQ[d.Cfg.BlockSize<<1:])
	copy(d.Signal, d.Signal[d.Cfg.BlockSize:])
	copy(d.Filtered, d.Filtered[d.DecCfg.BlockSize:])
	copy(d.Quantized, d.Quantized[d.DecCfg.BlockSize:])
	copy(d.IQ[d.Cfg.PacketLength<<1:], input[:])

	iqBlock := d.IQ[d.Cfg.PacketLength<<1:]
	signalBlock := d.Signal[d.Cfg.PacketLength:]

	// Compute the magnitude of the new block.
	d.demod.Execute(iqBlock, signalBlock)

	signalBlock = d.Signal[d.Cfg.PacketLength-d.Cfg.SymbolLength2:]
	filterBlock := d.Filtered[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]

	// Perform matched filter on new block.
	d.Filter(signalBlock, filterBlock)

	// Perform bit-decision on new block.
	Quantize(filterBlock, d.Quantized[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:])

	// Pack the quantized signal into slices for searching.
	d.Pack(d.Quantized[:d.DecCfg.BlockSize2], d.slices)

	// Return a list of indexes the preamble exists at.
	return d.Search(d.slices, d.preamble)
}

// A Demodulator knows how to demodulate an array of uint8 IQ samples into an
// array of float64 samples.
type Demodulator interface {
	Execute([]byte, []float64)
}

// Default Magnitude Lookup Table
type MagLUT []float64

// Pre-computes normalized squares with most common DC offset for rtl-sdr dongles.
func NewSqrtMagLUT() (lut MagLUT) {
	lut = make([]float64, 0x100)
	for idx := range lut {
		lut[idx] = 127.4 - float64(idx)
		lut[idx] *= lut[idx]
	}
	return
}

// Calculates complex magnitude on given IQ stream writing result to output.
func (lut MagLUT) Execute(input []byte, output []float64) {
	for idx := 0; idx < len(input); idx += 2 {
		output[idx>>1] = math.Sqrt(lut[input[idx]] + lut[input[idx+1]])
	}
}

// Alpha*Max + Beta*Min Magnitude Approximation Lookup Table.
type AlphaMaxBetaMinLUT []float64

// Pre-computes absolute values with most common DC offset for rtl-sdr dongles.
func NewAlphaMaxBetaMinLUT() (lut AlphaMaxBetaMinLUT) {
	lut = make([]float64, 0x100)
	for idx := range lut {
		lut[idx] = math.Abs(127.4 - float64(idx))
	}
	return
}

// Calculates complex magnitude on given IQ stream writing result to output.
func (lut AlphaMaxBetaMinLUT) Execute(input []byte, output []float64) {
	const (
		α = 0.948059448969
		ß = 0.392699081699
	)

	for idx := 0; idx < len(input); idx += 2 {
		i := lut[input[idx]]
		q := lut[input[idx+1]]
		if i > q {
			output[idx>>1] = α*i + ß*q
		} else {
			output[idx>>1] = α*q + ß*i
		}
	}
}

// Matched filter for Manchester coded signals. Output signal's sign at each
// sample determines the bit-value since Manchester symbols have odd symmetry.
func (d Decoder) Filter(input, output []float64) {
	// Computing the cumulative summation over the signal simplifies
	// filtering to the difference of a pair of subtractions.
	var sum float64
	for idx, v := range input {
		sum += v
		d.csum[idx+1] = sum
	}

	// Filter result is difference of summation of lower and upper symbols.
	lower := d.csum[d.Cfg.SymbolLength:]
	upper := d.csum[d.Cfg.SymbolLength2:]
	n := len(input) - d.Cfg.SymbolLength2
	for idx := 0; idx < n; idx += d.Decimation {
		output[idx/d.Decimation] = (lower[idx] - d.csum[idx]) - (upper[idx] - lower[idx])
	}

	return
}

// Bit-value is determined by the sign of each sample after filtering.
func Quantize(input []float64, output []byte) {
	for idx, val := range input {
		output[idx] = byte(math.Float64bits(val)>>63) ^ 0x01
	}

	return
}

// Packs quantized signal into slices such that the first rank represents
// sample offsets and the second represents the value of each symbol from the
// given offset.
func (d Decoder) Pack(input []byte, slices [][]byte) {
	for symbolOffset, slice := range slices {
		for symbolIdx := range slice {
			slice[symbolIdx] = input[symbolIdx*d.DecCfg.SymbolLength2+symbolOffset]
		}
	}

	return
}

// For each sample offset look for the preamble. Return a list of indexes the
// preamble is found at. Indexes are absolute in the unsliced quantized
// buffer.
func (d Decoder) Search(slices [][]byte, preamble []byte) (indexes []int) {
	preambleLength := len(preamble)
	for symbolOffset, slice := range slices {
		for symbolIdx := range slice[:len(slice)-preambleLength] {
			if bytes.Equal(preamble, slice[symbolIdx:][:preambleLength]) {
				indexes = append(indexes, symbolIdx*d.DecCfg.SymbolLength2+symbolOffset)
			}
		}
	}

	return
}

// Given a list of indeces the preamble exists at, sample the appropriate bits
// of the signal's bit-decision. Pack bits of each index into an array of byte
// arrays and return.
func (d Decoder) Slice(indices []int) (pkts [][]byte) {
	// We will likely find multiple instances of the message so only keep
	// track of unique instances.
	seen := make(map[string]bool)

	// For each of the indices the preamble exists at.
	for _, qIdx := range indices {
		// Check that we're still within the first sample block. We'll catch
		// the message on the next sample block otherwise.
		if qIdx > d.DecCfg.BlockSize {
			continue
		}

		// Packet is 1 bit per byte, pack to 8-bits per byte.
		for pIdx := 0; pIdx < d.DecCfg.PacketSymbols; pIdx++ {
			d.pkt[pIdx>>3] <<= 1
			d.pkt[pIdx>>3] |= d.Quantized[qIdx+(pIdx*d.DecCfg.SymbolLength2)]
		}

		// Store the packet in the seen map and append to the packet list.
		pktStr := fmt.Sprintf("%02X", d.pkt)
		if !seen[pktStr] {
			seen[pktStr] = true
			pkts = append(pkts, make([]byte, len(d.pkt)))
			copy(pkts[len(pkts)-1], d.pkt)
		}
	}

	return
}

func NextPowerOf2(v int) int {
	return 1 << uint(math.Ceil(math.Log2(float64(v))))
}
<commit_msg>Remove config debugging statements.<commit_after>// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
// Copyright (C) 2015 Douglas Hall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package decode

import (
	"bytes"
	"fmt"
	"log"
	"math"
)

// PacketConfig specifies packet-specific radio configuration.
type PacketConfig struct {
	DataRate int

	BlockSize, BlockSize2       int
	SymbolLength, SymbolLength2 int
	SampleRate                  int

	PreambleSymbols, PacketSymbols int
	PreambleLength, PacketLength   int
	Preamble                       string

	BufferLength int

	CenterFreq uint32
}

func (cfg PacketConfig) Decimate(decimation int) PacketConfig {
	cfg.BlockSize /= decimation
	cfg.BlockSize2 /= decimation
	cfg.SymbolLength /= decimation
	cfg.SymbolLength2 /= decimation
	cfg.SampleRate /= decimation

	cfg.PreambleLength /= decimation
	cfg.PacketLength /= decimation

	cfg.BufferLength /= decimation

	return cfg
}

func (cfg PacketConfig) Log() {
	log.Println("BlockSize:", cfg.BlockSize)
	log.Println("CenterFreq:", cfg.CenterFreq)
	log.Println("SampleRate:", cfg.SampleRate)
	log.Println("DataRate:", cfg.DataRate)
	log.Println("SymbolLength:", cfg.SymbolLength)
	log.Println("PreambleSymbols:", cfg.PreambleSymbols)
	log.Println("PreambleLength:", cfg.PreambleLength)
	log.Println("PacketSymbols:", cfg.PacketSymbols)
	log.Println("PacketLength:", cfg.PacketLength)
	log.Println("Preamble:", cfg.Preamble)
}

// Decoder contains buffers and radio configuration.
type Decoder struct {
	Cfg PacketConfig

	Decimation int
	DecCfg     PacketConfig

	IQ        []byte
	Signal    []float64
	Filtered  []float64
	Quantized []byte

	csum  []float64
	demod Demodulator

	preamble []byte
	slices   [][]byte

	pkt []byte
}

// Create a new decoder with the given packet configuration.
func NewDecoder(cfg PacketConfig, decimation int, fastMag bool) (d Decoder) {
	d.Cfg = cfg

	d.Cfg.SymbolLength2 = d.Cfg.SymbolLength << 1
	d.Cfg.SampleRate = d.Cfg.DataRate * d.Cfg.SymbolLength

	d.Cfg.PreambleLength = d.Cfg.PreambleSymbols * d.Cfg.SymbolLength2
	d.Cfg.PacketLength = d.Cfg.PacketSymbols * d.Cfg.SymbolLength2

	d.Cfg.BlockSize = NextPowerOf2(d.Cfg.PreambleLength)
	d.Cfg.BlockSize2 = d.Cfg.BlockSize << 1

	d.Cfg.BufferLength = d.Cfg.PacketLength + d.Cfg.BlockSize

	d.Decimation = decimation
	d.DecCfg = d.Cfg.Decimate(d.Decimation)

	// Allocate necessary buffers.
	d.IQ = make([]byte, d.Cfg.BufferLength<<1)
	d.Signal = make([]float64, d.Cfg.BufferLength)
	d.Filtered = make([]float64, d.DecCfg.BufferLength)
	d.Quantized = make([]byte, d.DecCfg.BufferLength)

	d.csum = make([]float64, (d.Cfg.BlockSize + d.Cfg.SymbolLength2 + 1))

	// Calculate magnitude lookup table specified by -fastmag flag.
	if fastMag {
		d.demod = NewAlphaMaxBetaMinLUT()
	} else {
		d.demod = NewSqrtMagLUT()
	}

	// Pre-calculate a byte-slice version of the preamble for searching.
	d.preamble = make([]byte, len(d.Cfg.Preamble))
	for idx := range d.Cfg.Preamble {
		if d.Cfg.Preamble[idx] == '1' {
			d.preamble[idx] = 1
		}
	}

	// Slice quantized sample buffer to make searching for the preamble more
	// memory local. Pre-allocate a flat buffer so memory is contiguous and
	// assign slices to the buffer.
	d.slices = make([][]byte, d.DecCfg.SymbolLength2)
	flat := make([]byte, d.DecCfg.BlockSize2-(d.DecCfg.BlockSize2%d.DecCfg.SymbolLength2))

	symbolsPerBlock := d.DecCfg.BlockSize2 / d.DecCfg.SymbolLength2
	for symbolOffset := range d.slices {
		lower := symbolOffset * symbolsPerBlock
		upper := (symbolOffset + 1) * symbolsPerBlock
		d.slices[symbolOffset] = flat[lower:upper]
	}

	// Signal up to the final stage is 1-bit per byte. Allocate a buffer to
	// store packed version 8-bits per byte.
	d.pkt = make([]byte, (d.DecCfg.PacketSymbols+7)>>3)

	return
}

// Decode accepts a sample block and performs various DSP techniques to extract a packet.
func (d Decoder) Decode(input []byte) []int {
	// Shift buffers to append new block.
	copy(d.IQ, d.IQ[d.Cfg.BlockSize<<1:])
	copy(d.Signal, d.Signal[d.Cfg.BlockSize:])
	copy(d.Filtered, d.Filtered[d.DecCfg.BlockSize:])
	copy(d.Quantized, d.Quantized[d.DecCfg.BlockSize:])
	copy(d.IQ[d.Cfg.PacketLength<<1:], input[:])

	iqBlock := d.IQ[d.Cfg.PacketLength<<1:]
	signalBlock := d.Signal[d.Cfg.PacketLength:]

	// Compute the magnitude of the new block.
	d.demod.Execute(iqBlock, signalBlock)

	signalBlock = d.Signal[d.Cfg.PacketLength-d.Cfg.SymbolLength2:]
	filterBlock := d.Filtered[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:]

	// Perform matched filter on new block.
	d.Filter(signalBlock, filterBlock)

	// Perform bit-decision on new block.
	Quantize(filterBlock, d.Quantized[d.DecCfg.PacketLength-d.DecCfg.SymbolLength2:])

	// Pack the quantized signal into slices for searching.
	d.Pack(d.Quantized[:d.DecCfg.BlockSize2], d.slices)

	// Return a list of indexes the preamble exists at.
	return d.Search(d.slices, d.preamble)
}

// A Demodulator knows how to demodulate an array of uint8 IQ samples into an
// array of float64 samples.
type Demodulator interface {
	Execute([]byte, []float64)
}

// Default Magnitude Lookup Table
type MagLUT []float64

// Pre-computes normalized squares with most common DC offset for rtl-sdr dongles.
func NewSqrtMagLUT() (lut MagLUT) {
	lut = make([]float64, 0x100)
	for idx := range lut {
		lut[idx] = 127.4 - float64(idx)
		lut[idx] *= lut[idx]
	}
	return
}

// Calculates complex magnitude on given IQ stream writing result to output.
func (lut MagLUT) Execute(input []byte, output []float64) {
	for idx := 0; idx < len(input); idx += 2 {
		output[idx>>1] = math.Sqrt(lut[input[idx]] + lut[input[idx+1]])
	}
}

// Alpha*Max + Beta*Min Magnitude Approximation Lookup Table.
type AlphaMaxBetaMinLUT []float64

// Pre-computes absolute values with most common DC offset for rtl-sdr dongles.
func NewAlphaMaxBetaMinLUT() (lut AlphaMaxBetaMinLUT) {
	lut = make([]float64, 0x100)
	for idx := range lut {
		lut[idx] = math.Abs(127.4 - float64(idx))
	}
	return
}

// Calculates complex magnitude on given IQ stream writing result to output.
func (lut AlphaMaxBetaMinLUT) Execute(input []byte, output []float64) {
	const (
		α = 0.948059448969
		ß = 0.392699081699
	)

	for idx := 0; idx < len(input); idx += 2 {
		i := lut[input[idx]]
		q := lut[input[idx+1]]
		if i > q {
			output[idx>>1] = α*i + ß*q
		} else {
			output[idx>>1] = α*q + ß*i
		}
	}
}

// Matched filter for Manchester coded signals. Output signal's sign at each
// sample determines the bit-value since Manchester symbols have odd symmetry.
func (d Decoder) Filter(input, output []float64) {
	// Computing the cumulative summation over the signal simplifies
	// filtering to the difference of a pair of subtractions.
	var sum float64
	for idx, v := range input {
		sum += v
		d.csum[idx+1] = sum
	}

	// Filter result is difference of summation of lower and upper symbols.
	lower := d.csum[d.Cfg.SymbolLength:]
	upper := d.csum[d.Cfg.SymbolLength2:]
	n := len(input) - d.Cfg.SymbolLength2
	for idx := 0; idx < n; idx += d.Decimation {
		output[idx/d.Decimation] = (lower[idx] - d.csum[idx]) - (upper[idx] - lower[idx])
	}

	return
}

// Bit-value is determined by the sign of each sample after filtering.
func Quantize(input []float64, output []byte) {
	for idx, val := range input {
		output[idx] = byte(math.Float64bits(val)>>63) ^ 0x01
	}

	return
}

// Packs quantized signal into slices such that the first rank represents
// sample offsets and the second represents the value of each symbol from the
// given offset.
func (d Decoder) Pack(input []byte, slices [][]byte) {
	for symbolOffset, slice := range slices {
		for symbolIdx := range slice {
			slice[symbolIdx] = input[symbolIdx*d.DecCfg.SymbolLength2+symbolOffset]
		}
	}

	return
}

// For each sample offset look for the preamble. Return a list of indexes the
// preamble is found at. Indexes are absolute in the unsliced quantized
// buffer.
func (d Decoder) Search(slices [][]byte, preamble []byte) (indexes []int) {
	preambleLength := len(preamble)
	for symbolOffset, slice := range slices {
		for symbolIdx := range slice[:len(slice)-preambleLength] {
			if bytes.Equal(preamble, slice[symbolIdx:][:preambleLength]) {
				indexes = append(indexes, symbolIdx*d.DecCfg.SymbolLength2+symbolOffset)
			}
		}
	}

	return
}

// Given a list of indeces the preamble exists at, sample the appropriate bits
// of the signal's bit-decision. Pack bits of each index into an array of byte
// arrays and return.
func (d Decoder) Slice(indices []int) (pkts [][]byte) {
	// We will likely find multiple instances of the message so only keep
	// track of unique instances.
	seen := make(map[string]bool)

	// For each of the indices the preamble exists at.
	for _, qIdx := range indices {
		// Check that we're still within the first sample block. We'll catch
		// the message on the next sample block otherwise.
		if qIdx > d.DecCfg.BlockSize {
			continue
		}

		// Packet is 1 bit per byte, pack to 8-bits per byte.
		for pIdx := 0; pIdx < d.DecCfg.PacketSymbols; pIdx++ {
			d.pkt[pIdx>>3] <<= 1
			d.pkt[pIdx>>3] |= d.Quantized[qIdx+(pIdx*d.DecCfg.SymbolLength2)]
		}

		// Store the packet in the seen map and append to the packet list.
		pktStr := fmt.Sprintf("%02X", d.pkt)
		if !seen[pktStr] {
			seen[pktStr] = true
			pkts = append(pkts, make([]byte, len(d.pkt)))
			copy(pkts[len(pkts)-1], d.pkt)
		}
	}

	return
}

func NextPowerOf2(v int) int {
	return 1 << uint(math.Ceil(math.Log2(float64(v))))
}
<|endoftext|>


<commit_before>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/pkg/errors"
)

// SizeOptions can be passed to a list request to configure size and cursor location.
// These values will be defaulted if omitted.
//
// This should be swapped to ResultInfoCursors once the types are corrected.
type SizeOptions struct {
	Size   int  `json:"size,omitempty" url:"size,omitempty"`
	Before *int `json:"before,omitempty" url:"before,omitempty"`
	After  *int `json:"after,omitempty" url:"after,omitempty"`
}

// PagesDeploymentStageLogs represents the logs for a Pages deployment stage.
type PagesDeploymentStageLogs struct {
	Name      string                         `json:"name"`
	StartedOn *time.Time                     `json:"started_on"`
	EndedOn   *time.Time                     `json:"ended_on"`
	Status    string                         `json:"status"`
	Start     int                            `json:"start"`
	End       int                            `json:"end"`
	Total     int                            `json:"total"`
	Data      []PagesDeploymentStageLogEntry `json:"data"`
}

// PagesDeploymentStageLogEntry represents a single log entry in a Pages deployment stage.
type PagesDeploymentStageLogEntry struct {
	ID        int        `json:"id"`
	Timestamp *time.Time `json:"timestamp"`
	Message   string     `json:"message"`
}

// PagesDeploymentLogs represents the logs for a Pages deployment.
type PagesDeploymentLogs struct {
	Total                 int                       `json:"total"`
	IncludesContainerLogs bool                      `json:"includes_container_logs"`
	Data                  []PagesDeploymentLogEntry `json:"data"`
}

// PagesDeploymentLogEntry represents a single log entry in a Pages deployment.
type PagesDeploymentLogEntry struct {
	Timestamp *time.Time `json:"ts"`
	Line      string     `json:"line"`
}

type pagesDeploymentListResponse struct {
	Response
	Result     []PagesProjectDeployment `json:"result"`
	ResultInfo `json:"result_info"`
}

type pagesDeploymentResponse struct {
	Response
	Result PagesProjectDeployment `json:"result"`
}

type pagesDeploymentStageLogsResponse struct {
	Response
	Result     PagesDeploymentStageLogs `json:"result"`
	ResultInfo `json:"result_info"`
}

type pagesDeploymentLogsResponse struct {
	Response
	Result     PagesDeploymentLogs `json:"result"`
	ResultInfo `json:"result_info"`
}

type ListPagesDeploymentsParams struct {
	AccountID   string
	ProjectName string

	PaginationOptions
}

type GetPagesDeploymentInfoParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type GetPagesDeploymentStageLogsParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
	StageName    string

	SizeOptions
}

type GetPagesDeploymentLogsParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string

	SizeOptions
}

type DeletePagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type CreatePagesDeploymentParams struct {
	AccountID   string
	ProjectName string
}

type RetryPagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type RollbackPagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

var (
	ErrMissingProjectName  = errors.New("required missing project name")
	ErrMissingDeploymentID = errors.New("required missing deployment ID")
	ErrMissingStageName    = errors.New("required missing stage name")
)

// ListPagesDeployments returns all deployments for a Pages project.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployments
func (api *API) ListPagesDeployments(ctx context.Context, params ListPagesDeploymentsParams) ([]PagesProjectDeployment, ResultInfo, error) {
	if params.AccountID == "" {
		return []PagesProjectDeployment{}, ResultInfo{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return []PagesProjectDeployment{}, ResultInfo{}, ErrMissingProjectName
	}

	uri := buildURI(fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments", params.AccountID, params.ProjectName), params.PaginationOptions)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []PagesProjectDeployment{}, ResultInfo{}, err
	}
	var r pagesDeploymentListResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return []PagesProjectDeployment{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, r.ResultInfo, nil
}

// GetPagesDeploymentInfo returns a deployment for a Pages project.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-info
func (api *API) GetPagesDeploymentInfo(ctx context.Context, params GetPagesDeploymentInfoParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPagesDeploymentStageLogs returns the logs for a Pages deployment stage.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-stage-logs
//
// Deprecated: Use GetPagesDeploymentLogs instead.
func (api *API) GetPagesDeploymentStageLogs(ctx context.Context, params GetPagesDeploymentStageLogsParams) (PagesDeploymentStageLogs, error) {
	if params.AccountID == "" {
		return PagesDeploymentStageLogs{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingDeploymentID
	}

	if params.StageName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingStageName
	}

	uri := buildURI(
		fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/history/%s/logs", params.AccountID, params.ProjectName, params.DeploymentID, params.StageName),
		params.SizeOptions,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesDeploymentStageLogs{}, err
	}
	var r pagesDeploymentStageLogsResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesDeploymentStageLogs{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPagesDeploymentStageLogs returns the logs for a Pages deployment stage.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-stage-logs
func (api *API) GetPagesDeploymentLogs(ctx context.Context, params GetPagesDeploymentLogsParams) (PagesDeploymentLogs, error) {
	if params.AccountID == "" {
		return PagesDeploymentLogs{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesDeploymentLogs{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesDeploymentLogs{}, ErrMissingDeploymentID
	}

	uri := buildURI(
		fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/history/logs", params.AccountID, params.ProjectName, params.DeploymentID),
		params.SizeOptions,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesDeploymentLogs{}, err
	}
	var r pagesDeploymentLogsResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesDeploymentLogs{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// DeletePagesDeployment deletes a Pages deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-delete-deployment
func (api *API) DeletePagesDeployment(ctx context.Context, params DeletePagesDeploymentParams) error {
	if params.AccountID == "" {
		return ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s", params.AccountID, params.ProjectName, params.DeploymentID)

	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

// CreatePagesDeployment creates a Pages production deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-create-deployment
func (api *API) CreatePagesDeployment(ctx context.Context, params CreatePagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments", params.AccountID, params.ProjectName)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RetryPagesDeployment retries a specific Pages deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-retry-deployment
func (api *API) RetryPagesDeployment(ctx context.Context, params RetryPagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/retry", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RollbackPagesDeployment rollbacks the Pages production deployment to a previous production deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-rollback-deployment
func (api *API) RollbackPagesDeployment(ctx context.Context, params RollbackPagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/rollback", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}
<commit_msg>Update doc link<commit_after>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/pkg/errors"
)

// SizeOptions can be passed to a list request to configure size and cursor location.
// These values will be defaulted if omitted.
//
// This should be swapped to ResultInfoCursors once the types are corrected.
type SizeOptions struct {
	Size   int  `json:"size,omitempty" url:"size,omitempty"`
	Before *int `json:"before,omitempty" url:"before,omitempty"`
	After  *int `json:"after,omitempty" url:"after,omitempty"`
}

// PagesDeploymentStageLogs represents the logs for a Pages deployment stage.
type PagesDeploymentStageLogs struct {
	Name      string                         `json:"name"`
	StartedOn *time.Time                     `json:"started_on"`
	EndedOn   *time.Time                     `json:"ended_on"`
	Status    string                         `json:"status"`
	Start     int                            `json:"start"`
	End       int                            `json:"end"`
	Total     int                            `json:"total"`
	Data      []PagesDeploymentStageLogEntry `json:"data"`
}

// PagesDeploymentStageLogEntry represents a single log entry in a Pages deployment stage.
type PagesDeploymentStageLogEntry struct {
	ID        int        `json:"id"`
	Timestamp *time.Time `json:"timestamp"`
	Message   string     `json:"message"`
}

// PagesDeploymentLogs represents the logs for a Pages deployment.
type PagesDeploymentLogs struct {
	Total                 int                       `json:"total"`
	IncludesContainerLogs bool                      `json:"includes_container_logs"`
	Data                  []PagesDeploymentLogEntry `json:"data"`
}

// PagesDeploymentLogEntry represents a single log entry in a Pages deployment.
type PagesDeploymentLogEntry struct {
	Timestamp *time.Time `json:"ts"`
	Line      string     `json:"line"`
}

type pagesDeploymentListResponse struct {
	Response
	Result     []PagesProjectDeployment `json:"result"`
	ResultInfo `json:"result_info"`
}

type pagesDeploymentResponse struct {
	Response
	Result PagesProjectDeployment `json:"result"`
}

type pagesDeploymentStageLogsResponse struct {
	Response
	Result     PagesDeploymentStageLogs `json:"result"`
	ResultInfo `json:"result_info"`
}

type pagesDeploymentLogsResponse struct {
	Response
	Result     PagesDeploymentLogs `json:"result"`
	ResultInfo `json:"result_info"`
}

type ListPagesDeploymentsParams struct {
	AccountID   string
	ProjectName string

	PaginationOptions
}

type GetPagesDeploymentInfoParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type GetPagesDeploymentStageLogsParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
	StageName    string

	SizeOptions
}

type GetPagesDeploymentLogsParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string

	SizeOptions
}

type DeletePagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type CreatePagesDeploymentParams struct {
	AccountID   string
	ProjectName string
}

type RetryPagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

type RollbackPagesDeploymentParams struct {
	AccountID    string
	ProjectName  string
	DeploymentID string
}

var (
	ErrMissingProjectName  = errors.New("required missing project name")
	ErrMissingDeploymentID = errors.New("required missing deployment ID")
	ErrMissingStageName    = errors.New("required missing stage name")
)

// ListPagesDeployments returns all deployments for a Pages project.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployments
func (api *API) ListPagesDeployments(ctx context.Context, params ListPagesDeploymentsParams) ([]PagesProjectDeployment, ResultInfo, error) {
	if params.AccountID == "" {
		return []PagesProjectDeployment{}, ResultInfo{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return []PagesProjectDeployment{}, ResultInfo{}, ErrMissingProjectName
	}

	uri := buildURI(fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments", params.AccountID, params.ProjectName), params.PaginationOptions)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []PagesProjectDeployment{}, ResultInfo{}, err
	}
	var r pagesDeploymentListResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return []PagesProjectDeployment{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, r.ResultInfo, nil
}

// GetPagesDeploymentInfo returns a deployment for a Pages project.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-info
func (api *API) GetPagesDeploymentInfo(ctx context.Context, params GetPagesDeploymentInfoParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPagesDeploymentStageLogs returns the logs for a Pages deployment stage.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-stage-logs
//
// Deprecated: Use GetPagesDeploymentLogs instead.
func (api *API) GetPagesDeploymentStageLogs(ctx context.Context, params GetPagesDeploymentStageLogsParams) (PagesDeploymentStageLogs, error) {
	if params.AccountID == "" {
		return PagesDeploymentStageLogs{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingDeploymentID
	}

	if params.StageName == "" {
		return PagesDeploymentStageLogs{}, ErrMissingStageName
	}

	uri := buildURI(
		fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/history/%s/logs", params.AccountID, params.ProjectName, params.DeploymentID, params.StageName),
		params.SizeOptions,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesDeploymentStageLogs{}, err
	}
	var r pagesDeploymentStageLogsResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesDeploymentStageLogs{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPagesDeploymentStageLogs returns the logs for a Pages deployment stage.
//
// API reference: https://api.cloudflare.com/#pages-deployment-get-deployment-logs
func (api *API) GetPagesDeploymentLogs(ctx context.Context, params GetPagesDeploymentLogsParams) (PagesDeploymentLogs, error) {
	if params.AccountID == "" {
		return PagesDeploymentLogs{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesDeploymentLogs{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesDeploymentLogs{}, ErrMissingDeploymentID
	}

	uri := buildURI(
		fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/history/logs", params.AccountID, params.ProjectName, params.DeploymentID),
		params.SizeOptions,
	)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return PagesDeploymentLogs{}, err
	}
	var r pagesDeploymentLogsResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesDeploymentLogs{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// DeletePagesDeployment deletes a Pages deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-delete-deployment
func (api *API) DeletePagesDeployment(ctx context.Context, params DeletePagesDeploymentParams) error {
	if params.AccountID == "" {
		return ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s", params.AccountID, params.ProjectName, params.DeploymentID)

	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return err
	}
	return nil
}

// CreatePagesDeployment creates a Pages production deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-create-deployment
func (api *API) CreatePagesDeployment(ctx context.Context, params CreatePagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments", params.AccountID, params.ProjectName)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RetryPagesDeployment retries a specific Pages deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-retry-deployment
func (api *API) RetryPagesDeployment(ctx context.Context, params RetryPagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/retry", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RollbackPagesDeployment rollbacks the Pages production deployment to a previous production deployment.
//
// API reference: https://api.cloudflare.com/#pages-deployment-rollback-deployment
func (api *API) RollbackPagesDeployment(ctx context.Context, params RollbackPagesDeploymentParams) (PagesProjectDeployment, error) {
	if params.AccountID == "" {
		return PagesProjectDeployment{}, ErrMissingAccountID
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingProjectName
	}

	if params.ProjectName == "" {
		return PagesProjectDeployment{}, ErrMissingDeploymentID
	}

	uri := fmt.Sprintf("/accounts/%s/pages/projects/%s/deployments/%s/rollback", params.AccountID, params.ProjectName, params.DeploymentID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
	if err != nil {
		return PagesProjectDeployment{}, err
	}
	var r pagesDeploymentResponse
	err = json.Unmarshal(res, &r)
	if err != nil {
		return PagesProjectDeployment{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Holt and The Caddy 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 reverseproxy

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strconv"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	caddycmd "github.com/caddyserver/caddy/v2/cmd"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
	"github.com/caddyserver/caddy/v2/modules/caddytls"
)

func init() {
	caddycmd.RegisterCommand(caddycmd.Command{
		Name:  "reverse-proxy",
		Func:  cmdReverseProxy,
		Usage: "[--from <addr>] [--to <addr>] [--change-host-header]",
		Short: "A quick and production-ready reverse proxy",
		Long: `
A simple but production-ready reverse proxy. Useful for quick deployments,
demos, and development.

Simply shuttles HTTP(S) traffic from the --from address to the --to address.

Unless otherwise specified in the addresses, the --from address will be
assumed to be HTTPS if a hostname is given, and the --to address will be
assumed to be HTTP.

If the --from address has a host or IP, Caddy will attempt to serve the
proxy over HTTPS with a certificate (unless overridden by the HTTP scheme
or port).

If --change-host-header is set, the Host header on the request will be modified
from its original incoming value to the address of the upstream. (Otherwise, by
default, all incoming headers are passed through unmodified.)
`,
		Flags: func() *flag.FlagSet {
			fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError)
			fs.String("from", "localhost", "Address on which to receive traffic")
			fs.String("to", "", "Upstream address to which traffic should be sent")
			fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
			fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING SSL CERTIFICATES!)")
			fs.Bool("internal-certs", false, "Use internal CA for issuing certs")
			return fs
		}(),
	})
}

func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
	caddy.TrapSignals()

	from := fs.String("from")
	to := fs.String("to")
	changeHost := fs.Bool("change-host-header")
	insecure := fs.Bool("insecure")
	internalCerts := fs.Bool("internal-certs")

	httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
	httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)

	if to == "" {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
	}

	// set up the downstream address; assume missing information from given parts
	fromAddr, err := httpcaddyfile.ParseAddress(from)
	if err != nil {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid downstream address %s: %v", from, err)
	}
	if fromAddr.Path != "" {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", from)
	}
	if fromAddr.Scheme == "" {
		if fromAddr.Port == httpPort || fromAddr.Host == "" {
			fromAddr.Scheme = "http"
		} else {
			fromAddr.Scheme = "https"
		}
	}
	if fromAddr.Port == "" {
		if fromAddr.Scheme == "http" {
			fromAddr.Port = httpPort
		} else if fromAddr.Scheme == "https" {
			fromAddr.Port = httpsPort
		}
	}

	// set up the upstream address; assume missing information from given parts
	toAddr, toScheme, err := parseUpstreamDialAddress(to)
	if err != nil {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", to, err)
	}

	// proceed to build the handler and server
	ht := HTTPTransport{}
	if toScheme == "https" {
		ht.TLS = new(TLSConfig)
		if insecure {
			ht.TLS.InsecureSkipVerify = true
		}
	}

	handler := Handler{
		TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
		Upstreams:    UpstreamPool{{Dial: toAddr}},
	}

	if changeHost {
		handler.Headers = &headers.Handler{
			Request: &headers.HeaderOps{
				Set: http.Header{
					"Host": []string{"{http.reverse_proxy.upstream.hostport}"},
				},
			},
		}
	}

	route := caddyhttp.Route{
		HandlersRaw: []json.RawMessage{
			caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil),
		},
	}
	if fromAddr.Host != "" {
		route.MatcherSetsRaw = []caddy.ModuleMap{
			{
				"host": caddyconfig.JSON(caddyhttp.MatchHost{fromAddr.Host}, nil),
			},
		}
	}

	server := &caddyhttp.Server{
		Routes: caddyhttp.RouteList{route},
		Listen: []string{":" + fromAddr.Port},
	}

	httpApp := caddyhttp.App{
		Servers: map[string]*caddyhttp.Server{"proxy": server},
	}

	appsRaw := caddy.ModuleMap{
		"http": caddyconfig.JSON(httpApp, nil),
	}
	if internalCerts && fromAddr.Host != "" {
		tlsApp := caddytls.TLS{
			Automation: &caddytls.AutomationConfig{
				Policies: []*caddytls.AutomationPolicy{{
					Subjects:   []string{fromAddr.Host},
					IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
				}},
			},
		}
		appsRaw["tls"] = caddyconfig.JSON(tlsApp, nil)
	}

	var false bool
	cfg := &caddy.Config{
		Admin: &caddy.AdminConfig{Disabled: true,
			Config: &caddy.ConfigSettings{
				Persist: &false,
			},
		},
		AppsRaw: appsRaw,
	}

	err = caddy.Run(cfg)
	if err != nil {
		return caddy.ExitCodeFailedStartup, err
	}

	fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), toAddr)

	select {}
}
<commit_msg>reverseproxy: Support repeated --to flags in command (#4693)<commit_after>// Copyright 2015 Matthew Holt and The Caddy 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 reverseproxy

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strconv"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	caddycmd "github.com/caddyserver/caddy/v2/cmd"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
	"github.com/caddyserver/caddy/v2/modules/caddytls"
)

func init() {
	caddycmd.RegisterCommand(caddycmd.Command{
		Name:  "reverse-proxy",
		Func:  cmdReverseProxy,
		Usage: "[--from <addr>] [--to <addr>] [--change-host-header]",
		Short: "A quick and production-ready reverse proxy",
		Long: `
A simple but production-ready reverse proxy. Useful for quick deployments,
demos, and development.

Simply shuttles HTTP(S) traffic from the --from address to the --to address.
Multiple --to addresses may be specified by repeating the flag.

Unless otherwise specified in the addresses, the --from address will be
assumed to be HTTPS if a hostname is given, and the --to address will be
assumed to be HTTP.

If the --from address has a host or IP, Caddy will attempt to serve the
proxy over HTTPS with a certificate (unless overridden by the HTTP scheme
or port).

If --change-host-header is set, the Host header on the request will be modified
from its original incoming value to the address of the upstream. (Otherwise, by
default, all incoming headers are passed through unmodified.)
`,
		Flags: func() *flag.FlagSet {
			fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError)
			fs.String("from", "localhost", "Address on which to receive traffic")
			fs.Var(&reverseProxyCmdTo, "to", "Upstream address(es) to which traffic should be sent")
			fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
			fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING SSL CERTIFICATES!)")
			fs.Bool("internal-certs", false, "Use internal CA for issuing certs")
			return fs
		}(),
	})
}

func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
	caddy.TrapSignals()

	from := fs.String("from")
	changeHost := fs.Bool("change-host-header")
	insecure := fs.Bool("insecure")
	internalCerts := fs.Bool("internal-certs")

	httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
	httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)

	if len(reverseProxyCmdTo) == 0 {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
	}

	// set up the downstream address; assume missing information from given parts
	fromAddr, err := httpcaddyfile.ParseAddress(from)
	if err != nil {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid downstream address %s: %v", from, err)
	}
	if fromAddr.Path != "" {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", from)
	}
	if fromAddr.Scheme == "" {
		if fromAddr.Port == httpPort || fromAddr.Host == "" {
			fromAddr.Scheme = "http"
		} else {
			fromAddr.Scheme = "https"
		}
	}
	if fromAddr.Port == "" {
		if fromAddr.Scheme == "http" {
			fromAddr.Port = httpPort
		} else if fromAddr.Scheme == "https" {
			fromAddr.Port = httpsPort
		}
	}

	// set up the upstream address; assume missing information from given parts
	// mixing schemes isn't supported, so use first defined (if available)
	toAddresses := make([]string, len(reverseProxyCmdTo))
	var toScheme string
	for i, toLoc := range reverseProxyCmdTo {
		addr, scheme, err := parseUpstreamDialAddress(toLoc)
		if err != nil {
			return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err)
		}
		if scheme != "" && toScheme != "" {
			toScheme = scheme
		}
		toAddresses[i] = addr
	}

	// proceed to build the handler and server
	ht := HTTPTransport{}
	if toScheme == "https" {
		ht.TLS = new(TLSConfig)
		if insecure {
			ht.TLS.InsecureSkipVerify = true
		}
	}

	upstreamPool := UpstreamPool{}
	for _, toAddr := range toAddresses {
		upstreamPool = append(upstreamPool, &Upstream{
			Dial: toAddr,
		})
	}

	handler := Handler{
		TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
		Upstreams:    upstreamPool,
	}

	if changeHost {
		handler.Headers = &headers.Handler{
			Request: &headers.HeaderOps{
				Set: http.Header{
					"Host": []string{"{http.reverse_proxy.upstream.hostport}"},
				},
			},
		}
	}

	route := caddyhttp.Route{
		HandlersRaw: []json.RawMessage{
			caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil),
		},
	}
	if fromAddr.Host != "" {
		route.MatcherSetsRaw = []caddy.ModuleMap{
			{
				"host": caddyconfig.JSON(caddyhttp.MatchHost{fromAddr.Host}, nil),
			},
		}
	}

	server := &caddyhttp.Server{
		Routes: caddyhttp.RouteList{route},
		Listen: []string{":" + fromAddr.Port},
	}

	httpApp := caddyhttp.App{
		Servers: map[string]*caddyhttp.Server{"proxy": server},
	}

	appsRaw := caddy.ModuleMap{
		"http": caddyconfig.JSON(httpApp, nil),
	}
	if internalCerts && fromAddr.Host != "" {
		tlsApp := caddytls.TLS{
			Automation: &caddytls.AutomationConfig{
				Policies: []*caddytls.AutomationPolicy{{
					Subjects:   []string{fromAddr.Host},
					IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
				}},
			},
		}
		appsRaw["tls"] = caddyconfig.JSON(tlsApp, nil)
	}

	var false bool
	cfg := &caddy.Config{
		Admin: &caddy.AdminConfig{Disabled: true,
			Config: &caddy.ConfigSettings{
				Persist: &false,
			},
		},
		AppsRaw: appsRaw,
	}

	err = caddy.Run(cfg)
	if err != nil {
		return caddy.ExitCodeFailedStartup, err
	}

	for _, to := range toAddresses {
		fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), to)
	}
	if len(toAddresses) > 1 {
		fmt.Println("Load balancing policy: random")
	}

	select {}
}

// reverseProxyCmdTo holds the parsed values from repeated use of the --to flag.
var reverseProxyCmdTo caddycmd.StringSlice
<|endoftext|>