<commit_before>package retry

import (
	"fmt"
	"log"
	"time"

	"github.com/Azure/azure-sdk-for-go/management"
)

// ExecuteAsyncOperation blocks until the provided asyncOperation is
// no longer in the InProgress state. Any known retryiable transient
// errors are retried and additional retry rules can be specified.
// If the operation was successful, nothing is returned, otherwise
// an error is returned.
func ExecuteAsyncOperation(client management.Client, asyncOperation func() (management.OperationID, error), extraRules ...RetryRule) error {
	if asyncOperation == nil {
		return fmt.Errorf("Parameter not specified: %s", "asyncOperation")
	}

	retryPolicy := append(newDefaultRetryPolicy(), extraRules...)

	for { // retry loop for azure errors, call continue for retryable errors

		operationId, err := asyncOperation()
		if err == nil {
			log.Printf("Waiting for operation: %s", operationId)
			err = client.WaitForOperation(operationId, nil)
		}
		if err != nil {
			log.Printf("Caught error (%T) during retryable operation: %v", err, err)
			// need to remove the pointer receiver in Azure SDK to make these *'s go away
			if azureError, ok := err.(*management.AzureError); ok {
				log.Printf("Error is Azure error, checking if we should retry...")
				if shouldRetry, backoff := retryPolicy.ShouldRetry(*azureError); shouldRetry {
					log.Printf("Error needs to be retried, sleeping %v", backoff)
					time.Sleep(backoff)
					continue // retry asyncOperation
				}
			}
		}
		return err
	}
}
<commit_msg>Fix error detection<commit_after>package retry

import (
	"fmt"
	"log"
	"time"

	"github.com/Azure/azure-sdk-for-go/management"
)

// ExecuteAsyncOperation blocks until the provided asyncOperation is
// no longer in the InProgress state. Any known retryiable transient
// errors are retried and additional retry rules can be specified.
// If the operation was successful, nothing is returned, otherwise
// an error is returned.
func ExecuteAsyncOperation(client management.Client, asyncOperation func() (management.OperationID, error), extraRules ...RetryRule) error {
	if asyncOperation == nil {
		return fmt.Errorf("Parameter not specified: %s", "asyncOperation")
	}

	retryPolicy := append(newDefaultRetryPolicy(), extraRules...)

	for { // retry loop for azure errors, call continue for retryable errors

		operationId, err := asyncOperation()
		if err == nil {
			log.Printf("Waiting for operation: %s", operationId)
			err = client.WaitForOperation(operationId, nil)
		}
		if err != nil {
			log.Printf("Caught error (%T) during retryable operation: %v", err, err)
			// need to remove the pointer receiver in Azure SDK to make these *'s go away
			if azureError, ok := err.(management.AzureError); ok {
				log.Printf("Error is Azure error, checking if we should retry...")
				if shouldRetry, backoff := retryPolicy.ShouldRetry(azureError); shouldRetry {
					log.Printf("Error needs to be retried, sleeping %v", backoff)
					time.Sleep(backoff)
					continue // retry asyncOperation
				}
			}
		}
		return err
	}
}
<|endoftext|>


<commit_before>// Copyright © 2018 Banzai Cloud
//
// 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 v1alpha1

import (
	"encoding/json"
	"reflect"

	"github.com/spf13/cast"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true

// VaultList represents a list of Vault Kubernetes objects
type VaultList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Vault `json:"items"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Vault represents a Vault Kubernetes object
type Vault struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	Spec              VaultSpec   `json:"spec"`
	Status            VaultStatus `json:"status,omitempty"`
}

// VaultSpec represents the Spec field of a Vault Kubernetes object
type VaultSpec struct {
	Size              int32                  `json:"size"`
	Image             string                 `json:"image"`
	BankVaultsImage   string                 `json:"bankVaultsImage"`
	StatsDImage       string                 `json:"statsdImage"`
	Annotations       map[string]string      `json:"annotations"`
	Config            map[string]interface{} `json:"config"`
	ExternalConfig    map[string]interface{} `json:"externalConfig"`
	UnsealConfig      UnsealConfig           `json:"unsealConfig"`
	CredentialsConfig CredentialsConfig      `json:"credentialsConfig"`
	// This option gives us the option to workaround current StatefulSet limitations around updates
	// See: https://github.com/kubernetes/kubernetes/issues/67250
	// TODO: Should be removed once the ParallelPodManagement policy supports the broken update.
	SupportUpgrade bool   `json:"supportUpgrade"`
	EtcdVersion    string `json:"etcdVersion"`
	EtcdSize       int    `json:"etcdSize"`
}

// HAStorageTypes is the set of storage backends supporting High Availability
var HAStorageTypes = map[string]bool{
	"consul":    true,
	"dynamodb":  true,
	"etcd":      true,
	"gcs":       true,
	"spanner":   true,
	"zookeeper": true,
}

// HasHAStorage detects if Vault is configured to use a storage backend which supports High Availability
func (spec *VaultSpec) HasHAStorage() bool {
	storageType := spec.GetStorageType()
	if _, ok := HAStorageTypes[storageType]; ok {
		return spec.HasStorageHAEnabled()
	}
	return false
}

// GetStorage returns Vault's storage stanza
func (spec *VaultSpec) GetStorage() map[string]interface{} {
	storage := spec.getStorage()
	return cast.ToStringMap(storage[spec.GetStorageType()])
}

func (spec *VaultSpec) getStorage() map[string]interface{} {
	return cast.ToStringMap(spec.Config["storage"])
}

// GetStorageType returns the type of Vault's storage stanza
func (spec *VaultSpec) GetStorageType() string {
	storage := spec.getStorage()
	return reflect.ValueOf(storage).MapKeys()[0].String()
}

// GetEtcdVersion returns the etcd version to use
func (spec *VaultSpec) GetEtcdVersion() string {
	if spec.EtcdVersion == "" {
		// See https://github.com/coreos/etcd-operator/issues/1962#issuecomment-390539621
		// for more details why we have to pin to 3.1.15
		return "3.1.15"
	}
	return spec.EtcdVersion
}

// GetEtcdSize returns the number of etcd pods to use
func (spec *VaultSpec) GetEtcdSize() int {
	if spec.EtcdSize < 1 {
		return 3
	}
	// check if size given is even. If even, subtract 1. Reasoning: Because of raft consensus protocol,
	// an odd-size cluster tolerates the same number of failures as an even-size cluster but with fewer nodes
	// See https://github.com/etcd-io/etcd/blob/master/Documentation/faq.md#what-is-failure-tolerance
	if spec.EtcdSize%2 == 0 {
		return spec.EtcdSize - 1
	}
	return spec.EtcdSize
}

// HasStorageHAEnabled detects if the ha_enabled field is set to true in Vault's storage stanza
func (spec *VaultSpec) HasStorageHAEnabled() bool {
	storageType := spec.GetStorageType()
	storage := spec.getStorage()
	storageSpecs := cast.ToStringMap(storage[storageType])
	// In Consul HA is always enabled
	return storageType == "consul" || cast.ToBool(storageSpecs["ha_enabled"])
}

// GetTLSDisable returns if Vault's TLS is disabled
func (spec *VaultSpec) GetTLSDisable() bool {
	listener := spec.getListener()
	tcpSpecs := cast.ToStringMap(listener["tcp"])
	return cast.ToBool(tcpSpecs["tls_disable"])
}

func (spec *VaultSpec) getListener() map[string]interface{} {
	return cast.ToStringMap(spec.Config["listener"])
}

// GetBankVaultsImage returns the bank-vaults image to use
func (spec *VaultSpec) GetBankVaultsImage() string {
	if spec.BankVaultsImage == "" {
		return "banzaicloud/bank-vaults:latest"
	}
	return spec.BankVaultsImage
}

// GetStatsDImage returns the StatsD image to use
func (spec *VaultSpec) GetStatsDImage() string {
	if spec.StatsDImage == "" {
		return "prom/statsd-exporter:latest"
	}
	return spec.StatsDImage
}

// GetAnnotations returns the Annotations
func (spec *VaultSpec) GetAnnotations() map[string]string {
	if spec.Annotations == nil {
		spec.Annotations = map[string]string{}
	}
	spec.Annotations["prometheus.io/scrape"] = "true"
	spec.Annotations["prometheus.io/path"] = "/metrics"
	spec.Annotations["prometheus.io/port"] = "9102"
	return spec.Annotations
}

// ConfigJSON returns the Config field as a JSON string
func (spec *VaultSpec) ConfigJSON() string {
	if _, ok := spec.Config["disable_clustering"]; !ok {
		spec.Config["disable_clustering"] = true
	}
	config, _ := json.Marshal(spec.Config)
	return string(config)
}

// ExternalConfigJSON returns the ExternalConfig field as a JSON string
func (spec *VaultSpec) ExternalConfigJSON() string {
	config, _ := json.Marshal(spec.ExternalConfig)
	return string(config)
}

// VaultStatus represents the Status field of a Vault Kubernetes object
type VaultStatus struct {
	Nodes []string `json:"nodes"`
}

// UnsealConfig represents the UnsealConfig field of a VaultSpec Kubernetes object
type UnsealConfig struct {
	Kubernetes *KubernetesUnsealConfig `json:"kubernetes"`
	Google     *GoogleUnsealConfig     `json:"google"`
	Alibaba    *AlibabaUnsealConfig    `json:"alibaba"`
	Azure      *AzureUnsealConfig      `json:"azure"`
	AWS        *AWSUnsealConfig        `json:"aws"`
}

// ToArgs returns the UnsealConfig as and argument array for bank-vaults
func (usc *UnsealConfig) ToArgs(vault *Vault) []string {
	if usc.Kubernetes != nil {
		secretNamespace := vault.Namespace
		if usc.Kubernetes.SecretNamespace != "" {
			secretNamespace = usc.Kubernetes.SecretNamespace
		}
		secretName := vault.Name + "-unseal-keys"
		if usc.Kubernetes.SecretName != "" {
			secretName = usc.Kubernetes.SecretName
		}
		return []string{"--mode", "k8s", "--k8s-secret-namespace", secretNamespace, "--k8s-secret-name", secretName}
	}
	if usc.Google != nil {
		return []string{
			"--mode",
			"google-cloud-kms-gcs",
			"--google-cloud-kms-key-ring",
			usc.Google.KMSKeyRing,
			"--google-cloud-kms-crypto-key",
			usc.Google.KMSCryptoKey,
			"--google-cloud-kms-location",
			usc.Google.KMSLocation,
			"--google-cloud-kms-project",
			usc.Google.KMSProject,
			"--google-cloud-storage-bucket",
			usc.Google.StorageBucket,
		}
	}
	if usc.Azure != nil {
		return []string{"--mode", "azure-key-vault", "--azure-key-vault-name", usc.Azure.KeyVaultName}
	}
	if usc.AWS != nil {
		return []string{
			"--mode",
			"aws-kms-s3",
			"--aws-kms-key-id",
			usc.AWS.KMSKeyID,
			"--aws-kms-region",
			usc.AWS.KMSRegion,
			"--aws-s3-bucket",
			usc.AWS.S3Bucket,
			"--aws-s3-prefix",
			usc.AWS.S3Prefix,
			"--aws-s3-region",
			usc.AWS.S3Region,
		}
	}
	if usc.Alibaba != nil {
		return []string{
			"--mode",
			"alibaba-kms-oss",
			"--alibaba-kms-region",
			usc.Alibaba.KMSRegion,
			"--alibaba-kms-key-id",
			usc.Alibaba.KMSKeyID,
			"--alibaba-oss-endpoint",
			usc.Alibaba.OSSEndpoint,
			"--alibaba-oss-bucket",
			usc.Alibaba.OSSBucket,
			"--alibaba-oss-prefix",
			usc.Alibaba.OSSPrefix,
		}
	}
	return []string{}
}

// KubernetesUnsealConfig holds the parameters for Kubernetes based unsealing
type KubernetesUnsealConfig struct {
	SecretNamespace string `json:"secretNamespace"`
	SecretName      string `json:"secretName"`
}

// GoogleUnsealConfig holds the parameters for Google KMS based unsealing
type GoogleUnsealConfig struct {
	KMSKeyRing    string `json:"kmsKeyRing"`
	KMSCryptoKey  string `json:"kmsCryptoKey"`
	KMSLocation   string `json:"kmsLocation"`
	KMSProject    string `json:"kmsProject"`
	StorageBucket string `json:"storageBucket"`
}

// AlibabaUnsealConfig holds the parameters for Alibaba Cloud KMS based unsealing
//  --alibaba-kms-region eu-central-1 --alibaba-kms-key-id 9d8063eb-f9dc-421b-be80-15d195c9f148 --alibaba-oss-endpoint oss-eu-central-1.aliyuncs.com --alibaba-oss-bucket bank-vaults
type AlibabaUnsealConfig struct {
	KMSRegion   string `json:"kmsRegion"`
	KMSKeyID    string `json:"kmsKeyId"`
	OSSEndpoint string `json:"ossEndpoint"`
	OSSBucket   string `json:"ossBucket"`
	OSSPrefix   string `json:"ossPrefix"`
}

// AzureUnsealConfig holds the parameters for Azure Key Vault based unsealing
type AzureUnsealConfig struct {
	KeyVaultName string `json:"keyVaultName"`
}

// AWSUnsealConfig holds the parameters for AWS KMS based unsealing
type AWSUnsealConfig struct {
	KMSKeyID  string `json:"kmsKeyId"`
	KMSRegion string `json:"kmsRegion"`
	S3Bucket  string `json:"s3Bucket"`
	S3Prefix  string `json:"s3Prefix"`
	S3Region  string `json:"s3Region"`
}

// CredentialsConfig configuration for a credentials file provided as a secret
type CredentialsConfig struct {
	Env        string `json:"env"`
	Path       string `json:"path"`
	SecretName string `json:"secretName"`
}
<commit_msg>Added mysql to HA storage type's<commit_after>// Copyright © 2018 Banzai Cloud
//
// 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 v1alpha1

import (
	"encoding/json"
	"reflect"

	"github.com/spf13/cast"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true

// VaultList represents a list of Vault Kubernetes objects
type VaultList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Vault `json:"items"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Vault represents a Vault Kubernetes object
type Vault struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	Spec              VaultSpec   `json:"spec"`
	Status            VaultStatus `json:"status,omitempty"`
}

// VaultSpec represents the Spec field of a Vault Kubernetes object
type VaultSpec struct {
	Size              int32                  `json:"size"`
	Image             string                 `json:"image"`
	BankVaultsImage   string                 `json:"bankVaultsImage"`
	StatsDImage       string                 `json:"statsdImage"`
	Annotations       map[string]string      `json:"annotations"`
	Config            map[string]interface{} `json:"config"`
	ExternalConfig    map[string]interface{} `json:"externalConfig"`
	UnsealConfig      UnsealConfig           `json:"unsealConfig"`
	CredentialsConfig CredentialsConfig      `json:"credentialsConfig"`
	// This option gives us the option to workaround current StatefulSet limitations around updates
	// See: https://github.com/kubernetes/kubernetes/issues/67250
	// TODO: Should be removed once the ParallelPodManagement policy supports the broken update.
	SupportUpgrade bool   `json:"supportUpgrade"`
	EtcdVersion    string `json:"etcdVersion"`
	EtcdSize       int    `json:"etcdSize"`
}

// HAStorageTypes is the set of storage backends supporting High Availability
var HAStorageTypes = map[string]bool{
	"consul":    true,
	"dynamodb":  true,
	"etcd":      true,
	"gcs":       true,
	"mysql":     true,
	"spanner":   true,
	"zookeeper": true,
}

// HasHAStorage detects if Vault is configured to use a storage backend which supports High Availability
func (spec *VaultSpec) HasHAStorage() bool {
	storageType := spec.GetStorageType()
	if _, ok := HAStorageTypes[storageType]; ok {
		return spec.HasStorageHAEnabled()
	}
	return false
}

// GetStorage returns Vault's storage stanza
func (spec *VaultSpec) GetStorage() map[string]interface{} {
	storage := spec.getStorage()
	return cast.ToStringMap(storage[spec.GetStorageType()])
}

func (spec *VaultSpec) getStorage() map[string]interface{} {
	return cast.ToStringMap(spec.Config["storage"])
}

// GetStorageType returns the type of Vault's storage stanza
func (spec *VaultSpec) GetStorageType() string {
	storage := spec.getStorage()
	return reflect.ValueOf(storage).MapKeys()[0].String()
}

// GetEtcdVersion returns the etcd version to use
func (spec *VaultSpec) GetEtcdVersion() string {
	if spec.EtcdVersion == "" {
		// See https://github.com/coreos/etcd-operator/issues/1962#issuecomment-390539621
		// for more details why we have to pin to 3.1.15
		return "3.1.15"
	}
	return spec.EtcdVersion
}

// GetEtcdSize returns the number of etcd pods to use
func (spec *VaultSpec) GetEtcdSize() int {
	if spec.EtcdSize < 1 {
		return 3
	}
	// check if size given is even. If even, subtract 1. Reasoning: Because of raft consensus protocol,
	// an odd-size cluster tolerates the same number of failures as an even-size cluster but with fewer nodes
	// See https://github.com/etcd-io/etcd/blob/master/Documentation/faq.md#what-is-failure-tolerance
	if spec.EtcdSize%2 == 0 {
		return spec.EtcdSize - 1
	}
	return spec.EtcdSize
}

// HasStorageHAEnabled detects if the ha_enabled field is set to true in Vault's storage stanza
func (spec *VaultSpec) HasStorageHAEnabled() bool {
	storageType := spec.GetStorageType()
	storage := spec.getStorage()
	storageSpecs := cast.ToStringMap(storage[storageType])
	// In Consul HA is always enabled
	return storageType == "consul" || cast.ToBool(storageSpecs["ha_enabled"])
}

// GetTLSDisable returns if Vault's TLS is disabled
func (spec *VaultSpec) GetTLSDisable() bool {
	listener := spec.getListener()
	tcpSpecs := cast.ToStringMap(listener["tcp"])
	return cast.ToBool(tcpSpecs["tls_disable"])
}

func (spec *VaultSpec) getListener() map[string]interface{} {
	return cast.ToStringMap(spec.Config["listener"])
}

// GetBankVaultsImage returns the bank-vaults image to use
func (spec *VaultSpec) GetBankVaultsImage() string {
	if spec.BankVaultsImage == "" {
		return "banzaicloud/bank-vaults:latest"
	}
	return spec.BankVaultsImage
}

// GetStatsDImage returns the StatsD image to use
func (spec *VaultSpec) GetStatsDImage() string {
	if spec.StatsDImage == "" {
		return "prom/statsd-exporter:latest"
	}
	return spec.StatsDImage
}

// GetAnnotations returns the Annotations
func (spec *VaultSpec) GetAnnotations() map[string]string {
	if spec.Annotations == nil {
		spec.Annotations = map[string]string{}
	}
	spec.Annotations["prometheus.io/scrape"] = "true"
	spec.Annotations["prometheus.io/path"] = "/metrics"
	spec.Annotations["prometheus.io/port"] = "9102"
	return spec.Annotations
}

// ConfigJSON returns the Config field as a JSON string
func (spec *VaultSpec) ConfigJSON() string {
	if _, ok := spec.Config["disable_clustering"]; !ok {
		spec.Config["disable_clustering"] = true
	}
	config, _ := json.Marshal(spec.Config)
	return string(config)
}

// ExternalConfigJSON returns the ExternalConfig field as a JSON string
func (spec *VaultSpec) ExternalConfigJSON() string {
	config, _ := json.Marshal(spec.ExternalConfig)
	return string(config)
}

// VaultStatus represents the Status field of a Vault Kubernetes object
type VaultStatus struct {
	Nodes []string `json:"nodes"`
}

// UnsealConfig represents the UnsealConfig field of a VaultSpec Kubernetes object
type UnsealConfig struct {
	Kubernetes *KubernetesUnsealConfig `json:"kubernetes"`
	Google     *GoogleUnsealConfig     `json:"google"`
	Alibaba    *AlibabaUnsealConfig    `json:"alibaba"`
	Azure      *AzureUnsealConfig      `json:"azure"`
	AWS        *AWSUnsealConfig        `json:"aws"`
}

// ToArgs returns the UnsealConfig as and argument array for bank-vaults
func (usc *UnsealConfig) ToArgs(vault *Vault) []string {
	if usc.Kubernetes != nil {
		secretNamespace := vault.Namespace
		if usc.Kubernetes.SecretNamespace != "" {
			secretNamespace = usc.Kubernetes.SecretNamespace
		}
		secretName := vault.Name + "-unseal-keys"
		if usc.Kubernetes.SecretName != "" {
			secretName = usc.Kubernetes.SecretName
		}
		return []string{"--mode", "k8s", "--k8s-secret-namespace", secretNamespace, "--k8s-secret-name", secretName}
	}
	if usc.Google != nil {
		return []string{
			"--mode",
			"google-cloud-kms-gcs",
			"--google-cloud-kms-key-ring",
			usc.Google.KMSKeyRing,
			"--google-cloud-kms-crypto-key",
			usc.Google.KMSCryptoKey,
			"--google-cloud-kms-location",
			usc.Google.KMSLocation,
			"--google-cloud-kms-project",
			usc.Google.KMSProject,
			"--google-cloud-storage-bucket",
			usc.Google.StorageBucket,
		}
	}
	if usc.Azure != nil {
		return []string{"--mode", "azure-key-vault", "--azure-key-vault-name", usc.Azure.KeyVaultName}
	}
	if usc.AWS != nil {
		return []string{
			"--mode",
			"aws-kms-s3",
			"--aws-kms-key-id",
			usc.AWS.KMSKeyID,
			"--aws-kms-region",
			usc.AWS.KMSRegion,
			"--aws-s3-bucket",
			usc.AWS.S3Bucket,
			"--aws-s3-prefix",
			usc.AWS.S3Prefix,
			"--aws-s3-region",
			usc.AWS.S3Region,
		}
	}
	if usc.Alibaba != nil {
		return []string{
			"--mode",
			"alibaba-kms-oss",
			"--alibaba-kms-region",
			usc.Alibaba.KMSRegion,
			"--alibaba-kms-key-id",
			usc.Alibaba.KMSKeyID,
			"--alibaba-oss-endpoint",
			usc.Alibaba.OSSEndpoint,
			"--alibaba-oss-bucket",
			usc.Alibaba.OSSBucket,
			"--alibaba-oss-prefix",
			usc.Alibaba.OSSPrefix,
		}
	}
	return []string{}
}

// KubernetesUnsealConfig holds the parameters for Kubernetes based unsealing
type KubernetesUnsealConfig struct {
	SecretNamespace string `json:"secretNamespace"`
	SecretName      string `json:"secretName"`
}

// GoogleUnsealConfig holds the parameters for Google KMS based unsealing
type GoogleUnsealConfig struct {
	KMSKeyRing    string `json:"kmsKeyRing"`
	KMSCryptoKey  string `json:"kmsCryptoKey"`
	KMSLocation   string `json:"kmsLocation"`
	KMSProject    string `json:"kmsProject"`
	StorageBucket string `json:"storageBucket"`
}

// AlibabaUnsealConfig holds the parameters for Alibaba Cloud KMS based unsealing
//  --alibaba-kms-region eu-central-1 --alibaba-kms-key-id 9d8063eb-f9dc-421b-be80-15d195c9f148 --alibaba-oss-endpoint oss-eu-central-1.aliyuncs.com --alibaba-oss-bucket bank-vaults
type AlibabaUnsealConfig struct {
	KMSRegion   string `json:"kmsRegion"`
	KMSKeyID    string `json:"kmsKeyId"`
	OSSEndpoint string `json:"ossEndpoint"`
	OSSBucket   string `json:"ossBucket"`
	OSSPrefix   string `json:"ossPrefix"`
}

// AzureUnsealConfig holds the parameters for Azure Key Vault based unsealing
type AzureUnsealConfig struct {
	KeyVaultName string `json:"keyVaultName"`
}

// AWSUnsealConfig holds the parameters for AWS KMS based unsealing
type AWSUnsealConfig struct {
	KMSKeyID  string `json:"kmsKeyId"`
	KMSRegion string `json:"kmsRegion"`
	S3Bucket  string `json:"s3Bucket"`
	S3Prefix  string `json:"s3Prefix"`
	S3Region  string `json:"s3Region"`
}

// CredentialsConfig configuration for a credentials file provided as a secret
type CredentialsConfig struct {
	Env        string `json:"env"`
	Path       string `json:"path"`
	SecretName string `json:"secretName"`
}
<|endoftext|>


<commit_before>package conf

import (
	"encoding/json"
	"errors"
	"strconv"
	"strings"

	"v2ray.com/core/app/router"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/tools/geoip"

	"github.com/golang/protobuf/proto"
)

type RouterRulesConfig struct {
	RuleList       []json.RawMessage `json:"rules"`
	DomainStrategy string            `json:"domainStrategy"`
}

type RouterConfig struct {
	Settings *RouterRulesConfig `json:"settings"`
}

func (this *RouterConfig) Build() (*router.Config, error) {
	if this.Settings == nil {
		return nil, errors.New("Router settings is not specified.")
	}
	config := new(router.Config)

	settings := this.Settings
	config.DomainStrategy = router.Config_AsIs
	config.Rule = make([]*router.RoutingRule, len(settings.RuleList))
	domainStrategy := strings.ToLower(settings.DomainStrategy)
	if domainStrategy == "alwaysip" {
		config.DomainStrategy = router.Config_UseIp
	} else if domainStrategy == "ipifnonmatch" {
		config.DomainStrategy = router.Config_IpIfNonMatch
	}
	for idx, rawRule := range settings.RuleList {
		rule := ParseRule(rawRule)
		config.Rule[idx] = rule
	}
	return config, nil
}

type RouterRule struct {
	Type        string `json:"type"`
	OutboundTag string `json:"outboundTag"`
}

func parseIP(s string) *router.CIDR {
	var addr, mask string
	i := strings.Index(s, "/")
	if i < 0 {
		addr = s
	} else {
		addr = s[:i]
		mask = s[i+1:]
	}
	ip := v2net.ParseAddress(addr)
	switch ip.Family() {
	case v2net.AddressFamilyIPv4:
		bits := uint32(32)
		if len(mask) > 0 {
			bits64, err := strconv.ParseUint(mask, 10, 32)
			if err != nil {
				return nil
			}
			bits = uint32(bits64)
		}
		if bits > 32 {
			log.Warning("Router: invalid network mask: ", bits)
			return nil
		}
		return &router.CIDR{
			Ip:     []byte(ip.IP()),
			Prefix: bits,
		}
	case v2net.AddressFamilyIPv6:
		bits := uint32(128)
		if len(mask) > 0 {
			bits64, err := strconv.ParseUint(mask, 10, 32)
			if err != nil {
				return nil
			}
			bits = uint32(bits64)
		}
		if bits > 128 {
			log.Warning("Router: invalid network mask: ", bits)
			return nil
		}
		return &router.CIDR{
			Ip:     []byte(ip.IP()),
			Prefix: bits,
		}
	default:
		log.Warning("Router: unsupported address: ", s)
		return nil
	}
}

func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
	type RawFieldRule struct {
		RouterRule
		Domain  *StringList  `json:"domain"`
		IP      *StringList  `json:"ip"`
		Port    *PortRange   `json:"port"`
		Network *NetworkList `json:"network"`
	}
	rawFieldRule := new(RawFieldRule)
	err := json.Unmarshal(msg, rawFieldRule)
	if err != nil {
		return nil, err
	}

	rule := new(router.RoutingRule)
	rule.Tag = rawFieldRule.OutboundTag

	if rawFieldRule.Domain != nil {
		for _, domain := range *rawFieldRule.Domain {
			domainRule := new(router.Domain)
			if strings.HasPrefix(domain, "regexp:") {
				domainRule.Type = router.Domain_Regex
				domainRule.Value = domain[7:]
			} else {
				domainRule.Type = router.Domain_Plain
				domainRule.Value = domain
			}
			rule.Domain = append(rule.Domain, domainRule)
		}
	}

	if rawFieldRule.IP != nil {
		for _, ip := range *rawFieldRule.IP {
			ipRule := parseIP(ip)
			if ipRule != nil {
				rule.Cidr = append(rule.Cidr, ipRule)
			}
		}
	}

	if rawFieldRule.Port != nil {
		rule.PortRange = rawFieldRule.Port.Build()
	}

	if rawFieldRule.Network != nil {
		rule.NetworkList = rawFieldRule.Network.Build()
	}

	return rule, nil
}

func ParseRule(msg json.RawMessage) *router.RoutingRule {
	rawRule := new(RouterRule)
	err := json.Unmarshal(msg, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil
	}
	if rawRule.Type == "field" {

		fieldrule, err := parseFieldRule(msg)
		if err != nil {
			log.Error("Invalid field rule: ", err)
			return nil
		}
		return fieldrule
	}
	if rawRule.Type == "chinaip" {
		chinaiprule, err := parseChinaIPRule(msg)
		if err != nil {
			log.Error("Router: Invalid chinaip rule: ", err)
			return nil
		}
		return chinaiprule
	}
	if rawRule.Type == "chinasites" {
		chinasitesrule, err := parseChinaSitesRule(msg)
		if err != nil {
			log.Error("Invalid chinasites rule: ", err)
			return nil
		}
		return chinasitesrule
	}
	log.Error("Unknown router rule type: ", rawRule.Type)
	return nil
}

func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
	rawRule := new(RouterRule)
	err := json.Unmarshal(data, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil, err
	}
	var chinaIPs geoip.CountryIPRange
	if err := proto.Unmarshal(geoip.ChinaIPs, &chinaIPs); err != nil {
		return nil, err
	}
	return &router.RoutingRule{
		Tag:  rawRule.OutboundTag,
		Cidr: chinaIPs.Ips,
	}, nil
}

func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
	rawRule := new(RouterRule)
	err := json.Unmarshal(data, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil, err
	}
	return &router.RoutingRule{
		Tag:    rawRule.OutboundTag,
		Domain: chinaSitesDomains,
	}, nil
}
<commit_msg>json conf for source session in router<commit_after>package conf

import (
	"encoding/json"
	"errors"
	"strconv"
	"strings"

	"v2ray.com/core/app/router"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/tools/geoip"

	"github.com/golang/protobuf/proto"
)

type RouterRulesConfig struct {
	RuleList       []json.RawMessage `json:"rules"`
	DomainStrategy string            `json:"domainStrategy"`
}

type RouterConfig struct {
	Settings *RouterRulesConfig `json:"settings"`
}

func (this *RouterConfig) Build() (*router.Config, error) {
	if this.Settings == nil {
		return nil, errors.New("Router settings is not specified.")
	}
	config := new(router.Config)

	settings := this.Settings
	config.DomainStrategy = router.Config_AsIs
	config.Rule = make([]*router.RoutingRule, len(settings.RuleList))
	domainStrategy := strings.ToLower(settings.DomainStrategy)
	if domainStrategy == "alwaysip" {
		config.DomainStrategy = router.Config_UseIp
	} else if domainStrategy == "ipifnonmatch" {
		config.DomainStrategy = router.Config_IpIfNonMatch
	}
	for idx, rawRule := range settings.RuleList {
		rule := ParseRule(rawRule)
		config.Rule[idx] = rule
	}
	return config, nil
}

type RouterRule struct {
	Type        string `json:"type"`
	OutboundTag string `json:"outboundTag"`
}

func parseIP(s string) *router.CIDR {
	var addr, mask string
	i := strings.Index(s, "/")
	if i < 0 {
		addr = s
	} else {
		addr = s[:i]
		mask = s[i+1:]
	}
	ip := v2net.ParseAddress(addr)
	switch ip.Family() {
	case v2net.AddressFamilyIPv4:
		bits := uint32(32)
		if len(mask) > 0 {
			bits64, err := strconv.ParseUint(mask, 10, 32)
			if err != nil {
				return nil
			}
			bits = uint32(bits64)
		}
		if bits > 32 {
			log.Warning("Router: invalid network mask: ", bits)
			return nil
		}
		return &router.CIDR{
			Ip:     []byte(ip.IP()),
			Prefix: bits,
		}
	case v2net.AddressFamilyIPv6:
		bits := uint32(128)
		if len(mask) > 0 {
			bits64, err := strconv.ParseUint(mask, 10, 32)
			if err != nil {
				return nil
			}
			bits = uint32(bits64)
		}
		if bits > 128 {
			log.Warning("Router: invalid network mask: ", bits)
			return nil
		}
		return &router.CIDR{
			Ip:     []byte(ip.IP()),
			Prefix: bits,
		}
	default:
		log.Warning("Router: unsupported address: ", s)
		return nil
	}
}

func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
	type RawFieldRule struct {
		RouterRule
		Domain   *StringList  `json:"domain"`
		IP       *StringList  `json:"ip"`
		Port     *PortRange   `json:"port"`
		Network  *NetworkList `json:"network"`
		SourceIP *StringList  `json:"source"`
		User     *StringList  `json:"user"`
	}
	rawFieldRule := new(RawFieldRule)
	err := json.Unmarshal(msg, rawFieldRule)
	if err != nil {
		return nil, err
	}

	rule := new(router.RoutingRule)
	rule.Tag = rawFieldRule.OutboundTag

	if rawFieldRule.Domain != nil {
		for _, domain := range *rawFieldRule.Domain {
			domainRule := new(router.Domain)
			if strings.HasPrefix(domain, "regexp:") {
				domainRule.Type = router.Domain_Regex
				domainRule.Value = domain[7:]
			} else {
				domainRule.Type = router.Domain_Plain
				domainRule.Value = domain
			}
			rule.Domain = append(rule.Domain, domainRule)
		}
	}

	if rawFieldRule.IP != nil {
		for _, ip := range *rawFieldRule.IP {
			ipRule := parseIP(ip)
			if ipRule != nil {
				rule.Cidr = append(rule.Cidr, ipRule)
			}
		}
	}

	if rawFieldRule.Port != nil {
		rule.PortRange = rawFieldRule.Port.Build()
	}

	if rawFieldRule.Network != nil {
		rule.NetworkList = rawFieldRule.Network.Build()
	}

	if rawFieldRule.SourceIP != nil {
		for _, ip := range *rawFieldRule.IP {
			ipRule := parseIP(ip)
			if ipRule != nil {
				rule.SourceCidr = append(rule.SourceCidr, ipRule)
			}
		}
	}

	if rawFieldRule.User != nil {
		for _, s := range *rawFieldRule.User {
			rule.UserEmail = append(rule.UserEmail, s)
		}
	}

	return rule, nil
}

func ParseRule(msg json.RawMessage) *router.RoutingRule {
	rawRule := new(RouterRule)
	err := json.Unmarshal(msg, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil
	}
	if rawRule.Type == "field" {

		fieldrule, err := parseFieldRule(msg)
		if err != nil {
			log.Error("Invalid field rule: ", err)
			return nil
		}
		return fieldrule
	}
	if rawRule.Type == "chinaip" {
		chinaiprule, err := parseChinaIPRule(msg)
		if err != nil {
			log.Error("Router: Invalid chinaip rule: ", err)
			return nil
		}
		return chinaiprule
	}
	if rawRule.Type == "chinasites" {
		chinasitesrule, err := parseChinaSitesRule(msg)
		if err != nil {
			log.Error("Invalid chinasites rule: ", err)
			return nil
		}
		return chinasitesrule
	}
	log.Error("Unknown router rule type: ", rawRule.Type)
	return nil
}

func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
	rawRule := new(RouterRule)
	err := json.Unmarshal(data, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil, err
	}
	var chinaIPs geoip.CountryIPRange
	if err := proto.Unmarshal(geoip.ChinaIPs, &chinaIPs); err != nil {
		return nil, err
	}
	return &router.RoutingRule{
		Tag:  rawRule.OutboundTag,
		Cidr: chinaIPs.Ips,
	}, nil
}

func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
	rawRule := new(RouterRule)
	err := json.Unmarshal(data, rawRule)
	if err != nil {
		log.Error("Router: Invalid router rule: ", err)
		return nil, err
	}
	return &router.RoutingRule{
		Tag:    rawRule.OutboundTag,
		Domain: chinaSitesDomains,
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/animenotifier/anilist"
	"github.com/animenotifier/arn"
	"github.com/fatih/color"
)

func main() {
	defer arn.Node.Close()

	arn.PanicOnError(anilist.Authorize())
	color.Green(anilist.AccessToken)

	allAnime := arn.AllAnime()

	count := 0

	for aniListAnime := range anilist.StreamAnime() {
		println(aniListAnime.TitleRomaji)

		anime := arn.FindAniListAnime(aniListAnime, allAnime)

		if anime != nil {
			color.Green("%s %s", anime.ID, aniListAnime.TitleRomaji)
			count++
		} else {
			color.Red("Not found")
		}
	}

	color.Green("%d anime are connected with AniList", count)
}
<commit_msg>Minor change<commit_after>package main

import (
	"github.com/animenotifier/anilist"
	"github.com/animenotifier/arn"
	"github.com/fatih/color"
)

func main() {
	defer arn.Node.Close()

	arn.PanicOnError(anilist.Authorize())
	color.Green(anilist.AccessToken)

	allAnime := arn.AllAnime()
	count := 0

	for aniListAnime := range anilist.StreamAnime() {
		println(aniListAnime.TitleRomaji)

		anime := arn.FindAniListAnime(aniListAnime, allAnime)

		if anime != nil {
			color.Green("%s %s", anime.ID, aniListAnime.TitleRomaji)
			count++
		} else {
			color.Red("Not found")
		}
	}

	color.Green("%d anime are connected with AniList", count)
}
<|endoftext|>


<commit_before>package paypal_rest

import (
	"code.google.com/p/godec/dec"
	"database/sql"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"gopkg.in/inconshreveable/log15.v2"

	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment_method"
)

const (
	paypalPaymentPath = "/v1/payments/payment"
)

func (d *Driver) InitPayment(p *payment.Payment, method *payment_method.Method) (http.Handler, error) {
	log := d.log.New(log15.Ctx{
		"method":          "InitPayment",
		"projectID":       p.ProjectID(),
		"paymentID":       p.ID(),
		"paymentMethodID": method.ID,
	})

	var tx *sql.Tx
	var err error
	var commit bool
	defer func() {
		if tx != nil && !commit {
			err = tx.Rollback()
			if err != nil {
				log.Crit("error on rollback", log15.Ctx{"err": err})
			}
		}
	}()
	tx, err = d.ctx.PaymentDB().Begin()
	if err != nil {
		commit = true
		log.Crit("error on begin tx", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	currentTx, err := TransactionCurrentByPaymentIDTx(tx, p.PaymentID())
	if err != nil && err != ErrTransactionNotFound {
		log.Error("error retrieving transaction", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}
	if err == nil {
		if Debug {
			log.Debug("already initialized payment")
		}
		switch currentTx.Type {
		case TransactionTypeError:
			return d.PaymentErrorHandler(p), nil
		default:
			return d.InitPageHandler(p), nil
		}
	}

	cfg, err := ConfigByPaymentMethodTx(tx, method)
	if err != nil {
		log.Error("error retrieving PayPal config", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	// create payment request
	req := &PayPalPaymentRequest{}
	if cfg.Type != "sale" && cfg.Type != "authorize" {
		log.Crit("invalid config type", log15.Ctx{"configType": cfg.Type})
		return nil, ErrInternal
	}
	req.Intent = cfg.Type
	req.Payer.PaymentMethod = PayPalPaymentMethodPayPal
	req.RedirectURLs, err = d.redirectURLs()
	if err != nil {
		log.Error("error creating redirect urls", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	req.Transactions = []PayPalTransaction{
		d.payPalTransactionFromPayment(p),
	}
	if Debug {
		log.Debug("created paypal payment request", log15.Ctx{"request": req})
	}

	endpoint, err := url.Parse(cfg.Endpoint)
	if err != nil {
		log.Error("error on endpoint URL", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	endpoint.Path = paypalPaymentPath

	jsonBytes, err := json.Marshal(req)
	if err != nil {
		log.Error("error encoding request", log15.Ctx{"err": err})
		return nil, ErrInternal
	}

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeCreatePayment,
	}
	paypalTx.Data = jsonBytes

	err = InsertTransactionTx(tx, paypalTx)
	if err != nil {
		log.Error("error saving transaction", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	commit = true
	err = tx.Commit()
	if err != nil {
		log.Crit("error on commit", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	errors := make(chan error)
	go func() {
		for {
			select {
			case err := <-errors:
				if err == nil {
					return
				}
				log.Error("error on initializing", log15.Ctx{"err": err})
				return
			case <-d.ctx.Done():
				log.Warn("cancelled initialization", log15.Ctx{"err": d.ctx.Err()})
				return
			}
		}
	}()
	go d.doInit(errors, cfg, endpoint, p, string(jsonBytes))

	return d.InitPageHandler(p), nil
}

func (d *Driver) redirectURLs() (PayPalRedirectURLs, error) {
	u := PayPalRedirectURLs{}
	returnRoute, err := d.mux.Get("returnHandler").URLPath()
	if err != nil {
		return u, err
	}
	cancelRoute, err := d.mux.Get("cancelHandler").URLPath()
	if err != nil {
		return u, err
	}

	returnURL := &(*d.baseURL)
	returnURL.Path = returnRoute.Path
	u.ReturnURL = returnURL.String()

	cancelURL := &(*d.baseURL)
	cancelURL.Path = cancelRoute.Path
	u.CancelURL = cancelURL.String()

	return u, nil
}

func (d *Driver) payPalTransactionFromPayment(p *payment.Payment) PayPalTransaction {
	t := PayPalTransaction{}
	encPaymentID := d.paymentService.EncodedPaymentID(p.PaymentID())
	t.Custom = encPaymentID.String()
	t.InvoiceNumber = encPaymentID.String()
	amnt := &p.Decimal().Dec
	amnt.Round(amnt, dec.Scale(2), dec.RoundHalfUp)
	t.Amount = PayPalAmount{
		Currency: p.Currency,
		Total:    amnt.String(),
	}
	return t
}

func (d *Driver) doInit(errors chan<- error, cfg *Config, reqURL *url.URL, p *payment.Payment, body string) {
	log := d.log.New(log15.Ctx{
		"method":    "doInit",
		"projectID": p.ProjectID(),
		"paymentID": p.ID(),
		"methodKey": cfg.MethodKey,
	})
	if Debug {
		log.Debug("posting...")
	}

	tr, err := d.oAuthTransport(log)(p, cfg)
	if err != nil {
		log.Error("error on auth transport", log15.Ctx{"err": err})
		errors <- err
		return
	}
	err = tr.AuthenticateClient()
	if err != nil {
		log.Error("error authenticating", log15.Ctx{"err": err})
		errors <- err
		return
	}
	cl := tr.Client()
	resp, err := cl.Post(reqURL.String(), "application/json", strings.NewReader(body))
	if err != nil {
		log.Error("error on HTTP POST", log15.Ctx{"err": err})
		errors <- err
		return
	}
	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		log.Error("error on HTTP request", log15.Ctx{"HTTPStatusCode": resp.StatusCode})
		d.setPayPalErrorResponse(p, resp)
		errors <- ErrHTTP
		return
	}
	paypalP := &PaypalPayment{}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(paypalP)
	if err != nil {
		log.Error("error decoding PayPal response", log15.Ctx{"err": err})
		errors <- ErrProvider
	}
	if Debug {
		log.Debug("received response", log15.Ctx{"paypalPayment": paypalP})
	}

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeCreatePaymentResponse,
	}
	if paypalP.ID != "" {
		paypalTx.SetPaypalID(paypalP.ID)
	}
	if paypalP.State != "" {
		paypalTx.SetState(paypalP.State)
	}
	if paypalP.CreateTime != "" {
		t, err := time.Parse(time.RFC3339, paypalP.CreateTime)
		if err != nil {
			log.Warn("error parsing paypal create time", log15.Ctx{"err": err})
		} else {
			paypalTx.PaypalCreateTime = &t
		}
	}
	if paypalP.UpdateTime != "" {
		t, err := time.Parse(time.RFC3339, paypalP.UpdateTime)
		if err != nil {
			log.Warn("error parsing paypal update time", log15.Ctx{"err": err})
		} else {
			paypalTx.PaypalUpdateTime = &t
		}
	}
	paypalTx.Links, err = json.Marshal(paypalP.Links)
	if err != nil {
		log.Error("error on saving links on response", log15.Ctx{"err": err})
		errors <- ErrProvider
		return
	}
	paypalTx.Data, err = json.Marshal(paypalP)
	if err != nil {
		log.Error("error marshalling paypal payment response", log15.Ctx{"err": err})
		errors <- ErrProvider
		return
	}
	err = InsertTransactionDB(d.ctx.PaymentDB(), paypalTx)
	if err != nil {
		log.Error("error saving paypal response", log15.Ctx{"err": err})
		errors <- ErrProvider
		return
	}

	close(errors)
}

func (d *Driver) setPayPalErrorResponse(p *payment.Payment, resp *http.Response) {
	log := d.log.New(log15.Ctx{
		"method":    "setPayPalErrorResponse",
		"projectID": p.ProjectID(),
		"paymentID": p.ID(),
	})

	errBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error("error reading paypal error response", log15.Ctx{"err": err})
		return
	}

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeError,
	}
	paypalTx.Data = errBody
	err = InsertTransactionDB(d.ctx.PaymentDB(), paypalTx)
	if err != nil {
		log.Error("error saving paypal transaction", log15.Ctx{"err": err})
	}
}
<commit_msg>rework init transaction logging<commit_after>package paypal_rest

import (
	"code.google.com/p/godec/dec"
	"database/sql"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"gopkg.in/inconshreveable/log15.v2"

	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment_method"
)

const (
	paypalPaymentPath = "/v1/payments/payment"
)

func (d *Driver) InitPayment(p *payment.Payment, method *payment_method.Method) (http.Handler, error) {
	log := d.log.New(log15.Ctx{
		"method":          "InitPayment",
		"projectID":       p.ProjectID(),
		"paymentID":       p.ID(),
		"paymentMethodID": method.ID,
	})

	var tx *sql.Tx
	var err error
	var commit bool
	defer func() {
		if tx != nil && !commit {
			err = tx.Rollback()
			if err != nil {
				log.Crit("error on rollback", log15.Ctx{"err": err})
			}
		}
	}()
	tx, err = d.ctx.PaymentDB().Begin()
	if err != nil {
		commit = true
		log.Crit("error on begin tx", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	currentTx, err := TransactionCurrentByPaymentIDTx(tx, p.PaymentID())
	if err != nil && err != ErrTransactionNotFound {
		log.Error("error retrieving transaction", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}
	if err == nil {
		if Debug {
			log.Debug("already initialized payment")
		}
		switch currentTx.Type {
		case TransactionTypeError:
			return d.PaymentErrorHandler(p), nil
		default:
			return d.InitPageHandler(p), nil
		}
	}

	cfg, err := ConfigByPaymentMethodTx(tx, method)
	if err != nil {
		log.Error("error retrieving PayPal config", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	// create payment request
	req := &PayPalPaymentRequest{}
	if cfg.Type != "sale" && cfg.Type != "authorize" {
		log.Crit("invalid config type", log15.Ctx{"configType": cfg.Type})
		return nil, ErrInternal
	}
	req.Intent = cfg.Type
	req.Payer.PaymentMethod = PayPalPaymentMethodPayPal
	req.RedirectURLs, err = d.redirectURLs()
	if err != nil {
		log.Error("error creating redirect urls", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	req.Transactions = []PayPalTransaction{
		d.payPalTransactionFromPayment(p),
	}
	if Debug {
		log.Debug("created paypal payment request", log15.Ctx{"request": req})
	}

	endpoint, err := url.Parse(cfg.Endpoint)
	if err != nil {
		log.Error("error on endpoint URL", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	endpoint.Path = paypalPaymentPath

	jsonBytes, err := json.Marshal(req)
	if err != nil {
		log.Error("error encoding request", log15.Ctx{"err": err})
		return nil, ErrInternal
	}

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeCreatePayment,
	}
	paypalTx.Data = jsonBytes

	err = InsertTransactionTx(tx, paypalTx)
	if err != nil {
		log.Error("error saving transaction", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	commit = true
	err = tx.Commit()
	if err != nil {
		log.Crit("error on commit", log15.Ctx{"err": err})
		return nil, ErrDatabase
	}

	errors := make(chan error)
	go func() {
		for {
			select {
			case err := <-errors:
				if err == nil {
					return
				}
				log.Error("error on initializing", log15.Ctx{"err": err})
				return
			case <-d.ctx.Done():
				log.Warn("cancelled initialization", log15.Ctx{"err": d.ctx.Err()})
				return
			}
		}
	}()
	go d.doInit(errors, cfg, endpoint, p, string(jsonBytes))

	return d.InitPageHandler(p), nil
}

func (d *Driver) redirectURLs() (PayPalRedirectURLs, error) {
	u := PayPalRedirectURLs{}
	returnRoute, err := d.mux.Get("returnHandler").URLPath()
	if err != nil {
		return u, err
	}
	cancelRoute, err := d.mux.Get("cancelHandler").URLPath()
	if err != nil {
		return u, err
	}

	returnURL := &(*d.baseURL)
	returnURL.Path = returnRoute.Path
	u.ReturnURL = returnURL.String()

	cancelURL := &(*d.baseURL)
	cancelURL.Path = cancelRoute.Path
	u.CancelURL = cancelURL.String()

	return u, nil
}

func (d *Driver) payPalTransactionFromPayment(p *payment.Payment) PayPalTransaction {
	t := PayPalTransaction{}
	encPaymentID := d.paymentService.EncodedPaymentID(p.PaymentID())
	t.Custom = encPaymentID.String()
	t.InvoiceNumber = encPaymentID.String()
	amnt := &p.Decimal().Dec
	amnt.Round(amnt, dec.Scale(2), dec.RoundHalfUp)
	t.Amount = PayPalAmount{
		Currency: p.Currency,
		Total:    amnt.String(),
	}
	return t
}

func (d *Driver) doInit(errors chan<- error, cfg *Config, reqURL *url.URL, p *payment.Payment, body string) {
	log := d.log.New(log15.Ctx{
		"method":      "doInit",
		"projectID":   p.ProjectID(),
		"paymentID":   p.ID(),
		"methodKey":   cfg.MethodKey,
		"requestBody": body,
	})
	if Debug {
		log.Debug("posting...")
	}

	tr, err := d.oAuthTransport(log)(p, cfg)
	if err != nil {
		log.Error("error on auth transport", log15.Ctx{"err": err})
		errors <- err
		return
	}
	err = tr.AuthenticateClient()
	if err != nil {
		log.Error("error authenticating", log15.Ctx{"err": err})
		errors <- err
		return
	}
	cl := tr.Client()
	resp, err := cl.Post(reqURL.String(), "application/json", strings.NewReader(body))
	if err != nil {
		log.Error("error on HTTP POST", log15.Ctx{"err": err})
		errors <- err
		return
	}
	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		log.Error("error on HTTP request", log15.Ctx{"HTTPStatusCode": resp.StatusCode})
		d.setPayPalErrorResponse(p, nil)
		errors <- ErrHTTP
		return
	}
	respBody, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		log.Error("error reading response body", log15.Ctx{"err": err})
		d.setPayPalErrorResponse(p, nil)
		errors <- ErrHTTP
		return
	}
	log = log.New(log15.Ctx{"responseBody": body})
	if Debug {
		log.Debug("received response")
	}
	paypalP := &PaypalPayment{}
	err = json.Unmarshal(respBody, paypalP)
	if err != nil {
		log.Error("error decoding PayPal response", log15.Ctx{"err": err})
		d.setPayPalErrorResponse(p, respBody)
		errors <- ErrProvider
	}

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeCreatePaymentResponse,
	}
	if paypalP.ID != "" {
		paypalTx.SetPaypalID(paypalP.ID)
	}
	if paypalP.State != "" {
		paypalTx.SetState(paypalP.State)
	}
	if paypalP.CreateTime != "" {
		t, err := time.Parse(time.RFC3339, paypalP.CreateTime)
		if err != nil {
			log.Warn("error parsing paypal create time", log15.Ctx{"err": err})
		} else {
			paypalTx.PaypalCreateTime = &t
		}
	}
	if paypalP.UpdateTime != "" {
		t, err := time.Parse(time.RFC3339, paypalP.UpdateTime)
		if err != nil {
			log.Warn("error parsing paypal update time", log15.Ctx{"err": err})
		} else {
			paypalTx.PaypalUpdateTime = &t
		}
	}
	paypalTx.Links, err = json.Marshal(paypalP.Links)
	if err != nil {
		log.Error("error on saving links on response", log15.Ctx{"err": err})
		d.setPayPalErrorResponse(p, respBody)
		errors <- ErrProvider
		return
	}
	paypalTx.Data, err = json.Marshal(paypalP)
	if err != nil {
		log.Error("error marshalling paypal payment response", log15.Ctx{"err": err})
		d.setPayPalErrorResponse(p, respBody)
		errors <- ErrProvider
		return
	}
	err = InsertTransactionDB(d.ctx.PaymentDB(), paypalTx)
	if err != nil {
		log.Error("error saving paypal response", log15.Ctx{"err": err})
		d.setPayPalErrorResponse(p, respBody)
		errors <- ErrProvider
		return
	}

	close(errors)
}

func (d *Driver) setPayPalErrorResponse(p *payment.Payment, data []byte) {
	log := d.log.New(log15.Ctx{
		"method":    "setPayPalErrorResponse",
		"projectID": p.ProjectID(),
		"paymentID": p.ID(),
	})

	paypalTx := &Transaction{
		ProjectID: p.ProjectID(),
		PaymentID: p.ID(),
		Timestamp: time.Now(),
		Type:      TransactionTypeError,
	}
	paypalTx.Data = data
	err := InsertTransactionDB(d.ctx.PaymentDB(), paypalTx)
	if err != nil {
		log.Error("error saving paypal transaction", log15.Ctx{"err": err})
	}
}
<|endoftext|>


<commit_before>package notifiers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "slack",
		Name:        "Slack",
		Description: "Sends notifications to Slack via Slack Webhooks",
		Heading:     "Slack settings",
		Factory:     NewSlackNotifier,
		Options: []alerting.NotifierOption{
			{
				Label:        "Url",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Placeholder:  "Slack incoming webhook url",
				PropertyName: "url",
				Required:     true,
				Secure:       true,
			},
			{
				Label:        "Recipient",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Override default channel or user, use #channel-name, @username (has to be all lowercase, no whitespace), or user/channel Slack ID",
				PropertyName: "recipient",
			},
			{
				Label:        "Username",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Set the username for the bot's message",
				PropertyName: "username",
			},
			{
				Label:        "Icon emoji",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide an emoji to use as the icon for the bot's message. Overrides the icon URL.",
				PropertyName: "iconEmoji",
			},
			{
				Label:        "Icon URL",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide a URL to an image to use as the icon for the bot's message",
				PropertyName: "iconUrl",
			},
			{
				Label:        "Mention Users",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Mention one or more users (comma separated) when notifying in a channel, by ID (you can copy this from the user's Slack profile)",
				PropertyName: "mentionUsers",
			},
			{
				Label:        "Mention Groups",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Mention one or more groups (comma separated) when notifying in a channel (you can copy this from the group's Slack profile URL)",
				PropertyName: "mentionGroups",
			},
			{
				Label:   "Mention Channel",
				Element: alerting.ElementTypeSelect,
				SelectOptions: []alerting.SelectOption{
					{
						Value: "",
						Label: "Disabled",
					},
					{
						Value: "here",
						Label: "Every active channel member",
					},
					{
						Value: "channel",
						Label: "Every channel member",
					},
				},
				Description:  "Mention whole channel or just active members when notifying",
				PropertyName: "mentionChannel",
			},
			{
				Label:        "Token",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide a bot token to use the Slack file.upload API (starts with \"xoxb\"). Specify Recipient for this to work",
				PropertyName: "token",
				Secure:       true,
			},
		},
	})
}

var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*)|(#[^ .A-Z]{1,79})|([a-zA-Z0-9]+))$")

// NewSlackNotifier is the constructor for the Slack notifier
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
	url := model.DecryptedValue("url", model.Settings.Get("url").MustString())
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	recipient := strings.TrimSpace(model.Settings.Get("recipient").MustString())
	if recipient != "" && !reRecipient.MatchString(recipient) {
		return nil, alerting.ValidationError{Reason: fmt.Sprintf("Recipient on invalid format: %q", recipient)}
	}
	username := model.Settings.Get("username").MustString()
	iconEmoji := model.Settings.Get("icon_emoji").MustString()
	iconURL := model.Settings.Get("icon_url").MustString()
	mentionUsersStr := model.Settings.Get("mentionUsers").MustString()
	mentionGroupsStr := model.Settings.Get("mentionGroups").MustString()
	mentionChannel := model.Settings.Get("mentionChannel").MustString()
	token := model.DecryptedValue("token", model.Settings.Get("token").MustString())

	uploadImage := model.Settings.Get("uploadImage").MustBool(true)

	if mentionChannel != "" && mentionChannel != "here" && mentionChannel != "channel" {
		return nil, alerting.ValidationError{
			Reason: fmt.Sprintf("Invalid value for mentionChannel: %q", mentionChannel),
		}
	}
	mentionUsers := []string{}
	for _, u := range strings.Split(mentionUsersStr, ",") {
		u = strings.TrimSpace(u)
		if u != "" {
			mentionUsers = append(mentionUsers, u)
		}
	}
	mentionGroups := []string{}
	for _, g := range strings.Split(mentionGroupsStr, ",") {
		g = strings.TrimSpace(g)
		if g != "" {
			mentionGroups = append(mentionGroups, g)
		}
	}

	return &SlackNotifier{
		NotifierBase:   NewNotifierBase(model),
		URL:            url,
		Recipient:      recipient,
		Username:       username,
		IconEmoji:      iconEmoji,
		IconURL:        iconURL,
		MentionUsers:   mentionUsers,
		MentionGroups:  mentionGroups,
		MentionChannel: mentionChannel,
		Token:          token,
		Upload:         uploadImage,
		log:            log.New("alerting.notifier.slack"),
	}, nil
}

// SlackNotifier is responsible for sending
// alert notification to Slack.
type SlackNotifier struct {
	NotifierBase
	URL            string
	Recipient      string
	Username       string
	IconEmoji      string
	IconURL        string
	MentionUsers   []string
	MentionGroups  []string
	MentionChannel string
	Token          string
	Upload         bool
	log            log.Logger
}

// Notify send alert notification to Slack.
func (sn *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
	sn.log.Info("Executing slack notification", "ruleId", evalContext.Rule.ID, "notification", sn.Name)

	ruleURL, err := evalContext.GetRuleURL()
	if err != nil {
		sn.log.Error("Failed get rule link", "error", err)
		return err
	}

	fields := make([]map[string]interface{}, 0)
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"title": evt.Metric,
			"value": evt.Value,
			"short": true,
		})
		if index > fieldLimitCount {
			break
		}
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"title": "Error message",
			"value": evalContext.Error.Error(),
			"short": false,
		})
	}

	mentionsBuilder := strings.Builder{}
	appendSpace := func() {
		if mentionsBuilder.Len() > 0 {
			mentionsBuilder.WriteString(" ")
		}
	}
	mentionChannel := strings.TrimSpace(sn.MentionChannel)
	if mentionChannel != "" {
		mentionsBuilder.WriteString(fmt.Sprintf("<!%s|%s>", mentionChannel, mentionChannel))
	}
	if len(sn.MentionGroups) > 0 {
		appendSpace()
		for _, g := range sn.MentionGroups {
			mentionsBuilder.WriteString(fmt.Sprintf("<!subteam^%s>", g))
		}
	}
	if len(sn.MentionUsers) > 0 {
		appendSpace()
		for _, u := range sn.MentionUsers {
			mentionsBuilder.WriteString(fmt.Sprintf("<@%s>", u))
		}
	}
	msg := ""
	if evalContext.Rule.State != models.AlertStateOK { // don't add message when going back to alert state ok.
		msg = evalContext.Rule.Message
	}
	imageURL := ""
	// default to file.upload API method if a token is provided
	if sn.Token == "" {
		imageURL = evalContext.ImagePublicURL
	}

	var blocks []map[string]interface{}
	if mentionsBuilder.Len() > 0 {
		blocks = []map[string]interface{}{
			{
				"type": "section",
				"text": map[string]interface{}{
					"type": "mrkdwn",
					"text": mentionsBuilder.String(),
				},
			},
		}
	}
	attachment := map[string]interface{}{
		"color":       evalContext.GetStateModel().Color,
		"title":       evalContext.GetNotificationTitle(),
		"title_link":  ruleURL,
		"text":        msg,
		"fallback":    evalContext.GetNotificationTitle(),
		"fields":      fields,
		"footer":      "Grafana v" + setting.BuildVersion,
		"footer_icon": "https://grafana.com/assets/img/fav32.png",
		"ts":          time.Now().Unix(),
	}
	if sn.NeedsImage() && imageURL != "" {
		attachment["image_url"] = imageURL
	}
	body := map[string]interface{}{
		"text": evalContext.GetNotificationTitle(),
		"attachments": []map[string]interface{}{
			attachment,
		},
	}
	if len(blocks) > 0 {
		body["blocks"] = blocks
	}

	// recipient override
	if sn.Recipient != "" {
		body["channel"] = sn.Recipient
	}
	if sn.Username != "" {
		body["username"] = sn.Username
	}
	if sn.IconEmoji != "" {
		body["icon_emoji"] = sn.IconEmoji
	}
	if sn.IconURL != "" {
		body["icon_url"] = sn.IconURL
	}
	data, err := json.Marshal(&body)
	if err != nil {
		return err
	}

	cmd := &models.SendWebhookSync{
		Url:        sn.URL,
		Body:       string(data),
		HttpMethod: http.MethodPost,
	}
	if sn.Token != "" {
		sn.log.Debug("Adding authorization header to HTTP request")
		cmd.HttpHeader = map[string]string{
			"Authorization": fmt.Sprintf("Bearer %s", sn.Token),
		}
	}
	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		sn.log.Error("Failed to send slack notification", "error", err, "webhook", sn.Name)
		return err
	}
	if sn.Token != "" && sn.UploadImage {
		err = sn.slackFileUpload(evalContext, sn.log, "https://slack.com/api/files.upload", sn.Recipient, sn.Token)
		if err != nil {
			return err
		}
	}
	return nil
}

func (sn *SlackNotifier) slackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error {
	if evalContext.ImageOnDiskPath == "" {
		// nolint:gosec
		// We can ignore the gosec G304 warning on this one because `setting.HomePath` comes from Grafana's configuration file.
		evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png")
	}
	log.Info("Uploading to slack via file.upload API")
	headers, uploadBody, err := sn.generateSlackBody(evalContext.ImageOnDiskPath, token, recipient)
	if err != nil {
		return err
	}
	cmd := &models.SendWebhookSync{Url: url, Body: uploadBody.String(), HttpHeader: headers, HttpMethod: "POST"}
	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		log.Error("Failed to upload slack image", "error", err, "webhook", "file.upload")
		return err
	}
	return nil
}

func (sn *SlackNotifier) generateSlackBody(path string, token string, recipient string) (map[string]string, bytes.Buffer, error) {
	// Slack requires all POSTs to files.upload to present
	// an "application/x-www-form-urlencoded" encoded querystring
	// See https://api.slack.com/methods/files.upload
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	defer func() {
		if err := w.Close(); err != nil {
			// Shouldn't matter since we already close w explicitly on the non-error path
			sn.log.Warn("Failed to close multipart writer", "err", err)
		}
	}()

	// Add the generated image file
	// We can ignore the gosec G304 warning on this one because `imagePath` comes
	// from the alert `evalContext` that generates the images. `evalContext` in turn derives the root of the file
	// path from configuration variables.
	// nolint:gosec
	f, err := os.Open(path)
	if err != nil {
		return nil, b, err
	}
	defer func() {
		if err := f.Close(); err != nil {
			sn.log.Warn("Failed to close file", "path", path, "err", err)
		}
	}()
	fw, err := w.CreateFormFile("file", path)
	if err != nil {
		return nil, b, err
	}
	if _, err := io.Copy(fw, f); err != nil {
		return nil, b, err
	}
	// Add the authorization token
	if err := w.WriteField("token", token); err != nil {
		return nil, b, err
	}
	// Add the channel(s) to POST to
	if err := w.WriteField("channels", recipient); err != nil {
		return nil, b, err
	}
	if err := w.Close(); err != nil {
		return nil, b, fmt.Errorf("failed to close multipart writer: %w", err)
	}
	headers := map[string]string{
		"Content-Type":  w.FormDataContentType(),
		"Authorization": "auth_token=\"" + token + "\"",
	}
	return headers, b, nil
}
<commit_msg>Remove field limitation from slack notification (#33113)<commit_after>package notifiers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "slack",
		Name:        "Slack",
		Description: "Sends notifications to Slack via Slack Webhooks",
		Heading:     "Slack settings",
		Factory:     NewSlackNotifier,
		Options: []alerting.NotifierOption{
			{
				Label:        "Url",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Placeholder:  "Slack incoming webhook url",
				PropertyName: "url",
				Required:     true,
				Secure:       true,
			},
			{
				Label:        "Recipient",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Override default channel or user, use #channel-name, @username (has to be all lowercase, no whitespace), or user/channel Slack ID",
				PropertyName: "recipient",
			},
			{
				Label:        "Username",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Set the username for the bot's message",
				PropertyName: "username",
			},
			{
				Label:        "Icon emoji",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide an emoji to use as the icon for the bot's message. Overrides the icon URL.",
				PropertyName: "iconEmoji",
			},
			{
				Label:        "Icon URL",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide a URL to an image to use as the icon for the bot's message",
				PropertyName: "iconUrl",
			},
			{
				Label:        "Mention Users",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Mention one or more users (comma separated) when notifying in a channel, by ID (you can copy this from the user's Slack profile)",
				PropertyName: "mentionUsers",
			},
			{
				Label:        "Mention Groups",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Mention one or more groups (comma separated) when notifying in a channel (you can copy this from the group's Slack profile URL)",
				PropertyName: "mentionGroups",
			},
			{
				Label:   "Mention Channel",
				Element: alerting.ElementTypeSelect,
				SelectOptions: []alerting.SelectOption{
					{
						Value: "",
						Label: "Disabled",
					},
					{
						Value: "here",
						Label: "Every active channel member",
					},
					{
						Value: "channel",
						Label: "Every channel member",
					},
				},
				Description:  "Mention whole channel or just active members when notifying",
				PropertyName: "mentionChannel",
			},
			{
				Label:        "Token",
				Element:      alerting.ElementTypeInput,
				InputType:    alerting.InputTypeText,
				Description:  "Provide a bot token to use the Slack file.upload API (starts with \"xoxb\"). Specify Recipient for this to work",
				PropertyName: "token",
				Secure:       true,
			},
		},
	})
}

var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*)|(#[^ .A-Z]{1,79})|([a-zA-Z0-9]+))$")

// NewSlackNotifier is the constructor for the Slack notifier
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
	url := model.DecryptedValue("url", model.Settings.Get("url").MustString())
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	recipient := strings.TrimSpace(model.Settings.Get("recipient").MustString())
	if recipient != "" && !reRecipient.MatchString(recipient) {
		return nil, alerting.ValidationError{Reason: fmt.Sprintf("Recipient on invalid format: %q", recipient)}
	}
	username := model.Settings.Get("username").MustString()
	iconEmoji := model.Settings.Get("icon_emoji").MustString()
	iconURL := model.Settings.Get("icon_url").MustString()
	mentionUsersStr := model.Settings.Get("mentionUsers").MustString()
	mentionGroupsStr := model.Settings.Get("mentionGroups").MustString()
	mentionChannel := model.Settings.Get("mentionChannel").MustString()
	token := model.DecryptedValue("token", model.Settings.Get("token").MustString())

	uploadImage := model.Settings.Get("uploadImage").MustBool(true)

	if mentionChannel != "" && mentionChannel != "here" && mentionChannel != "channel" {
		return nil, alerting.ValidationError{
			Reason: fmt.Sprintf("Invalid value for mentionChannel: %q", mentionChannel),
		}
	}
	mentionUsers := []string{}
	for _, u := range strings.Split(mentionUsersStr, ",") {
		u = strings.TrimSpace(u)
		if u != "" {
			mentionUsers = append(mentionUsers, u)
		}
	}
	mentionGroups := []string{}
	for _, g := range strings.Split(mentionGroupsStr, ",") {
		g = strings.TrimSpace(g)
		if g != "" {
			mentionGroups = append(mentionGroups, g)
		}
	}

	return &SlackNotifier{
		NotifierBase:   NewNotifierBase(model),
		URL:            url,
		Recipient:      recipient,
		Username:       username,
		IconEmoji:      iconEmoji,
		IconURL:        iconURL,
		MentionUsers:   mentionUsers,
		MentionGroups:  mentionGroups,
		MentionChannel: mentionChannel,
		Token:          token,
		Upload:         uploadImage,
		log:            log.New("alerting.notifier.slack"),
	}, nil
}

// SlackNotifier is responsible for sending
// alert notification to Slack.
type SlackNotifier struct {
	NotifierBase
	URL            string
	Recipient      string
	Username       string
	IconEmoji      string
	IconURL        string
	MentionUsers   []string
	MentionGroups  []string
	MentionChannel string
	Token          string
	Upload         bool
	log            log.Logger
}

// Notify send alert notification to Slack.
func (sn *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
	sn.log.Info("Executing slack notification", "ruleId", evalContext.Rule.ID, "notification", sn.Name)

	ruleURL, err := evalContext.GetRuleURL()
	if err != nil {
		sn.log.Error("Failed get rule link", "error", err)
		return err
	}

	fields := make([]map[string]interface{}, 0)
	for _, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"title": evt.Metric,
			"value": evt.Value,
			"short": true,
		})
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"title": "Error message",
			"value": evalContext.Error.Error(),
			"short": false,
		})
	}

	mentionsBuilder := strings.Builder{}
	appendSpace := func() {
		if mentionsBuilder.Len() > 0 {
			mentionsBuilder.WriteString(" ")
		}
	}
	mentionChannel := strings.TrimSpace(sn.MentionChannel)
	if mentionChannel != "" {
		mentionsBuilder.WriteString(fmt.Sprintf("<!%s|%s>", mentionChannel, mentionChannel))
	}
	if len(sn.MentionGroups) > 0 {
		appendSpace()
		for _, g := range sn.MentionGroups {
			mentionsBuilder.WriteString(fmt.Sprintf("<!subteam^%s>", g))
		}
	}
	if len(sn.MentionUsers) > 0 {
		appendSpace()
		for _, u := range sn.MentionUsers {
			mentionsBuilder.WriteString(fmt.Sprintf("<@%s>", u))
		}
	}
	msg := ""
	if evalContext.Rule.State != models.AlertStateOK { // don't add message when going back to alert state ok.
		msg = evalContext.Rule.Message
	}
	imageURL := ""
	// default to file.upload API method if a token is provided
	if sn.Token == "" {
		imageURL = evalContext.ImagePublicURL
	}

	var blocks []map[string]interface{}
	if mentionsBuilder.Len() > 0 {
		blocks = []map[string]interface{}{
			{
				"type": "section",
				"text": map[string]interface{}{
					"type": "mrkdwn",
					"text": mentionsBuilder.String(),
				},
			},
		}
	}
	attachment := map[string]interface{}{
		"color":       evalContext.GetStateModel().Color,
		"title":       evalContext.GetNotificationTitle(),
		"title_link":  ruleURL,
		"text":        msg,
		"fallback":    evalContext.GetNotificationTitle(),
		"fields":      fields,
		"footer":      "Grafana v" + setting.BuildVersion,
		"footer_icon": "https://grafana.com/assets/img/fav32.png",
		"ts":          time.Now().Unix(),
	}
	if sn.NeedsImage() && imageURL != "" {
		attachment["image_url"] = imageURL
	}
	body := map[string]interface{}{
		"text": evalContext.GetNotificationTitle(),
		"attachments": []map[string]interface{}{
			attachment,
		},
	}
	if len(blocks) > 0 {
		body["blocks"] = blocks
	}

	// recipient override
	if sn.Recipient != "" {
		body["channel"] = sn.Recipient
	}
	if sn.Username != "" {
		body["username"] = sn.Username
	}
	if sn.IconEmoji != "" {
		body["icon_emoji"] = sn.IconEmoji
	}
	if sn.IconURL != "" {
		body["icon_url"] = sn.IconURL
	}
	data, err := json.Marshal(&body)
	if err != nil {
		return err
	}

	cmd := &models.SendWebhookSync{
		Url:        sn.URL,
		Body:       string(data),
		HttpMethod: http.MethodPost,
	}
	if sn.Token != "" {
		sn.log.Debug("Adding authorization header to HTTP request")
		cmd.HttpHeader = map[string]string{
			"Authorization": fmt.Sprintf("Bearer %s", sn.Token),
		}
	}
	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		sn.log.Error("Failed to send slack notification", "error", err, "webhook", sn.Name)
		return err
	}
	if sn.Token != "" && sn.UploadImage {
		err = sn.slackFileUpload(evalContext, sn.log, "https://slack.com/api/files.upload", sn.Recipient, sn.Token)
		if err != nil {
			return err
		}
	}
	return nil
}

func (sn *SlackNotifier) slackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error {
	if evalContext.ImageOnDiskPath == "" {
		// nolint:gosec
		// We can ignore the gosec G304 warning on this one because `setting.HomePath` comes from Grafana's configuration file.
		evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png")
	}
	log.Info("Uploading to slack via file.upload API")
	headers, uploadBody, err := sn.generateSlackBody(evalContext.ImageOnDiskPath, token, recipient)
	if err != nil {
		return err
	}
	cmd := &models.SendWebhookSync{Url: url, Body: uploadBody.String(), HttpHeader: headers, HttpMethod: "POST"}
	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		log.Error("Failed to upload slack image", "error", err, "webhook", "file.upload")
		return err
	}
	return nil
}

func (sn *SlackNotifier) generateSlackBody(path string, token string, recipient string) (map[string]string, bytes.Buffer, error) {
	// Slack requires all POSTs to files.upload to present
	// an "application/x-www-form-urlencoded" encoded querystring
	// See https://api.slack.com/methods/files.upload
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	defer func() {
		if err := w.Close(); err != nil {
			// Shouldn't matter since we already close w explicitly on the non-error path
			sn.log.Warn("Failed to close multipart writer", "err", err)
		}
	}()

	// Add the generated image file
	// We can ignore the gosec G304 warning on this one because `imagePath` comes
	// from the alert `evalContext` that generates the images. `evalContext` in turn derives the root of the file
	// path from configuration variables.
	// nolint:gosec
	f, err := os.Open(path)
	if err != nil {
		return nil, b, err
	}
	defer func() {
		if err := f.Close(); err != nil {
			sn.log.Warn("Failed to close file", "path", path, "err", err)
		}
	}()
	fw, err := w.CreateFormFile("file", path)
	if err != nil {
		return nil, b, err
	}
	if _, err := io.Copy(fw, f); err != nil {
		return nil, b, err
	}
	// Add the authorization token
	if err := w.WriteField("token", token); err != nil {
		return nil, b, err
	}
	// Add the channel(s) to POST to
	if err := w.WriteField("channels", recipient); err != nil {
		return nil, b, err
	}
	if err := w.Close(); err != nil {
		return nil, b, fmt.Errorf("failed to close multipart writer: %w", err)
	}
	headers := map[string]string{
		"Content-Type":  w.FormDataContentType(),
		"Authorization": "auth_token=\"" + token + "\"",
	}
	return headers, b, nil
}
<|endoftext|>


<commit_before>package main

var IGSModels map[string]string = map[string]string{
	"Chokering (TurboRogue)":        "AOAD/M_T",
	"Ashtech chokering 700936B":     "ASH700936B_M",
	"Ashtech chokering 700936C":     "ASH700936C_M",
	"Ashtech chokering 700936D":     "ASH700936D_M",
	"Ashtech chokering 700936E":     "ASH700936E",
	"Ashtech chokering 701933B":     "ASH701933C_M",
	"Ashtech chokering 701945C":     "ASH701945B_M",
	"Ashtech chokering 701945E":     "ASH701945E_M",
	"Ashtech MicroZ":                "ASHTECH MICROZ",
	"Ashtech Z-XII3":                "ASHTECH Z-XII3",
	"Chokering AT504":               "LEIAT504",
	"CRS1000":                       "LEICA CRS1000",
	"GRX1200GGPRO":                  "LEICA GRX1200GGPRO",
	"RS500":                         "LEICA RS500",
	"Rogue SNR-8000":                "ROGUE SNR-8000",
	"4000SSE":                       "TRIMBLE 4000SSE",
	"4000SSI":                       "TRIMBLE 4000SSI",
	"5700":                          "TRIMBLE 5700",
	"NetR5 GNSS Receiver":           "TRIMBLE NETR5",
	"NetR9":                         "TRIMBLE NETR9",
	"NetRS":                         "TRIMBLE NETRS",
	"TRM22020.00+GP":                "TRM22020.00+GP",
	"Chokering Model 29659.00":      "TRM29659.00",
	"Zephyr - Stealth Ground Plane": "TRM41249.00",
	"Zephyr 2a (55971-00)":          "TRM57971.00",
	"Zephyr 2b (57971-00)":          "TRM55971.00",
	"LEIS Radome":                   "LEIS",
	"SNOW Radome":                   "SNOW",
	"SCIS Radome":                   "SCIS",
}
<commit_msg>correct igs.go based on IGS rcvr_ant.tab<commit_after>package main

var IGSModels map[string]string = map[string]string{
	"Chokering (TurboRogue)":        "AOAD/M_T",
	"Ashtech chokering 700936B":     "ASH700936B_M",
	"Ashtech chokering 700936C":     "ASH700936C_M",
	"Ashtech chokering 700936D":     "ASH700936D_M",
	"Ashtech chokering 700936E":     "ASH700936E",
	"Ashtech chokering 701933B":     "ASH701933B_M",
	"Ashtech chokering 701933C":     "ASH701933C_M",
	"Ashtech chokering 701945B":     "ASH701945B_M",
	"Ashtech chokering 701945C":     "ASH701945C_M",
	"Ashtech chokering 701945E":     "ASH701945E_M",
	"Ashtech MicroZ":                "ASHTECH MICROZ",
	"Ashtech Z-XII3":                "ASHTECH Z-XII3",
	"Chokering AT504":               "LEIAT504",
	"CRS1000":                       "LEICA CRS1000",
	"GRX1200GGPRO":                  "LEICA GRX1200GGPRO",
	"RS500":                         "LEICA RS500",
	"Rogue SNR-8000":                "ROGUE SNR-8000",
	"4000SSE":                       "TRIMBLE 4000SSE",
	"4000SSI":                       "TRIMBLE 4000SSI",
	"5700":                          "TRIMBLE 5700",
	"NetR5 GNSS Receiver":           "TRIMBLE NETR5",
	"NetR9":                         "TRIMBLE NETR9",
	"NetRS":                         "TRIMBLE NETRS",
	"TRM22020.00+GP":                "TRM22020.00+GP",
	"Chokering Model 29659.00":      "TRM29659.00",
	"Zephyr - Stealth Ground Plane": "TRM41249.00",
	"Zephyr 2a (55971-00)":          "TRM55971.00",
	"Zephyr 2b (57971-00)":          "TRM57971.00",
	"LEIS Radome":                   "LEIS",
	"SNOW Radome":                   "SNOW",
	"SCIS Radome":                   "SCIS",
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Nuclio 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 resource

import (
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/nuclio/nuclio/pkg/dashboard"
	"github.com/nuclio/nuclio/pkg/platform"
	"github.com/nuclio/nuclio/pkg/restful"
)

type invocationResource struct {
	*resource
	nodeAddresses []string
}

// called after initialization
func (tr *invocationResource) OnAfterInitialize() error {

	// all methods
	for _, registrar := range []func(string, http.HandlerFunc){
		tr.GetRouter().Get,
		tr.GetRouter().Post,
		tr.GetRouter().Put,
		tr.GetRouter().Delete,
		tr.GetRouter().Patch,
		tr.GetRouter().Options,
	} {
		registrar("/*", tr.handleRequest)
	}

	return nil
}

func (tr *invocationResource) handleRequest(responseWriter http.ResponseWriter, request *http.Request) {
	path := request.Header.Get("x-nuclio-path")
	functionName := request.Header.Get("x-nuclio-function-name")
	invokeVia := tr.getInvokeVia(request.Header.Get("x-nuclio-invoke-via"))

	// get namespace from request or use the provided default
	functionNamespace := tr.getNamespaceOrDefault(request.Header.Get("x-nuclio-function-namespace"))

	// if user prefixed path with "/", remove it
	path = strings.TrimLeft(path, "/")

	if functionName == "" || functionNamespace == "" {
		responseWriter.WriteHeader(http.StatusBadRequest)
		responseWriter.Write([]byte(`{"error": "Function name must be provided"}`)) // nolint: errcheck
		return
	}

	requestBody, err := ioutil.ReadAll(request.Body)
	if err != nil {
		responseWriter.WriteHeader(http.StatusInternalServerError)
		responseWriter.Write([]byte(`{"error": "Failed to read request body"}`)) // nolint: errcheck
		return
	}

	// resolve the function host
	invocationResult, err := tr.getPlatform().CreateFunctionInvocation(&platform.CreateFunctionInvocationOptions{
		Name:      functionName,
		Namespace: functionNamespace,
		Path:      path,
		Method:    request.Method,
		Headers:   request.Header,
		Body:      requestBody,
		Via:       invokeVia,
	})

	if err != nil {
		tr.Logger.WarnWith("Failed to invoke function", "err", err)

		responseWriter.WriteHeader(http.StatusInternalServerError)
		responseWriter.Write([]byte(`{"error": "Failed to invoke function"}`)) // nolint: errcheck
		return
	}

	// set headers
	for headerName, headerValue := range invocationResult.Headers {

		// don't send nuclio headers to the actual function
		if !strings.HasPrefix(headerName, "x-nuclio") {
			responseWriter.Header().Set(headerName, headerValue[0])
		}
	}

	responseWriter.Header().Set("Content-Type", "application/json")
	responseWriter.WriteHeader(invocationResult.StatusCode)
	responseWriter.Write(invocationResult.Body) // nolint: errcheck
}

func (tr *invocationResource) getInvokeVia(invokeViaName string) platform.InvokeViaType {
	switch invokeViaName {
	// erd: For now, if the UI asked for external IP, force using "via any". "Any" should try external IP
	// and then domain name, which is better
	// case "external-ip":
	// 	 return platform.InvokeViaExternalIP
	case "loadbalancer":
		return platform.InvokeViaLoadBalancer
	case "domain-name":
		return platform.InvokeViaDomainName
	default:
		return platform.InvokeViaAny
	}
}

// register the resource
var invocationResourceInstance = &invocationResource{
	resource: newResource("api/function_invocations", []restful.ResourceMethod{}),
}

func init() {
	invocationResourceInstance.Resource = invocationResourceInstance
	invocationResourceInstance.Register(dashboard.DashboardResourceRegistrySingleton)
}
<commit_msg>Fix content type on invocation response (#1087)<commit_after>/*
Copyright 2017 The Nuclio 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 resource

import (
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/nuclio/nuclio/pkg/dashboard"
	"github.com/nuclio/nuclio/pkg/platform"
	"github.com/nuclio/nuclio/pkg/restful"
)

type invocationResource struct {
	*resource
	nodeAddresses []string
}

// called after initialization
func (tr *invocationResource) OnAfterInitialize() error {

	// all methods
	for _, registrar := range []func(string, http.HandlerFunc){
		tr.GetRouter().Get,
		tr.GetRouter().Post,
		tr.GetRouter().Put,
		tr.GetRouter().Delete,
		tr.GetRouter().Patch,
		tr.GetRouter().Options,
	} {
		registrar("/*", tr.handleRequest)
	}

	return nil
}

func (tr *invocationResource) handleRequest(responseWriter http.ResponseWriter, request *http.Request) {
	path := request.Header.Get("x-nuclio-path")
	functionName := request.Header.Get("x-nuclio-function-name")
	invokeVia := tr.getInvokeVia(request.Header.Get("x-nuclio-invoke-via"))

	// get namespace from request or use the provided default
	functionNamespace := tr.getNamespaceOrDefault(request.Header.Get("x-nuclio-function-namespace"))

	// if user prefixed path with "/", remove it
	path = strings.TrimLeft(path, "/")

	if functionName == "" || functionNamespace == "" {
		tr.writeErrorHeader(responseWriter, http.StatusBadRequest)
		responseWriter.Write([]byte(`{"error": "Function name must be provided"}`)) // nolint: errcheck
		return
	}

	requestBody, err := ioutil.ReadAll(request.Body)
	if err != nil {
		tr.writeErrorHeader(responseWriter, http.StatusInternalServerError)
		responseWriter.Write([]byte(`{"error": "Failed to read request body"}`)) // nolint: errcheck
		return
	}

	// resolve the function host
	invocationResult, err := tr.getPlatform().CreateFunctionInvocation(&platform.CreateFunctionInvocationOptions{
		Name:      functionName,
		Namespace: functionNamespace,
		Path:      path,
		Method:    request.Method,
		Headers:   request.Header,
		Body:      requestBody,
		Via:       invokeVia,
	})

	if err != nil {
		tr.Logger.WarnWith("Failed to invoke function", "err", err)

		tr.writeErrorHeader(responseWriter, http.StatusInternalServerError)
		responseWriter.Write([]byte(`{"error": "Failed to invoke function"}`)) // nolint: errcheck
		return
	}

	// set headers
	for headerName, headerValue := range invocationResult.Headers {

		// don't send nuclio headers to the actual function
		if !strings.HasPrefix(headerName, "x-nuclio") {
			responseWriter.Header().Set(headerName, headerValue[0])
		}
	}

	responseWriter.WriteHeader(invocationResult.StatusCode)
	responseWriter.Write(invocationResult.Body) // nolint: errcheck
}

func (tr *invocationResource) getInvokeVia(invokeViaName string) platform.InvokeViaType {
	switch invokeViaName {
	// erd: For now, if the UI asked for external IP, force using "via any". "Any" should try external IP
	// and then domain name, which is better
	// case "external-ip":
	// 	 return platform.InvokeViaExternalIP
	case "loadbalancer":
		return platform.InvokeViaLoadBalancer
	case "domain-name":
		return platform.InvokeViaDomainName
	default:
		return platform.InvokeViaAny
	}
}

func (tr *invocationResource) writeErrorHeader(responseWriter http.ResponseWriter, statusCode int) {
	responseWriter.Header().Set("Content-Type", "application/json")
	responseWriter.WriteHeader(statusCode)
}

// register the resource
var invocationResourceInstance = &invocationResource{
	resource: newResource("api/function_invocations", []restful.ResourceMethod{}),
}

func init() {
	invocationResourceInstance.Resource = invocationResourceInstance
	invocationResourceInstance.Register(dashboard.DashboardResourceRegistrySingleton)
}
<|endoftext|>