1
0
Fork 0
mirror of https://github.com/prometheus-operator/prometheus-operator.git synced 2025-04-21 11:48:53 +00:00

prometheus: Use hashstructure package for input checksum

Instead of marshalling all StatefulSet inputs to JSON and hashing that
string, use hashstructure [1] package. In addition do not generate new
rule config maps if underlying rules did not change.

[1] github.com/mitchellh/hashstructure
This commit is contained in:
Max Leonard Inden 2018-08-02 15:09:55 +02:00
parent 3d30a7caa1
commit 767c36bb2d
No known key found for this signature in database
GPG key ID: 5403C5464810BC26
21 changed files with 1166 additions and 177 deletions

View file

@ -271,7 +271,7 @@ PrometheusSpec is a specification of the desired behavior of the Prometheus clus
| externalUrl | The external URL the Prometheus instances will be available under. This is necessary to generate correct URLs. This is necessary if Prometheus is not served from root of a DNS name. | string | false |
| routePrefix | The route prefix Prometheus registers HTTP handlers for. This is useful, if using ExternalURL and a proxy is rewriting HTTP routes of a request, and the actual ExternalURL is still true, but the server serves requests under a different route prefix. For example for use with `kubectl proxy`. | string | false |
| storage | Storage spec to specify how storage shall be used. | *[StorageSpec](#storagespec) | false |
| ruleSelector | A selector to select which PrometheusRules to mount for loading alerting rules from. Until (excluding) Prometheus Operator v0.24.0 Prometheus Operator will migrate any legacy rule config maps to PrometheusRule custom resources selected by RuleSelector. Make sure it does not match any config maps that you do not want to be migrated. | *[metav1.LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#labelselector-v1-meta) | false |
| ruleSelector | A selector to select which PrometheusRules to mount for loading alerting rules from. Until (excluding) Prometheus Operator v0.24.0 Prometheus Operator will migrate any legacy rule ConfigMaps to PrometheusRule custom resources selected by RuleSelector. Make sure it does not match any config maps that you do not want to be migrated. | *[metav1.LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#labelselector-v1-meta) | false |
| ruleNamespaceSelector | Namespaces to be selected for PrometheusRules discovery. If unspecified, only the same namespace as the Prometheus object is in is used. | *[metav1.LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#labelselector-v1-meta) | false |
| alerting | Define details regarding alerting. | *[AlertingSpec](#alertingspec) | false |
| resources | Define resources requests and limits for single Pods. | [v1.ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#resourcerequirements-v1-core) | false |

9
Gopkg.lock generated
View file

@ -310,6 +310,14 @@
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:0de0f377aeccd41384e883c59c6f184c9db01c96db33a2724a1eaadd60f92629"
name = "github.com/mitchellh/hashstructure"
packages = ["."]
pruneopts = ""
revision = "2bca23e0e452137f789efbc8610126fd8b94f73b"
[[projects]]
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
name = "github.com/modern-go/concurrent"
@ -714,6 +722,7 @@
"github.com/hashicorp/go-version",
"github.com/improbable-eng/thanos/pkg/reloader",
"github.com/kylelemons/godebug/pretty",
"github.com/mitchellh/hashstructure",
"github.com/oklog/run",
"github.com/pkg/errors",
"github.com/prometheus/client_golang/prometheus",

View file

@ -63,3 +63,7 @@
[[override]]
name = "github.com/json-iterator/go"
revision = "f2b4162afba35581b6d4a50d3b8f34e33c144682"
[[constraint]]
branch = "master"
name = "github.com/mitchellh/hashstructure"

View file

@ -31,7 +31,7 @@ import (
)
func main() {
var ruleConfigMapName = flag.String("rule-config-map", "", "path to rule config map")
var ruleConfigMapName = flag.String("rule-config-map", "", "path to rule ConfigMap")
var ruleCRDSDestination = flag.String("rule-crds-destination", "", "destination new crds should be created in")
flag.Parse()
@ -67,7 +67,7 @@ func main() {
ruleFiles, err := prometheus.CMToRule(&configMap)
if err != nil {
log.Fatalf("failed to transform config map to rule file crds: %v", err.Error())
log.Fatalf("failed to transform ConfigMap to rule file crds: %v", err.Error())
}
for _, ruleFile := range ruleFiles {

View file

@ -1279,7 +1279,7 @@ func schema_pkg_client_monitoring_v1_PrometheusSpec(ref common.ReferenceCallback
},
"ruleSelector": {
SchemaProps: spec.SchemaProps{
Description: "A selector to select which PrometheusRules to mount for loading alerting rules from. Until (excluding) Prometheus Operator v0.24.0 Prometheus Operator will migrate any legacy rule config maps to PrometheusRule custom resources selected by RuleSelector. Make sure it does not match any config maps that you do not want to be migrated.",
Description: "A selector to select which PrometheusRules to mount for loading alerting rules from. Until (excluding) Prometheus Operator v0.24.0 Prometheus Operator will migrate any legacy rule ConfigMaps to PrometheusRule custom resources selected by RuleSelector. Make sure it does not match any config maps that you do not want to be migrated.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
},
},

View file

@ -102,7 +102,7 @@ type PrometheusSpec struct {
Storage *StorageSpec `json:"storage,omitempty"`
// A selector to select which PrometheusRules to mount for loading alerting
// rules from. Until (excluding) Prometheus Operator v0.24.0 Prometheus
// Operator will migrate any legacy rule config maps to PrometheusRule custom
// Operator will migrate any legacy rule ConfigMaps to PrometheusRule custom
// resources selected by RuleSelector. Make sure it does not match any config
// maps that you do not want to be migrated.
RuleSelector *metav1.LabelSelector `json:"ruleSelector,omitempty"`

View file

@ -16,8 +16,6 @@ package prometheus
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"reflect"
"strings"
@ -29,6 +27,7 @@ import (
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/mitchellh/hashstructure"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
appsv1 "k8s.io/api/apps/v1beta2"
@ -930,14 +929,14 @@ func (c *Operator) sync(key string) error {
return errors.Wrap(err, "retrieving statefulset failed")
}
newSSetInputChecksum, err := createSSetInputChecksum(*p, c.config, ruleConfigMapNames)
newSSetInputHash, err := createSSetInputHash(*p, c.config, ruleConfigMapNames)
if err != nil {
return err
}
if !exists {
level.Debug(c.logger).Log("msg", "no current Prometheus statefulset found")
sset, err := makeStatefulSet(*p, "", &c.config, ruleConfigMapNames, newSSetInputChecksum)
sset, err := makeStatefulSet(*p, "", &c.config, ruleConfigMapNames, newSSetInputHash)
if err != nil {
return errors.Wrap(err, "making statefulset failed")
}
@ -949,13 +948,13 @@ func (c *Operator) sync(key string) error {
return nil
}
oldSSetInputChecksum := obj.(*appsv1.StatefulSet).ObjectMeta.Annotations[sSetInputChecksumName]
if newSSetInputChecksum == oldSSetInputChecksum {
oldSSetInputHash := obj.(*appsv1.StatefulSet).ObjectMeta.Annotations[sSetInputHashName]
if newSSetInputHash == oldSSetInputHash {
level.Debug(c.logger).Log("msg", "new statefulset generation inputs match current, skipping any actions")
return nil
}
sset, err := makeStatefulSet(*p, obj.(*appsv1.StatefulSet).Spec.PodManagementPolicy, &c.config, ruleConfigMapNames, newSSetInputChecksum)
sset, err := makeStatefulSet(*p, obj.(*appsv1.StatefulSet).Spec.PodManagementPolicy, &c.config, ruleConfigMapNames, newSSetInputHash)
if err != nil {
return errors.Wrap(err, "making statefulset failed")
}
@ -968,19 +967,23 @@ func (c *Operator) sync(key string) error {
return nil
}
func createSSetInputChecksum(p monitoringv1.Prometheus, c Config, ruleConfigMapNames []string) (string, error) {
json, err := json.Marshal(
struct {
P monitoringv1.Prometheus
C Config
R []string
}{p, c, ruleConfigMapNames},
func createSSetInputHash(p monitoringv1.Prometheus, c Config, ruleConfigMapNames []string) (string, error) {
hash, err := hashstructure.Hash(struct {
P monitoringv1.Prometheus
C Config
R []string `hash:"set"`
}{p, c, ruleConfigMapNames},
nil,
)
if err != nil {
return "", errors.Wrap(err, "failed to marshal Prometheus CRD and config to json")
return "", errors.Wrap(
err,
"failed to calculate combined hash of Prometheus CRD, config and"+
" rule ConfigMap names",
)
}
return fmt.Sprintf("%x", md5.Sum(json)), nil
return fmt.Sprintf("%d", hash), nil
}
func ListOptions(name string) metav1.ListOptions {

View file

@ -29,23 +29,23 @@ func TestListOptions(t *testing.T) {
}
}
func TestCreateStatefulSetChecksum(t *testing.T) {
func TestCreateStatefulSetInputHash(t *testing.T) {
p1 := monitoringv1.Prometheus{}
p1.Spec.Version = "v1.7.0"
p2 := monitoringv1.Prometheus{}
p2.Spec.Version = "v1.7.2"
c := Config{}
p1Checksum, err := createSSetInputChecksum(p1, c, []string{})
p1Hash, err := createSSetInputHash(p1, c, []string{})
if err != nil {
t.Fatal(err)
}
p2Checksum, err := createSSetInputChecksum(p2, c, []string{})
p2Hash, err := createSSetInputHash(p2, c, []string{})
if err != nil {
t.Fatal(err)
}
if p1Checksum == p2Checksum {
t.Fatal("expected two different Prometheus CRDs to result in two different checksums but got equal checksums")
if p1Hash == p2Hash {
t.Fatal("expected two different Prometheus CRDs to result in two different hash but got equal hash")
}
}

View file

@ -105,10 +105,10 @@ func (c *Operator) getRuleCMs(ns string, cmLabelSelector *metav1.LabelSelector)
}
// If a user sets the RuleSelector property of a Prometheus custom resource
// to an empty object (`{}`), all config maps in that namespace will be
// selected for migration, including the Prometheus config map used to mount
// to an empty object (`{}`), all ConfigMaps in that namespace will be
// selected for migration, including the Prometheus ConfigMap used to mount
// all PrometheusRules into the Prometheus statefulset. The following filters
// this config map out.
// this ConfigMap out.
cmsNotGeneratedByPO := []*v1.ConfigMap{}
for _, cm := range configMaps {
migrate := true
@ -127,7 +127,7 @@ func (c *Operator) getRuleCMs(ns string, cmLabelSelector *metav1.LabelSelector)
return cmsNotGeneratedByPO, nil
}
// CMToRule takes a rule config map and transforms it to possibly multiple
// CMToRule takes a rule ConfigMap and transforms it to possibly multiple
// rule file crds. It is used in `cmd/po-rule-cm-to-rule-file-crds`. Thereby it
// needs to be public.
func CMToRule(cm *v1.ConfigMap) ([]monitoringv1.PrometheusRule, error) {

View file

@ -15,9 +15,8 @@
package prometheus
import (
"crypto/sha256"
"fmt"
"sort"
"reflect"
"strconv"
"strings"
@ -32,9 +31,9 @@ import (
"github.com/pkg/errors"
)
// The maximum `Data` size of a config map seems to differ between
// The maximum `Data` size of a ConfigMap seems to differ between
// environments. This is probably due to different meta data sizes which count
// into the overall maximum size of a config map. Thereby lets leave a
// into the overall maximum size of a ConfigMap. Thereby lets leave a
// large buffer.
var maxConfigMapDataSize = int(float64(v1.MaxSecretSize) * 0.5)
@ -46,27 +45,48 @@ func (c *Operator) createOrUpdateRuleConfigMaps(p *monitoringv1.Prometheus) ([]s
return nil, err
}
rules, err := c.selectRules(p, namespaces)
newRules, err := c.selectRules(p, namespaces)
if err != nil {
return nil, err
}
newConfigMaps, err := makeRulesConfigMaps(p, rules)
if err != nil {
return nil, errors.Wrap(err, "failed to make rules config maps")
}
newConfigMapNames := []string{}
for _, cm := range newConfigMaps {
newConfigMapNames = append(newConfigMapNames, cm.Name)
}
currentConfigMapList, err := cClient.List(prometheusRulesConfigMapSelector(p.Name))
if err != nil {
return nil, err
}
currentConfigMaps := currentConfigMapList.Items
currentRules := map[string]string{}
for _, cm := range currentConfigMaps {
for ruleFileName, ruleFile := range cm.Data {
currentRules[ruleFileName] = ruleFile
}
}
equal := reflect.DeepEqual(newRules, currentRules)
if equal && len(currentConfigMaps) != 0 {
level.Debug(c.logger).Log(
"msg", "no PrometheusRule changes",
"namespace", p.Namespace,
"prometheus", p.Name,
)
currentConfigMapNames := []string{}
for _, cm := range currentConfigMaps {
currentConfigMapNames = append(currentConfigMapNames, cm.Name)
}
return currentConfigMapNames, nil
}
newConfigMaps, err := makeRulesConfigMaps(p, newRules)
if err != nil {
return nil, errors.Wrap(err, "failed to make rules ConfigMaps")
}
newConfigMapNames := []string{}
for _, cm := range newConfigMaps {
newConfigMapNames = append(newConfigMapNames, cm.Name)
}
if len(currentConfigMaps) == 0 {
level.Debug(c.logger).Log(
"msg", "no PrometheusRule configmap found, creating new one",
@ -76,30 +96,18 @@ func (c *Operator) createOrUpdateRuleConfigMaps(p *monitoringv1.Prometheus) ([]s
for _, cm := range newConfigMaps {
_, err = cClient.Create(&cm)
if err != nil {
return nil, errors.Wrapf(err, "failed to create config map '%v'", cm.Name)
return nil, errors.Wrapf(err, "failed to create ConfigMap '%v'", cm.Name)
}
}
return newConfigMapNames, nil
}
newChecksum := checksumConfigMaps(newConfigMaps)
currentChecksum := checksumConfigMaps(currentConfigMaps)
if newChecksum == currentChecksum {
level.Debug(c.logger).Log(
"msg", "no PrometheusRule changes",
"namespace", p.Namespace,
"prometheus", p.Name,
)
return newConfigMapNames, nil
}
// Simply deleting old config maps and creating new ones for now. Could be
// replaced by logic that only deletes obsolete config maps in the future.
// Simply deleting old ConfigMaps and creating new ones for now. Could be
// replaced by logic that only deletes obsolete ConfigMaps in the future.
for _, cm := range currentConfigMaps {
err := cClient.Delete(cm.Name, &metav1.DeleteOptions{})
if err != nil {
return nil, errors.Wrapf(err, "failed to delete current config map '%v'", cm.Name)
return nil, errors.Wrapf(err, "failed to delete current ConfigMap '%v'", cm.Name)
}
}
@ -111,7 +119,7 @@ func (c *Operator) createOrUpdateRuleConfigMaps(p *monitoringv1.Prometheus) ([]s
for _, cm := range newConfigMaps {
_, err = cClient.Create(&cm)
if err != nil {
return nil, errors.Wrapf(err, "failed to create new config map '%v'", cm.Name)
return nil, errors.Wrapf(err, "failed to create new ConfigMap '%v'", cm.Name)
}
}
@ -191,30 +199,20 @@ func (c *Operator) selectRules(p *monitoringv1.Prometheus, namespaces []string)
return rules, nil
}
func sortKeyesOfStringMap(m map[string]string) []string {
keys := []string{}
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// makeRulesConfigMaps takes a Prometheus configuration and rule files and
// returns a list of Kubernetes config maps to be later on mounted into the
// returns a list of Kubernetes ConfigMaps to be later on mounted into the
// Prometheus instance.
// If the total size of rule files exceeds the Kubernetes config map limit,
// If the total size of rule files exceeds the Kubernetes ConfigMap limit,
// they are split up via the simple first-fit [1] bin packing algorithm. In the
// future this can be replaced by a more sophisticated algorithm, but for now
// simplicity should be sufficient.
// [1] https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
func makeRulesConfigMaps(p *monitoringv1.Prometheus, ruleFiles map[string]string) ([]v1.ConfigMap, error) {
//check if none of the rule files is too large for a single config map
//check if none of the rule files is too large for a single ConfigMap
for filename, file := range ruleFiles {
if len(file) > maxConfigMapDataSize {
return nil, errors.Errorf(
"rule file '%v' is too large for a single Kubernetes config map",
"rule file '%v' is too large for a single Kubernetes ConfigMap",
filename,
)
}
@ -224,15 +222,14 @@ func makeRulesConfigMaps(p *monitoringv1.Prometheus, ruleFiles map[string]string
{},
}
currBucketIndex := 0
sortedNames := sortKeyesOfStringMap(ruleFiles)
for _, filename := range sortedNames {
for filename, filecontent := range ruleFiles {
// If rule file doesn't fit into current bucket, create new bucket
if bucketSize(buckets[currBucketIndex])+len(ruleFiles[filename]) > maxConfigMapDataSize {
if bucketSize(buckets[currBucketIndex])+len(filecontent) > maxConfigMapDataSize {
buckets = append(buckets, map[string]string{})
currBucketIndex++
}
buckets[currBucketIndex][filename] = ruleFiles[filename]
buckets[currBucketIndex][filename] = filecontent
}
ruleFileConfigMaps := []v1.ConfigMap{}
@ -281,24 +278,6 @@ func makeRulesConfigMap(p *monitoringv1.Prometheus, ruleFiles map[string]string)
}
}
func checksumConfigMaps(configMaps []v1.ConfigMap) string {
ruleFiles := map[string]string{}
for _, cm := range configMaps {
for filename, file := range cm.Data {
ruleFiles[filename] = file
}
}
sortedKeys := sortKeyesOfStringMap(ruleFiles)
sum := ""
for _, name := range sortedKeys {
sum += name + ruleFiles[name]
}
return fmt.Sprintf("%x", sha256.Sum256([]byte(sum)))
}
func prometheusRuleConfigMapName(prometheusName string) string {
return "prometheus-" + prometheusName + "-rulefiles"
}

View file

@ -29,7 +29,7 @@ func TestMakeRulesConfigMaps(t *testing.T) {
t.Run("ShouldSplitUpLargeSmallIntoTwo", shouldSplitUpLargeSmallIntoTwo)
}
// makeRulesConfigMaps should return at least one config map even if it is empty
// makeRulesConfigMaps should return at least one ConfigMap even if it is empty
// when there are no rules. Otherwise adding a rule to a Prometheus without rules
// would change the statefulset definition and thereby force Prometheus to
// restart.
@ -43,12 +43,12 @@ func shouldReturnAtLeastOneConfigMap(t *testing.T) {
}
if len(configMaps) != 1 {
t.Fatalf("expected one config maps but got %v", len(configMaps))
t.Fatalf("expected one ConfigMaps but got %v", len(configMaps))
}
}
func shouldErrorOnTooLargeRuleFile(t *testing.T) {
expectedError := "rule file 'my-rule-file' is too large for a single Kubernetes config map"
expectedError := "rule file 'my-rule-file' is too large for a single Kubernetes ConfigMap"
p := &monitoringv1.Prometheus{}
ruleFiles := map[string]string{}
@ -73,46 +73,11 @@ func shouldSplitUpLargeSmallIntoTwo(t *testing.T) {
}
if len(configMaps) != 2 {
t.Fatalf("expected rule files to be split up into two config maps, but got '%v' instead", len(configMaps))
t.Fatalf("expected rule files to be split up into two ConfigMaps, but got '%v' instead", len(configMaps))
}
if configMaps[0].Data["my-rule-file-1"] != ruleFiles["my-rule-file-1"] &&
configMaps[1].Data["my-rule-file-2"] != ruleFiles["my-rule-file-2"] {
t.Fatal("expected config map data to match rule file content")
}
}
func TestChecksumConfigMaps(t *testing.T) {
configMapsAsc := []v1.ConfigMap{
{
Data: map[string]string{
"key1a": "value1a",
"key1b": "value1b",
},
},
{
Data: map[string]string{
"key2a": "value2a",
"key2b": "value2b",
},
},
}
configMapsDesc := []v1.ConfigMap{
{
Data: map[string]string{
"key2b": "value2b",
"key2a": "value2a",
},
},
{
Data: map[string]string{
"key1b": "value1b",
"key1a": "value1a",
},
},
}
if checksumConfigMaps(configMapsAsc) != checksumConfigMaps(configMapsDesc) {
t.Fatal("expected two config map slices with the same keys and values in different order to have same checksum")
t.Fatal("expected ConfigMap data to match rule file content")
}
}

View file

@ -43,7 +43,7 @@ const (
secretsDir = "/etc/prometheus/secrets/"
configFilename = "prometheus.yaml"
configEnvsubstFilename = "prometheus.env.yaml"
sSetInputChecksumName = "prometheus-operator-input-checksum"
sSetInputHashName = "prometheus-operator-input-hash"
)
var (
@ -82,7 +82,7 @@ func makeStatefulSet(
previousPodManagementPolicy appsv1.PodManagementPolicyType,
config *Config,
ruleConfigMapNames []string,
inputChecksum string,
inputHash string,
) (*appsv1.StatefulSet, error) {
// TODO(fabxc): is this the right point to inject defaults?
// Ideally we would do it before storing but that's currently not possible.
@ -162,10 +162,10 @@ func makeStatefulSet(
if statefulset.ObjectMeta.Annotations == nil {
statefulset.ObjectMeta.Annotations = map[string]string{
sSetInputChecksumName: inputChecksum,
sSetInputHashName: inputHash,
}
} else {
statefulset.ObjectMeta.Annotations[sSetInputChecksumName] = inputChecksum
statefulset.ObjectMeta.Annotations[sSetInputHashName] = inputHash
}
if p.Spec.ImagePullSecrets != nil && len(p.Spec.ImagePullSecrets) > 0 {
@ -215,27 +215,6 @@ func makeEmptyConfigurationSecret(p *monitoringv1.Prometheus, config Config) (*v
return s, nil
}
type ConfigMapReference struct {
Key string `json:"key"`
Checksum string `json:"checksum"`
}
type ConfigMapReferenceList struct {
Items []*ConfigMapReference `json:"items"`
}
func (l *ConfigMapReferenceList) Len() int {
return len(l.Items)
}
func (l *ConfigMapReferenceList) Less(i, j int) bool {
return l.Items[i].Key < l.Items[j].Key
}
func (l *ConfigMapReferenceList) Swap(i, j int) {
l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
}
func makeConfigSecret(p *monitoringv1.Prometheus, config Config) *v1.Secret {
boolTrue := true
return &v1.Secret{

View file

@ -16,7 +16,6 @@ package e2e
import (
"fmt"
"log"
"strconv"
"strings"
"testing"
@ -277,12 +276,9 @@ receivers:
}
firstExpectedString := "firstConfigWebHook"
log.Println("waiting for first expected config")
if err := framework.WaitForAlertmanagerConfigToContainString(ns, alertmanager.Name, firstExpectedString); err != nil {
t.Fatal(err)
t.Fatal(errors.Wrap(err, "failed to wait for first expected config"))
}
log.Println("first expected config found")
cfg.Data["alertmanager.yaml"] = []byte(secondConfig)
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Update(cfg); err != nil {
@ -291,11 +287,9 @@ receivers:
secondExpectedString := "secondConfigWebHook"
log.Println("waiting for second expected config")
if err := framework.WaitForAlertmanagerConfigToContainString(ns, alertmanager.Name, secondExpectedString); err != nil {
t.Fatal(err)
t.Fatal(errors.Wrap(err, "failed to wait for second expected config"))
}
log.Println("second expected config found")
}
func TestAlertmanagerZeroDowntimeRollingDeployment(t *testing.T) {

View file

@ -501,7 +501,7 @@ groups:
},
}
if _, err := framework.KubeClient.CoreV1().ConfigMaps(ns).Create(&cm); err != nil {
t.Fatalf("failed to create legacy rule config map: %v", err.Error())
t.Fatalf("failed to create legacy rule ConfigMap: %v", err.Error())
}
p := framework.MakeBasicPrometheus(ns, name, name, 1)
@ -522,7 +522,7 @@ groups:
}
if err := framework.WaitForRule(ns, cm.Name+"-"+ruleFileName); err != nil {
t.Fatalf("waiting for rule config map to be converted to rule file crd: %v", err)
t.Fatalf("waiting for rule ConfigMap to be converted to rule file crd: %v", err)
}
if err := framework.WaitForPrometheusFiringAlert(ns, pSVC.Name, alertName); err != nil {
@ -702,7 +702,7 @@ func TestPrometheusRulesExceedingConfigMapLimit(t *testing.T) {
}
// generateHugePrometheusRule returns a Prometheus rule instance that would fill
// more than half of the space of a Kubernetes config map.
// more than half of the space of a Kubernetes ConfigMap.
func generateHugePrometheusRule(ns, identifier string) monitoringv1.PrometheusRule {
alertName := "my-alert"
groups := []monitoringv1.RuleGroup{
@ -767,7 +767,7 @@ func TestPrometheusOnlyUpdatedOnRelevantChanges(t *testing.T) {
ConfigMaps(ns).
Get("prometheus-"+prometheusName+"-rulefiles-0", metav1.GetOptions{})
},
// The Prometheus Operator first creates the config map for the
// The Prometheus Operator first creates the ConfigMap for the
// given Prometheus stateful set and then updates it with the matching
// Prometheus rules.
MaxExpectedChanges: 2,

View file

@ -44,7 +44,7 @@ func (f *Framework) WaitForConfigMapExist(ns, name string) (*v1.ConfigMap, error
return true, nil
})
return configMap, errors.Wrapf(err, "waiting for config map '%v' in namespace '%v'", name, ns)
return configMap, errors.Wrapf(err, "waiting for ConfigMap '%v' in namespace '%v'", name, ns)
}
func (f *Framework) WaitForConfigMapNotExist(ns, name string) error {
@ -65,5 +65,5 @@ func (f *Framework) WaitForConfigMapNotExist(ns, name string) error {
return false, nil
})
return errors.Wrapf(err, "waiting for config map '%v' in namespace '%v' to not exist", name, ns)
return errors.Wrapf(err, "waiting for ConfigMap '%v' in namespace '%v' to not exist", name, ns)
}

21
vendor/github.com/mitchellh/hashstructure/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

65
vendor/github.com/mitchellh/hashstructure/README.md generated vendored Normal file
View file

@ -0,0 +1,65 @@
# hashstructure [![GoDoc](https://godoc.org/github.com/mitchellh/hashstructure?status.svg)](https://godoc.org/github.com/mitchellh/hashstructure)
hashstructure is a Go library for creating a unique hash value
for arbitrary values in Go.
This can be used to key values in a hash (for use in a map, set, etc.)
that are complex. The most common use case is comparing two values without
sending data across the network, caching values locally (de-dup), and so on.
## Features
* Hash any arbitrary Go value, including complex types.
* Tag a struct field to ignore it and not affect the hash value.
* Tag a slice type struct field to treat it as a set where ordering
doesn't affect the hash code but the field itself is still taken into
account to create the hash value.
* Optionally specify a custom hash function to optimize for speed, collision
avoidance for your data set, etc.
* Optionally hash the output of `.String()` on structs that implement fmt.Stringer,
allowing effective hashing of time.Time
## Installation
Standard `go get`:
```
$ go get github.com/mitchellh/hashstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/hashstructure).
A quick code example is shown below:
```go
type ComplexStruct struct {
Name string
Age uint
Metadata map[string]interface{}
}
v := ComplexStruct{
Name: "mitchellh",
Age: 64,
Metadata: map[string]interface{}{
"car": true,
"location": "California",
"siblings": []string{"Bob", "John"},
},
}
hash, err := hashstructure.Hash(v, nil)
if err != nil {
panic(err)
}
fmt.Printf("%d", hash)
// Output:
// 2307517237273902113
```

View file

@ -0,0 +1,358 @@
package hashstructure
import (
"encoding/binary"
"fmt"
"hash"
"hash/fnv"
"reflect"
)
// ErrNotStringer is returned when there's an error with hash:"string"
type ErrNotStringer struct {
Field string
}
// Error implements error for ErrNotStringer
func (ens *ErrNotStringer) Error() string {
return fmt.Sprintf("hashstructure: %s has hash:\"string\" set, but does not implement fmt.Stringer", ens.Field)
}
// HashOptions are options that are available for hashing.
type HashOptions struct {
// Hasher is the hash function to use. If this isn't set, it will
// default to FNV.
Hasher hash.Hash64
// TagName is the struct tag to look at when hashing the structure.
// By default this is "hash".
TagName string
// ZeroNil is flag determining if nil pointer should be treated equal
// to a zero value of pointed type. By default this is false.
ZeroNil bool
}
// Hash returns the hash value of an arbitrary value.
//
// If opts is nil, then default options will be used. See HashOptions
// for the default values. The same *HashOptions value cannot be used
// concurrently. None of the values within a *HashOptions struct are
// safe to read/write while hashing is being done.
//
// Notes on the value:
//
// * Unexported fields on structs are ignored and do not affect the
// hash value.
//
// * Adding an exported field to a struct with the zero value will change
// the hash value.
//
// For structs, the hashing can be controlled using tags. For example:
//
// struct {
// Name string
// UUID string `hash:"ignore"`
// }
//
// The available tag values are:
//
// * "ignore" or "-" - The field will be ignored and not affect the hash code.
//
// * "set" - The field will be treated as a set, where ordering doesn't
// affect the hash code. This only works for slices.
//
// * "string" - The field will be hashed as a string, only works when the
// field implements fmt.Stringer
//
func Hash(v interface{}, opts *HashOptions) (uint64, error) {
// Create default options
if opts == nil {
opts = &HashOptions{}
}
if opts.Hasher == nil {
opts.Hasher = fnv.New64()
}
if opts.TagName == "" {
opts.TagName = "hash"
}
// Reset the hash
opts.Hasher.Reset()
// Create our walker and walk the structure
w := &walker{
h: opts.Hasher,
tag: opts.TagName,
zeronil: opts.ZeroNil,
}
return w.visit(reflect.ValueOf(v), nil)
}
type walker struct {
h hash.Hash64
tag string
zeronil bool
}
type visitOpts struct {
// Flags are a bitmask of flags to affect behavior of this visit
Flags visitFlag
// Information about the struct containing this field
Struct interface{}
StructField string
}
func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
t := reflect.TypeOf(0)
// Loop since these can be wrapped in multiple layers of pointers
// and interfaces.
for {
// If we have an interface, dereference it. We have to do this up
// here because it might be a nil in there and the check below must
// catch that.
if v.Kind() == reflect.Interface {
v = v.Elem()
continue
}
if v.Kind() == reflect.Ptr {
if w.zeronil {
t = v.Type().Elem()
}
v = reflect.Indirect(v)
continue
}
break
}
// If it is nil, treat it like a zero.
if !v.IsValid() {
v = reflect.Zero(t)
}
// Binary writing can use raw ints, we have to convert to
// a sized-int, we'll choose the largest...
switch v.Kind() {
case reflect.Int:
v = reflect.ValueOf(int64(v.Int()))
case reflect.Uint:
v = reflect.ValueOf(uint64(v.Uint()))
case reflect.Bool:
var tmp int8
if v.Bool() {
tmp = 1
}
v = reflect.ValueOf(tmp)
}
k := v.Kind()
// We can shortcut numeric values by directly binary writing them
if k >= reflect.Int && k <= reflect.Complex64 {
// A direct hash calculation
w.h.Reset()
err := binary.Write(w.h, binary.LittleEndian, v.Interface())
return w.h.Sum64(), err
}
switch k {
case reflect.Array:
var h uint64
l := v.Len()
for i := 0; i < l; i++ {
current, err := w.visit(v.Index(i), nil)
if err != nil {
return 0, err
}
h = hashUpdateOrdered(w.h, h, current)
}
return h, nil
case reflect.Map:
var includeMap IncludableMap
if opts != nil && opts.Struct != nil {
if v, ok := opts.Struct.(IncludableMap); ok {
includeMap = v
}
}
// Build the hash for the map. We do this by XOR-ing all the key
// and value hashes. This makes it deterministic despite ordering.
var h uint64
for _, k := range v.MapKeys() {
v := v.MapIndex(k)
if includeMap != nil {
incl, err := includeMap.HashIncludeMap(
opts.StructField, k.Interface(), v.Interface())
if err != nil {
return 0, err
}
if !incl {
continue
}
}
kh, err := w.visit(k, nil)
if err != nil {
return 0, err
}
vh, err := w.visit(v, nil)
if err != nil {
return 0, err
}
fieldHash := hashUpdateOrdered(w.h, kh, vh)
h = hashUpdateUnordered(h, fieldHash)
}
return h, nil
case reflect.Struct:
parent := v.Interface()
var include Includable
if impl, ok := parent.(Includable); ok {
include = impl
}
t := v.Type()
h, err := w.visit(reflect.ValueOf(t.Name()), nil)
if err != nil {
return 0, err
}
l := v.NumField()
for i := 0; i < l; i++ {
if innerV := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
var f visitFlag
fieldType := t.Field(i)
if fieldType.PkgPath != "" {
// Unexported
continue
}
tag := fieldType.Tag.Get(w.tag)
if tag == "ignore" || tag == "-" {
// Ignore this field
continue
}
// if string is set, use the string value
if tag == "string" {
if impl, ok := innerV.Interface().(fmt.Stringer); ok {
innerV = reflect.ValueOf(impl.String())
} else {
return 0, &ErrNotStringer{
Field: v.Type().Field(i).Name,
}
}
}
// Check if we implement includable and check it
if include != nil {
incl, err := include.HashInclude(fieldType.Name, innerV)
if err != nil {
return 0, err
}
if !incl {
continue
}
}
switch tag {
case "set":
f |= visitFlagSet
}
kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil)
if err != nil {
return 0, err
}
vh, err := w.visit(innerV, &visitOpts{
Flags: f,
Struct: parent,
StructField: fieldType.Name,
})
if err != nil {
return 0, err
}
fieldHash := hashUpdateOrdered(w.h, kh, vh)
h = hashUpdateUnordered(h, fieldHash)
}
}
return h, nil
case reflect.Slice:
// We have two behaviors here. If it isn't a set, then we just
// visit all the elements. If it is a set, then we do a deterministic
// hash code.
var h uint64
var set bool
if opts != nil {
set = (opts.Flags & visitFlagSet) != 0
}
l := v.Len()
for i := 0; i < l; i++ {
current, err := w.visit(v.Index(i), nil)
if err != nil {
return 0, err
}
if set {
h = hashUpdateUnordered(h, current)
} else {
h = hashUpdateOrdered(w.h, h, current)
}
}
return h, nil
case reflect.String:
// Directly hash
w.h.Reset()
_, err := w.h.Write([]byte(v.String()))
return w.h.Sum64(), err
default:
return 0, fmt.Errorf("unknown kind to hash: %s", k)
}
}
func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {
// For ordered updates, use a real hash function
h.Reset()
// We just panic if the binary writes fail because we are writing
// an int64 which should never be fail-able.
e1 := binary.Write(h, binary.LittleEndian, a)
e2 := binary.Write(h, binary.LittleEndian, b)
if e1 != nil {
panic(e1)
}
if e2 != nil {
panic(e2)
}
return h.Sum64()
}
func hashUpdateUnordered(a, b uint64) uint64 {
return a ^ b
}
// visitFlag is used as a bitmask for affecting visit behavior
type visitFlag uint
const (
visitFlagInvalid visitFlag = iota
visitFlagSet = iota << 1
)

View file

@ -0,0 +1,32 @@
package hashstructure
import (
"fmt"
)
func ExampleHash() {
type ComplexStruct struct {
Name string
Age uint
Metadata map[string]interface{}
}
v := ComplexStruct{
Name: "mitchellh",
Age: 64,
Metadata: map[string]interface{}{
"car": true,
"location": "California",
"siblings": []string{"Bob", "John"},
},
}
hash, err := Hash(v, nil)
if err != nil {
panic(err)
}
fmt.Printf("%d", hash)
// Output:
// 6691276962590150517
}

View file

@ -0,0 +1,565 @@
package hashstructure
import (
"fmt"
"testing"
"time"
)
func TestHash_identity(t *testing.T) {
cases := []interface{}{
nil,
"foo",
42,
true,
false,
[]string{"foo", "bar"},
[]interface{}{1, nil, "foo"},
map[string]string{"foo": "bar"},
map[interface{}]string{"foo": "bar"},
map[interface{}]interface{}{"foo": "bar", "bar": 0},
struct {
Foo string
Bar []interface{}
}{
Foo: "foo",
Bar: []interface{}{nil, nil, nil},
},
&struct {
Foo string
Bar []interface{}
}{
Foo: "foo",
Bar: []interface{}{nil, nil, nil},
},
}
for _, tc := range cases {
// We run the test 100 times to try to tease out variability
// in the runtime in terms of ordering.
valuelist := make([]uint64, 100)
for i, _ := range valuelist {
v, err := Hash(tc, nil)
if err != nil {
t.Fatalf("Error: %s\n\n%#v", err, tc)
}
valuelist[i] = v
}
// Zero is always wrong
if valuelist[0] == 0 {
t.Fatalf("zero hash: %#v", tc)
}
// Make sure all the values match
t.Logf("%#v: %d", tc, valuelist[0])
for i := 1; i < len(valuelist); i++ {
if valuelist[i] != valuelist[0] {
t.Fatalf("non-matching: %d, %d\n\n%#v", i, 0, tc)
}
}
}
}
func TestHash_equal(t *testing.T) {
type testFoo struct{ Name string }
type testBar struct{ Name string }
cases := []struct {
One, Two interface{}
Match bool
}{
{
map[string]string{"foo": "bar"},
map[interface{}]string{"foo": "bar"},
true,
},
{
map[string]interface{}{"1": "1"},
map[string]interface{}{"1": "1", "2": "2"},
false,
},
{
struct{ Fname, Lname string }{"foo", "bar"},
struct{ Fname, Lname string }{"bar", "foo"},
false,
},
{
struct{ Lname, Fname string }{"foo", "bar"},
struct{ Fname, Lname string }{"foo", "bar"},
false,
},
{
struct{ Lname, Fname string }{"foo", "bar"},
struct{ Fname, Lname string }{"bar", "foo"},
true,
},
{
testFoo{"foo"},
testBar{"foo"},
false,
},
{
struct {
Foo string
unexported string
}{
Foo: "bar",
unexported: "baz",
},
struct {
Foo string
unexported string
}{
Foo: "bar",
unexported: "bang",
},
true,
},
{
struct {
testFoo
Foo string
}{
Foo: "bar",
testFoo: testFoo{Name: "baz"},
},
struct {
testFoo
Foo string
}{
Foo: "bar",
},
true,
},
{
struct {
Foo string
}{
Foo: "bar",
},
struct {
testFoo
Foo string
}{
Foo: "bar",
},
true,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
t.Logf("Hashing: %#v", tc.One)
one, err := Hash(tc.One, nil)
t.Logf("Result: %d", one)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
t.Logf("Hashing: %#v", tc.Two)
two, err := Hash(tc.Two, nil)
t.Logf("Result: %d", two)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
})
}
}
func TestHash_equalIgnore(t *testing.T) {
type Test1 struct {
Name string
UUID string `hash:"ignore"`
}
type Test2 struct {
Name string
UUID string `hash:"-"`
}
type TestTime struct {
Name string
Time time.Time `hash:"string"`
}
type TestTime2 struct {
Name string
Time time.Time
}
now := time.Now()
cases := []struct {
One, Two interface{}
Match bool
}{
{
Test1{Name: "foo", UUID: "foo"},
Test1{Name: "foo", UUID: "bar"},
true,
},
{
Test1{Name: "foo", UUID: "foo"},
Test1{Name: "foo", UUID: "foo"},
true,
},
{
Test2{Name: "foo", UUID: "foo"},
Test2{Name: "foo", UUID: "bar"},
true,
},
{
Test2{Name: "foo", UUID: "foo"},
Test2{Name: "foo", UUID: "foo"},
true,
},
{
TestTime{Name: "foo", Time: now},
TestTime{Name: "foo", Time: time.Time{}},
false,
},
{
TestTime{Name: "foo", Time: now},
TestTime{Name: "foo", Time: now},
true,
},
{
TestTime2{Name: "foo", Time: now},
TestTime2{Name: "foo", Time: time.Time{}},
true,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_stringTagError(t *testing.T) {
type Test1 struct {
Name string
BrokenField string `hash:"string"`
}
type Test2 struct {
Name string
BustedField int `hash:"string"`
}
type Test3 struct {
Name string
Time time.Time `hash:"string"`
}
cases := []struct {
Test interface{}
Field string
}{
{
Test1{Name: "foo", BrokenField: "bar"},
"BrokenField",
},
{
Test2{Name: "foo", BustedField: 23},
"BustedField",
},
{
Test3{Name: "foo", Time: time.Now()},
"",
},
}
for _, tc := range cases {
_, err := Hash(tc.Test, nil)
if err != nil {
if ens, ok := err.(*ErrNotStringer); ok {
if ens.Field != tc.Field {
t.Fatalf("did not get expected field %#v: got %s wanted %s", tc.Test, ens.Field, tc.Field)
}
} else {
t.Fatalf("unknown error %#v: got %s", tc, err)
}
}
}
}
func TestHash_equalNil(t *testing.T) {
type Test struct {
Str *string
Int *int
Map map[string]string
Slice []string
}
cases := []struct {
One, Two interface{}
ZeroNil bool
Match bool
}{
{
Test{
Str: nil,
Int: nil,
Map: nil,
Slice: nil,
},
Test{
Str: new(string),
Int: new(int),
Map: make(map[string]string),
Slice: make([]string, 0),
},
true,
true,
},
{
Test{
Str: nil,
Int: nil,
Map: nil,
Slice: nil,
},
Test{
Str: new(string),
Int: new(int),
Map: make(map[string]string),
Slice: make([]string, 0),
},
false,
false,
},
{
nil,
0,
true,
true,
},
{
nil,
0,
false,
true,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, &HashOptions{ZeroNil: tc.ZeroNil})
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, &HashOptions{ZeroNil: tc.ZeroNil})
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_equalSet(t *testing.T) {
type Test struct {
Name string
Friends []string `hash:"set"`
}
cases := []struct {
One, Two interface{}
Match bool
}{
{
Test{Name: "foo", Friends: []string{"foo", "bar"}},
Test{Name: "foo", Friends: []string{"bar", "foo"}},
true,
},
{
Test{Name: "foo", Friends: []string{"foo", "bar"}},
Test{Name: "foo", Friends: []string{"foo", "bar"}},
true,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_includable(t *testing.T) {
cases := []struct {
One, Two interface{}
Match bool
}{
{
testIncludable{Value: "foo"},
testIncludable{Value: "foo"},
true,
},
{
testIncludable{Value: "foo", Ignore: "bar"},
testIncludable{Value: "foo"},
true,
},
{
testIncludable{Value: "foo", Ignore: "bar"},
testIncludable{Value: "bar"},
false,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_includableMap(t *testing.T) {
cases := []struct {
One, Two interface{}
Match bool
}{
{
testIncludableMap{Map: map[string]string{"foo": "bar"}},
testIncludableMap{Map: map[string]string{"foo": "bar"}},
true,
},
{
testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}},
testIncludableMap{Map: map[string]string{"foo": "bar"}},
true,
},
{
testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}},
testIncludableMap{Map: map[string]string{"bar": "baz"}},
false,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
type testIncludable struct {
Value string
Ignore string
}
func (t testIncludable) HashInclude(field string, v interface{}) (bool, error) {
return field != "Ignore", nil
}
type testIncludableMap struct {
Map map[string]string
}
func (t testIncludableMap) HashIncludeMap(field string, k, v interface{}) (bool, error) {
if field != "Map" {
return true, nil
}
if s, ok := k.(string); ok && s == "ignore" {
return false, nil
}
return true, nil
}

15
vendor/github.com/mitchellh/hashstructure/include.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
package hashstructure
// Includable is an interface that can optionally be implemented by
// a struct. It will be called for each field in the struct to check whether
// it should be included in the hash.
type Includable interface {
HashInclude(field string, v interface{}) (bool, error)
}
// IncludableMap is an interface that can optionally be implemented by
// a struct. It will be called when a map-type field is found to ask the
// struct if the map item should be included in the hash.
type IncludableMap interface {
HashIncludeMap(field string, k, v interface{}) (bool, error)
}