<commit_before>package idp

import (
	"crypto/rsa"
	"net/http"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gorilla/sessions"
	hclient "github.com/ory-am/hydra/client"
	hjwk "github.com/ory-am/hydra/jwk"
	hoauth2 "github.com/ory-am/hydra/oauth2"
	hydra "github.com/ory-am/hydra/sdk"
	"github.com/patrickmn/go-cache"
)

const (
	VerifyPublicKey   = "VerifyPublic"
	ConsentPrivateKey = "ConsentPrivate"
)

func ClientInfoKey(clientID string) string {
	return "ClientInfo:" + clientID
}

var encryptionkey = "something-very-secret"

// Identity Provider's options
type IDPConfig struct {
	// Client id issued by Hydra
	ClientID string `yaml:"client_id"`

	// Client secret issued by Hydra
	ClientSecret string `yaml:"client_secret"`

	// Hydra's address
	ClusterURL string `yaml:"hydra_address"`

	// Expiration time of internal key cache
	KeyCacheExpiration time.Duration `yaml:"key_cache_expiration"`

	// Expiration time of internal clientid cache
	ClientCacheExpiration time.Duration `yaml:"client_cache_expiration"`

	// Internal cache cleanup interval
	CacheCleanupInterval time.Duration `yaml:"cache_cleanup_interval"`

	// Expiration time of internal clientid cache
	ChallengeExpiration time.Duration `yaml:"challenge_expiration"`

	// Gorilla sessions Store for storing the Challenge.
	ChallengeStore sessions.Store
}

// Identity Provider helper
type IDP struct {
	config *IDPConfig

	// Communication with Hydra
	hc *hydra.Client

	// Http client for communicating with Hydra
	client *http.Client

	// Cache for all private and public keys
	cache *cache.Cache

	// Prepared cookie options for creating and deleting cookies
	createChallengeCookieOptions *sessions.Options
	deleteChallengeCookieOptions *sessions.Options
}

// Create the Identity Provider helper
func NewIDP(config *IDPConfig) *IDP {
	var idp = new(IDP)
	idp.config = config

	idp.cache = cache.New(config.KeyCacheExpiration, config.CacheCleanupInterval)
	idp.cache.OnEvicted(func(key string, value interface{}) { idp.refreshCache(key) })

	idp.createChallengeCookieOptions = new(sessions.Options)
	idp.createChallengeCookieOptions.Path = "/"
	idp.createChallengeCookieOptions.MaxAge = int(config.ChallengeExpiration.Seconds())
	idp.createChallengeCookieOptions.Secure = true // Send only via https
	idp.createChallengeCookieOptions.HttpOnly = false

	idp.deleteChallengeCookieOptions = new(sessions.Options)
	idp.deleteChallengeCookieOptions.Path = "/"
	idp.deleteChallengeCookieOptions.MaxAge = -1   // Mark for deletion
	idp.deleteChallengeCookieOptions.Secure = true // Send only via https
	idp.deleteChallengeCookieOptions.HttpOnly = false

	return idp
}

func (idp *IDP) cacheConsentKey() error {
	consentKey, err := idp.downloadConsentKey()

	duration := cache.DefaultExpiration
	if err != nil {
		// re-cache the result even if there's an error, but
		// do it with a shorter timeout. This will ensure we
		// try to refresh the key once that timeout expires,
		// otherwise we'll _never_ refresh the key again.
		duration = idp.config.CacheCleanupInterval
	}

	idp.cache.Set(ConsentPrivateKey, consentKey, duration)
	return err
}

func (idp *IDP) cacheVerificationKey() error {
	verifyKey, err := idp.downloadVerificationKey()

	duration := cache.DefaultExpiration
	if err != nil {
		// re-cache the result even if there's an error, but
		// do it with a shorter timeout. This will ensure we
		// try to refresh the key once that timeout expires,
		// otherwise we'll _never_ refresh the key again.
		duration = idp.config.CacheCleanupInterval
	}

	idp.cache.Set(VerifyPublicKey, verifyKey, duration)
	return err
}

// Called when any key expires
func (idp *IDP) refreshCache(key string) {
	switch key {
	case VerifyPublicKey:
		idp.cacheVerificationKey()
		return

	case ConsentPrivateKey:
		idp.cacheConsentKey()
		return

	default:
		// Will get here for client IDs.
		// Fine to just let them expire, the next request from that
		// client will trigger a refresh
		return
	}
}

// Downloads the hydra's public key
func (idp *IDP) downloadVerificationKey() (*rsa.PublicKey, error) {

	jwk, err := idp.hc.JWK.GetKey(hoauth2.ConsentChallengeKey, "public")
	if err != nil {
		return nil, err
	}

	rsaKey, ok := hjwk.First(jwk.Keys).Key.(*rsa.PublicKey)
	if !ok {
		return nil, ErrorBadPublicKey
	}

	return rsaKey, nil
}

// Downloads the private key used for signing the consent
func (idp *IDP) downloadConsentKey() (*rsa.PrivateKey, error) {
	jwk, err := idp.hc.JWK.GetKey(hoauth2.ConsentEndpointKey, "private")
	if err != nil {
		return nil, err
	}

	rsaKey, ok := hjwk.First(jwk.Keys).Key.(*rsa.PrivateKey)
	if !ok {
		return nil, ErrorBadPrivateKey
	}

	return rsaKey, nil
}

// Connect to Hydra
func (idp *IDP) Connect(verifyTLS bool) error {
	var err error
	if verifyTLS {
		idp.hc, err = hydra.Connect(
			hydra.ClientID(idp.config.ClientID),
			hydra.ClientSecret(idp.config.ClientSecret),
			hydra.ClusterURL(idp.config.ClusterURL),
		)
	} else {
		idp.hc, err = hydra.Connect(
			hydra.ClientID(idp.config.ClientID),
			hydra.ClientSecret(idp.config.ClientSecret),
			hydra.ClusterURL(idp.config.ClusterURL),
			hydra.SkipTLSVerify(),
		)
	}

	if err != nil {
		return err
	}

	err = idp.cacheVerificationKey()
	if err != nil {
		return err
	}

	err = idp.cacheConsentKey()
	if err != nil {
		return err
	}

	return nil
}

// Parse and verify the challenge JWT
func (idp *IDP) getChallengeToken(challengeString string) (*jwt.Token, error) {
	token, err := jwt.Parse(challengeString, func(token *jwt.Token) (interface{}, error) {
		_, ok := token.Method.(*jwt.SigningMethodRSA)
		if !ok {
			return nil, ErrorBadSigningMethod
		}

		return idp.getVerificationKey()
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, ErrorInvalidToken
	}

	return token, nil
}

func (idp *IDP) getConsentKey() (*rsa.PrivateKey, error) {
	data, ok := idp.cache.Get(ConsentPrivateKey)
	if !ok {
		return nil, ErrorNotInCache
	}

	key, ok := data.(*rsa.PrivateKey)
	if !ok {
		return nil, ErrorBadKey
	}

	return key, nil
}

func (idp *IDP) getVerificationKey() (*rsa.PublicKey, error) {
	data, ok := idp.cache.Get(VerifyPublicKey)
	if !ok {
		return nil, ErrorNotInCache
	}

	key, ok := data.(*rsa.PublicKey)
	if !ok {
		return nil, ErrorBadKey
	}

	return key, nil
}

func (idp *IDP) getClient(clientID string) (*hclient.Client, error) {
	clientKey := ClientInfoKey(clientID)
	data, ok := idp.cache.Get(clientKey)
	if ok {
		if data != nil {
			client := data.(*hclient.Client)
			return client, nil
		}
		return nil, ErrorNoSuchClient
	}

	client, err := idp.hc.Client.GetClient(clientID)
	if err != nil {
		// Either the client isn't registered in hydra, or maybe hydra is
		// having some problem. Either way, ensure we don't hit hydra again
		// for this client if someone (maybe an attacker) retries quickly.
		idp.cache.Set(clientKey, nil, idp.config.ClientCacheExpiration)
		return nil, err
	}

	c := client.(*hclient.Client)
	idp.cache.Set(clientKey, client, idp.config.ClientCacheExpiration)
	return c, nil
}

// Create a new Challenge. The request will contain all the necessary information from Hydra, passed in the URL.
func (idp *IDP) NewChallenge(r *http.Request, user string) (challenge *Challenge, err error) {
	tokenStr := r.FormValue("challenge")
	if tokenStr == "" {
		// No challenge token
		err = ErrorBadRequest
		return
	}

	token, err := idp.getChallengeToken(tokenStr)
	if err != nil {
		// Most probably, token can't be verified or parsed
		return
	}
	claims := token.Claims.(jwt.MapClaims)

	challenge = new(Challenge)
	challenge.Expires = time.Unix(int64(claims["exp"].(float64)), 0)
	if challenge.Expires.Before(time.Now()) {
		challenge = nil
		err = ErrorChallengeExpired
		return
	}

	// Get data from the challenge jwt
	challenge.Client, err = idp.getClient(claims["aud"].(string))
	if err != nil {
		return nil, err
	}

	challenge.Redirect = claims["redir"].(string)
	challenge.User = user
	challenge.idp = idp

	scopes := claims["scp"].([]interface{})
	challenge.Scopes = make([]string, len(scopes), len(scopes))
	for i, scope := range scopes {
		challenge.Scopes[i] = scope.(string)
	}

	return
}

// Get the Challenge from a cookie, using Gorilla sessions
func (idp *IDP) GetChallenge(r *http.Request) (*Challenge, error) {
	session, err := idp.config.ChallengeStore.Get(r, SessionCookieName)
	if err != nil {
		return nil, err
	}

	challenge, ok := session.Values[SessionCookieName].(*Challenge)
	if !ok {
		return nil, ErrorBadChallengeCookie
	}

	if challenge.Expires.Before(time.Now()) {
		return nil, ErrorChallengeExpired
	}

	challenge.idp = idp

	return challenge, nil
}

// Closes connection to Hydra, cleans cache etc.
func (idp *IDP) Close() {
	idp.client = nil

	// Removes all keys from the cache
	idp.cache.Flush()
}
<commit_msg>Fixed changes in Hydra's SDK<commit_after>package idp

import (
	"crypto/rsa"
	"net/http"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gorilla/sessions"
	hclient "github.com/ory-am/hydra/client"
	hjwk "github.com/ory-am/hydra/jwk"
	hoauth2 "github.com/ory-am/hydra/oauth2"
	hydra "github.com/ory-am/hydra/sdk"
	"github.com/patrickmn/go-cache"
)

const (
	VerifyPublicKey   = "VerifyPublic"
	ConsentPrivateKey = "ConsentPrivate"
)

func ClientInfoKey(clientID string) string {
	return "ClientInfo:" + clientID
}

var encryptionkey = "something-very-secret"

// Identity Provider's options
type IDPConfig struct {
	// Client id issued by Hydra
	ClientID string `yaml:"client_id"`

	// Client secret issued by Hydra
	ClientSecret string `yaml:"client_secret"`

	// Hydra's address
	ClusterURL string `yaml:"hydra_address"`

	// Expiration time of internal key cache
	KeyCacheExpiration time.Duration `yaml:"key_cache_expiration"`

	// Expiration time of internal clientid cache
	ClientCacheExpiration time.Duration `yaml:"client_cache_expiration"`

	// Internal cache cleanup interval
	CacheCleanupInterval time.Duration `yaml:"cache_cleanup_interval"`

	// Expiration time of internal clientid cache
	ChallengeExpiration time.Duration `yaml:"challenge_expiration"`

	// Gorilla sessions Store for storing the Challenge.
	ChallengeStore sessions.Store
}

// Identity Provider helper
type IDP struct {
	config *IDPConfig

	// Communication with Hydra
	hc *hydra.Client

	// Http client for communicating with Hydra
	client *http.Client

	// Cache for all private and public keys
	cache *cache.Cache

	// Prepared cookie options for creating and deleting cookies
	createChallengeCookieOptions *sessions.Options
	deleteChallengeCookieOptions *sessions.Options
}

// Create the Identity Provider helper
func NewIDP(config *IDPConfig) *IDP {
	var idp = new(IDP)
	idp.config = config

	idp.cache = cache.New(config.KeyCacheExpiration, config.CacheCleanupInterval)
	idp.cache.OnEvicted(func(key string, value interface{}) { idp.refreshCache(key) })

	idp.createChallengeCookieOptions = new(sessions.Options)
	idp.createChallengeCookieOptions.Path = "/"
	idp.createChallengeCookieOptions.MaxAge = int(config.ChallengeExpiration.Seconds())
	idp.createChallengeCookieOptions.Secure = true // Send only via https
	idp.createChallengeCookieOptions.HttpOnly = false

	idp.deleteChallengeCookieOptions = new(sessions.Options)
	idp.deleteChallengeCookieOptions.Path = "/"
	idp.deleteChallengeCookieOptions.MaxAge = -1   // Mark for deletion
	idp.deleteChallengeCookieOptions.Secure = true // Send only via https
	idp.deleteChallengeCookieOptions.HttpOnly = false

	return idp
}

func (idp *IDP) cacheConsentKey() error {
	consentKey, err := idp.downloadConsentKey()

	duration := cache.DefaultExpiration
	if err != nil {
		// re-cache the result even if there's an error, but
		// do it with a shorter timeout. This will ensure we
		// try to refresh the key once that timeout expires,
		// otherwise we'll _never_ refresh the key again.
		duration = idp.config.CacheCleanupInterval
	}

	idp.cache.Set(ConsentPrivateKey, consentKey, duration)
	return err
}

func (idp *IDP) cacheVerificationKey() error {
	verifyKey, err := idp.downloadVerificationKey()

	duration := cache.DefaultExpiration
	if err != nil {
		// re-cache the result even if there's an error, but
		// do it with a shorter timeout. This will ensure we
		// try to refresh the key once that timeout expires,
		// otherwise we'll _never_ refresh the key again.
		duration = idp.config.CacheCleanupInterval
	}

	idp.cache.Set(VerifyPublicKey, verifyKey, duration)
	return err
}

// Called when any key expires
func (idp *IDP) refreshCache(key string) {
	switch key {
	case VerifyPublicKey:
		idp.cacheVerificationKey()
		return

	case ConsentPrivateKey:
		idp.cacheConsentKey()
		return

	default:
		// Will get here for client IDs.
		// Fine to just let them expire, the next request from that
		// client will trigger a refresh
		return
	}
}

// Downloads the hydra's public key
func (idp *IDP) downloadVerificationKey() (*rsa.PublicKey, error) {

	jwk, err := idp.hc.JSONWebKeys.GetKey(hoauth2.ConsentChallengeKey, "public")
	if err != nil {
		return nil, err
	}

	rsaKey, ok := hjwk.First(jwk.Keys).Key.(*rsa.PublicKey)
	if !ok {
		return nil, ErrorBadPublicKey
	}

	return rsaKey, nil
}

// Downloads the private key used for signing the consent
func (idp *IDP) downloadConsentKey() (*rsa.PrivateKey, error) {
	jwk, err := idp.hc.JSONWebKeys.GetKey(hoauth2.ConsentEndpointKey, "private")
	if err != nil {
		return nil, err
	}

	rsaKey, ok := hjwk.First(jwk.Keys).Key.(*rsa.PrivateKey)
	if !ok {
		return nil, ErrorBadPrivateKey
	}

	return rsaKey, nil
}

// Connect to Hydra
func (idp *IDP) Connect(verifyTLS bool) error {
	var err error
	if verifyTLS {
		idp.hc, err = hydra.Connect(
			hydra.ClientID(idp.config.ClientID),
			hydra.ClientSecret(idp.config.ClientSecret),
			hydra.ClusterURL(idp.config.ClusterURL),
		)
	} else {
		idp.hc, err = hydra.Connect(
			hydra.ClientID(idp.config.ClientID),
			hydra.ClientSecret(idp.config.ClientSecret),
			hydra.ClusterURL(idp.config.ClusterURL),
			hydra.SkipTLSVerify(),
		)
	}

	if err != nil {
		return err
	}

	err = idp.cacheVerificationKey()
	if err != nil {
		return err
	}

	err = idp.cacheConsentKey()
	if err != nil {
		return err
	}

	return nil
}

// Parse and verify the challenge JWT
func (idp *IDP) getChallengeToken(challengeString string) (*jwt.Token, error) {
	token, err := jwt.Parse(challengeString, func(token *jwt.Token) (interface{}, error) {
		_, ok := token.Method.(*jwt.SigningMethodRSA)
		if !ok {
			return nil, ErrorBadSigningMethod
		}

		return idp.getVerificationKey()
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, ErrorInvalidToken
	}

	return token, nil
}

func (idp *IDP) getConsentKey() (*rsa.PrivateKey, error) {
	data, ok := idp.cache.Get(ConsentPrivateKey)
	if !ok {
		return nil, ErrorNotInCache
	}

	key, ok := data.(*rsa.PrivateKey)
	if !ok {
		return nil, ErrorBadKey
	}

	return key, nil
}

func (idp *IDP) getVerificationKey() (*rsa.PublicKey, error) {
	data, ok := idp.cache.Get(VerifyPublicKey)
	if !ok {
		return nil, ErrorNotInCache
	}

	key, ok := data.(*rsa.PublicKey)
	if !ok {
		return nil, ErrorBadKey
	}

	return key, nil
}

func (idp *IDP) getClient(clientID string) (*hclient.Client, error) {
	clientKey := ClientInfoKey(clientID)
	data, ok := idp.cache.Get(clientKey)
	if ok {
		if data != nil {
			client := data.(*hclient.Client)
			return client, nil
		}
		return nil, ErrorNoSuchClient
	}

	client, err := idp.hc.Clients.GetClient(clientID)
	if err != nil {
		// Either the client isn't registered in hydra, or maybe hydra is
		// having some problem. Either way, ensure we don't hit hydra again
		// for this client if someone (maybe an attacker) retries quickly.
		idp.cache.Set(clientKey, nil, idp.config.ClientCacheExpiration)
		return nil, err
	}

	c := client.(*hclient.Client)
	idp.cache.Set(clientKey, client, idp.config.ClientCacheExpiration)
	return c, nil
}

// Create a new Challenge. The request will contain all the necessary information from Hydra, passed in the URL.
func (idp *IDP) NewChallenge(r *http.Request, user string) (challenge *Challenge, err error) {
	tokenStr := r.FormValue("challenge")
	if tokenStr == "" {
		// No challenge token
		err = ErrorBadRequest
		return
	}

	token, err := idp.getChallengeToken(tokenStr)
	if err != nil {
		// Most probably, token can't be verified or parsed
		return
	}
	claims := token.Claims.(jwt.MapClaims)

	challenge = new(Challenge)
	challenge.Expires = time.Unix(int64(claims["exp"].(float64)), 0)
	if challenge.Expires.Before(time.Now()) {
		challenge = nil
		err = ErrorChallengeExpired
		return
	}

	// Get data from the challenge jwt
	challenge.Client, err = idp.getClient(claims["aud"].(string))
	if err != nil {
		return nil, err
	}

	challenge.Redirect = claims["redir"].(string)
	challenge.User = user
	challenge.idp = idp

	scopes := claims["scp"].([]interface{})
	challenge.Scopes = make([]string, len(scopes), len(scopes))
	for i, scope := range scopes {
		challenge.Scopes[i] = scope.(string)
	}

	return
}

// Get the Challenge from a cookie, using Gorilla sessions
func (idp *IDP) GetChallenge(r *http.Request) (*Challenge, error) {
	session, err := idp.config.ChallengeStore.Get(r, SessionCookieName)
	if err != nil {
		return nil, err
	}

	challenge, ok := session.Values[SessionCookieName].(*Challenge)
	if !ok {
		return nil, ErrorBadChallengeCookie
	}

	if challenge.Expires.Before(time.Now()) {
		return nil, ErrorChallengeExpired
	}

	challenge.idp = idp

	return challenge, nil
}

// Closes connection to Hydra, cleans cache etc.
func (idp *IDP) Close() {
	idp.client = nil

	// Removes all keys from the cache
	idp.cache.Flush()
}
<|endoftext|>


<commit_before>package e2e

import (
	"context"
	"time"

	"github.com/blang/semver/v4"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
)

var missingAPI = `{"apiVersion":"verticalpodautoscalers.autoscaling.k8s.io/v1","kind":"VerticalPodAutoscaler","metadata":{"name":"my.thing","namespace":"foo"}}`

var _ = Describe("Not found APIs", func() {
	BeforeEach(func() {
		csv := newCSV("test-csv", testNamespace, "", semver.Version{}, nil, nil, nil)
		Expect(ctx.Ctx().Client().Create(context.TODO(), &csv)).To(Succeed())
	})
	AfterEach(func() {
		TearDown(testNamespace)
	})

	When("objects with APIs that are not on-cluster are created in the installplan", func() {
		// each entry is an installplan with a deprecated resource
		type payload struct {
			name       string
			IP         *operatorsv1alpha1.InstallPlan
			errMessage string
		}

		tableEntries := []table.TableEntry{
			table.Entry("contains an entry with a missing API not found on cluster ", payload{
				name: "installplan contains a missing API",
				IP: &operatorsv1alpha1.InstallPlan{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: *namespace, // this is necessary due to ginkgo table semantics, see https://github.com/onsi/ginkgo/issues/378
						Name:      "test-plan-api",
					},
					Spec: operatorsv1alpha1.InstallPlanSpec{
						Approval:                   operatorsv1alpha1.ApprovalAutomatic,
						Approved:                   true,
						ClusterServiceVersionNames: []string{},
					},
				},
				errMessage: "api-server resource not found installing VerticalPodAutoscaler my.thing: GroupVersionKind " +
					"verticalpodautoscalers.autoscaling.k8s.io/v1, Kind=VerticalPodAutoscaler not found on the cluster",
			}),
		}

		table.DescribeTable("the ip enters a failed state with a helpful error message", func(tt payload) {
			Expect(ctx.Ctx().Client().Create(context.Background(), tt.IP)).To(Succeed())

			tt.IP.Status = operatorsv1alpha1.InstallPlanStatus{
				Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
				CatalogSources: []string{},
				Plan: []*operatorsv1alpha1.Step{
					{
						Resolving: "test-csv",
						Status:    operatorsv1alpha1.StepStatusUnknown,
						Resource: operatorsv1alpha1.StepResource{
							Name:     "my.thing",
							Group:    "verticalpodautoscalers.autoscaling.k8s.io",
							Version:  "v1",
							Kind:     "VerticalPodAutoscaler",
							Manifest: missingAPI,
						},
					},
				},
			}

			Expect(ctx.Ctx().Client().Status().Update(context.Background(), tt.IP)).To(Succeed(), "failed to update the resource")

			// The IP sits in the Installing phase with the GVK missing error
			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
				return tt.IP, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(tt.IP), tt.IP)
			}).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseInstalling)), HaveMessage(tt.errMessage))

			// Eventually the IP fails with the GVK missing error, after installplan retries, which is by default 1 minute.
			Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
				return tt.IP, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(tt.IP), tt.IP)
			}, 2*time.Minute).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed)), HaveMessage(tt.errMessage))
		}, tableEntries...)
	})
})
<commit_msg>Move deprecated e2e to one namespace per spec (#2707)<commit_after>package e2e

import (
	"context"
	"fmt"
	"time"

	"github.com/blang/semver/v4"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var missingAPI = `{"apiVersion":"verticalpodautoscalers.autoscaling.k8s.io/v1","kind":"VerticalPodAutoscaler","metadata":{"name":"my.thing","namespace":"foo"}}`

var _ = Describe("Not found APIs", func() {

	var ns corev1.Namespace

	BeforeEach(func() {
		namespaceName := genName("deprecated-e2e-")
		og := operatorsv1.OperatorGroup{
			ObjectMeta: metav1.ObjectMeta{
				Name:      fmt.Sprintf("%s-operatorgroup", namespaceName),
				Namespace: namespaceName,
			},
		}
		ns = SetupGeneratedTestNamespaceWithOperatorGroup(namespaceName, og)

		csv := newCSV("test-csv", ns.GetName(), "", semver.Version{}, nil, nil, nil)
		Expect(ctx.Ctx().Client().Create(context.TODO(), &csv)).To(Succeed())
	})

	AfterEach(func() {
		TeardownNamespace(ns.GetName())
	})

	Context("objects with APIs that are not on-cluster are created in the installplan", func() {
		When("installplan contains a missing API", func() {
			It("the ip enters a failed state with a helpful error message", func() {
				ip := &operatorsv1alpha1.InstallPlan{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-plan-api",
						Namespace: ns.GetName(),
					},
					Spec: operatorsv1alpha1.InstallPlanSpec{
						Approval:                   operatorsv1alpha1.ApprovalAutomatic,
						Approved:                   true,
						ClusterServiceVersionNames: []string{},
					},
				}
				Expect(ctx.Ctx().Client().Create(context.Background(), ip)).To(Succeed())

				ip.Status = operatorsv1alpha1.InstallPlanStatus{
					Phase:          operatorsv1alpha1.InstallPlanPhaseInstalling,
					CatalogSources: []string{},
					Plan: []*operatorsv1alpha1.Step{
						{
							Resolving: "test-csv",
							Status:    operatorsv1alpha1.StepStatusUnknown,
							Resource: operatorsv1alpha1.StepResource{
								Name:     "my.thing",
								Group:    "verticalpodautoscalers.autoscaling.k8s.io",
								Version:  "v1",
								Kind:     "VerticalPodAutoscaler",
								Manifest: missingAPI,
							},
						},
					},
				}

				Expect(ctx.Ctx().Client().Status().Update(context.Background(), ip)).To(Succeed(), "failed to update the resource")

				errMessage := "api-server resource not found installing VerticalPodAutoscaler my.thing: GroupVersionKind " +
					"verticalpodautoscalers.autoscaling.k8s.io/v1, Kind=VerticalPodAutoscaler not found on the cluster"
				// The IP sits in the Installing phase with the GVK missing error
				Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
					return ip, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(ip), ip)
				}).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseInstalling)), HaveMessage(errMessage))

				// Eventually the IP fails with the GVK missing error, after installplan retries, which is by default 1 minute.
				Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
					return ip, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(ip), ip)
				}, 2*time.Minute).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed)), HaveMessage(errMessage))
			})
		})
	})
})
<|endoftext|>


<commit_before>package aranGO

import (
  "testing"
  "github.com/stretchr/testify/assert"
)

// Configure to start testing
var(
  TestCollection = "apps"
  TestDoc        DocTest
  TestDbName     = "goleat"
  TestUsername       = ""
  TestPassword   = ""
  verbose    = false
  TestServer     = "http://localhost:8529"
  s *Session
)

// document to test
type DocTest struct {
  Document // arango Document to save id, key, rev
}

func TestSimple(t *testing.T){
  // connect
  s ,err := Connect(TestServer, TestUsername, TestPassword, verbose)
  assert.Nil(t,err)

  db := s.DB(TestDbName)
  assert.NotNil(t,db)

  c  := db.Col(TestCollection)
  assert.NotNil(t,c)

  // Any
  err = c.Any(&TestDoc)
  assert.Equal(t,TestDoc.Error,false)
  assert.Nil(t,err)

  // Example
  cur, err := c.Example(map[string]interface{}{},0,10)
  assert.Equal(t,TestDoc.Error,false)
  assert.Nil(t,err)
  assert.NotNil(t,cur)

  // need to add new functions!
}
<commit_msg>Added `save` and `FetchOne` tests<commit_after>package aranGO

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// Configure to start testing
var (
	TestCollection = "apps"
	TestDoc        DocTest
	TestDbName     = "goleat"
	TestUsername   = ""
	TestPassword   = ""
	verbose        = false
	TestServer     = "http://localhost:8529"
	s              *Session
)

// document to test
type DocTest struct {
	Document // arango Document to save id, key, rev
	Text     string
}

func TestSimple(t *testing.T) {
	// connect
	s, err := Connect(TestServer, TestUsername, TestPassword, verbose)
	assert.Nil(t, err)

	// Create the db
	s.CreateDB(TestDbName, nil)

	db := s.DB(TestDbName)
	assert.NotNil(t, db)

	c := db.Col(TestCollection)
	assert.NotNil(t, c)

	// Any
	err = c.Any(&TestDoc)
	assert.Equal(t, TestDoc.Error, false)
	assert.Nil(t, err)

	// Save
	var saveTestDoc DocTest
	saveTestDoc.Text = "Stringy string"
	err = c.Save(saveTestDoc)
	assert.Nil(t, err)

	// Example
	cur, err := c.Example(map[string]interface{}{}, 0, 10)
	assert.Equal(t, TestDoc.Error, false)
	assert.Nil(t, err)
	assert.NotNil(t, cur)

	var newTestDoc DocTest
	moreFiles := cur.FetchOne(newTestDoc)
	assert.Equal(t, moreFiles, false)
	assert.Equal(t, saveTestDoc, newTestDoc)

	// need to add new functions!

}
<|endoftext|>


<commit_before>package ofp10

import "github.com/kopwei/goof/protocols/ofpgeneral"

//enum ofp_stats_types {
const (
	/* Description of this OpenFlow switch.
	 * The request body is empty.
	 * The reply body is struct ofp_desc_stats. */
	OfpStatsTypeDesc = iota

	/* Individual flow statistics.
	 * The request body is struct ofp_flow_stats_request.
	 * The reply body is an array of struct ofp_flow_stats. */
	OfpStatsTypeFlow

	/* Aggregate flow statistics.
	 * The request body is struct ofp_aggregate_stats_request.
	 * The reply body is struct ofp_aggregate_stats_reply. */
	OfpStatsTypeAggregate

	/* Flow table statistics.
	 * The request body is empty.
	 * The reply body is an array of struct ofp_table_stats. */
	OfpStatsTypeTable

	/* Physical port statistics.
	 * The request body is struct ofp_port_stats_request.
	 * The reply body is an array of struct ofp_port_stats. */
	OfpStatsTypePort

	/* Queue statistics for a port
	 * The request body defines the port
	 * The reply body is an array of struct ofp_queue_stats */
	OfpStatsTypeQueue

	/* Vendor extension.
	 * The request and reply bodies begin with a 32-bit vendor ID, which takes
	 * the same form as in "struct ofp_vendor_header".  The request and reply
	 * bodies are otherwise vendor-defined. */
	OfpStatsTypeVendor = 0xffff
)

// enum ofp_stats_reply_flags {
const (
	OfpStatsReplyMore = iota /* More replies to follow. */
)

const (
	descStrLen   = 256
	serialNumLen = 32
)

// OfpStatsReqMsg represents the structure of Stats request msg
type OfpStatsReqMsg struct {
	Header ofpgeneral.OfpHeader
	Type   uint16 /* One of the OFPST_* constants. */
	Flags  uint16 /* OFPSF_REQ_* flags (none yet defined). */
	Body   []byte /* Body of the request. */
}

// OfpStatsReplyMsg represents the structure of stats reply msg
type OfpStatsReplyMsg struct {
	Header ofpgeneral.OfpHeader
	Type   uint16 /* One of the OFPST_* constants. */
	Flags  uint16 /* OFPSF_REPLY_* flags. */
	Body   []byte /* Body of the reply. */
}

// OfpDescStats represents the structure of descriptive stats
type OfpDescStats struct {
	ManufacurerDesc [descStrLen]byte   /* Manufacturer description. */
	HwDesc          [descStrLen]byte   /* Hardware description. */
	SwDesc          [descStrLen]byte   /* Software description. */
	SerialNum       [serialNumLen]byte /* Serial number. */
	DatapathDesc    [descStrLen]byte   /* Human readable description of datapath. */
}

// OfpFlowStatsReq represents the structure body for ofp_stats_request of type OFPST_FLOW.
type OfpFlowStatsReq struct {
	Match   OfpMatch // Fields to match.
	TableID uint8    //  ID of table to read (from ofp_table_stats), 0xff for all tables or 0xfe for emergency.
	//uint8_t pad;              /* Align to 32 bits. */
	OutPort uint16 // Require matching entries to include this as an output port.  A value of OFPP_NONE indicates no restriction.
}

// OfpFlowStats represents the structure body of reply to OFPST_FLOW request.
type OfpFlowStats struct {
	Length  uint16 /* Length of this entry. */
	TableID uint8  /* ID of table flow came from. */
	//uint8_t pad;
	Match           OfpMatch /* Description of fields. */
	DurationSec     uint32   /* Time flow has been alive in seconds. */
	DurationNanoSec uint32   /* Time flow has been alive in nanoseconds beyond
	   duration_sec. */
	Priority uint16 /* Priority of the entry. Only meaningful
	   when this is not an exact-match entry. */
	IdleTimeout uint16 /* Number of seconds idle before expiration. */
	HardTimeout uint16 /* Number of seconds before expiration. */
	//uint8_t pad2[6];          /* Align to 64-bits. */
	Cookie      uint64            /* Opaque controller-issued identifier. */
	PacketCount uint64            /* Number of packets in flow. */
	ByteCount   uint64            /* Number of bytes in flow. */
	Actions     []OfpActionHeader /* Actions. */
}

// OfpAggStatsRequest represents the structure body for ofp_stats_request of type OFPST_AGGREGATE.
type OfpAggStatsRequest struct {
	Match   OfpMatch /* Fields to match. */
	TableID uint8    /* ID of table to read (from ofp_table_stats) 0xff for all tables or 0xfe for emergency. */
	//uint8_t pad;              /* Align to 32 bits. */
	OutPort uint16 /* Require matching entries to include this as an output port.  A value of OFPP_NONE
	   indicates no restriction. */
}

// OfpAggStatsReply represents the structure body of reply to OFPST_AGGREGATE request. */
type OfpAggStatsReply struct {
	PacketCount uint64 /* Number of packets in flows. */
	ByteCount   uint64 /* Number of bytes in flows. */
	FlowCount   uint32 /* Number of flows. */
	//uint8_t pad[4];           /* Align to 64 bits. */
}

// OfpTableStats represents the structure body of reply to OFPST_TABLE request.
type OfpTableStats struct {
	TableID uint8 // Identifier of table.  Lower numbered tables are consulted first.
	//uint8_t pad[3];          /* Align to 32-bits. */
	Name         [32]byte
	WildCards    uint32 /* Bitmap of OFPFW_* wildcards that are supported by the table. */
	MaxEntries   uint32 /* Max number of entries supported. */
	ActiveCount  uint32 /* Number of active entries. */
	LookupCount  uint64 /* Number of packets looked up in table. */
	MatchedCount uint64 /* Number of packets that hit table. */
}

// OfpPortStatsRequest represents structure body for ofp_stats_request of type OFPST_PORT.
type OfpPortStatsRequest struct {
	// PortNo is the OFPST_PORT message must request statistics
	// either for a single port (specified in
	// port_no) or for all ports (if port_no ==
	// OFPP_NONE).
	PortNo uint16
	//uint8_t pad[6];
}

// OfpPortStats represents the structure body of reply to OFPST_PORT request. If a counter is unsupported, set
// the field to all ones.
type OfpPortStats struct {
	PortNo uint16
	//uint8_t pad[6];          /* Align to 64-bits. */
	RxPackets uint64 /* Number of received packets. */
	TxPackets uint64 /* Number of transmitted packets. */
	RxBytes   uint64 /* Number of received bytes. */
	TxBytes   uint64 /* Number of transmitted bytes. */
	RxDropped uint64 /* Number of packets dropped by RX. */
	TxDropped uint64 /* Number of packets dropped by TX. */
	RxErrors  uint64 /* Number of receive errors.  This is a super-set
	   of more specific receive errors and should be
	   greater than or equal to the sum of all
	   rx_*_err values. */
	TxErrors uint64 /* Number of transmit errors.  This is a super-set
	   of more specific transmit errors and should be
	   greater than or equal to the sum of all
	   tx_*_err values (none currently defined.) */
	RxFrameErr uint64 /* Number of frame alignment errors. */
	RxOverErr  uint64 /* Number of packets with RX overrun. */
	RxCrcErr   uint64 /* Number of CRC errors. */
	Collisions uint64 /* Number of collisions. */
}

// OfpVendorHeader represents the header structure of Vendor extension.
type OfpVendorHeader struct {
	Header ofpgeneral.OfpHeader /* Type OFPT_VENDOR. */
	Vendor uint32               /* Vendor ID:
	 * - MSB 0: low-order bytes are IEEE OUI.
	 * - MSB != 0: defined by OpenFlow
	 *   consortium. */
	/* Vendor-defined arbitrary additional data. */
}

// OfpQueueStatsReq represents the ofp queue stats query structure
type OfpQueueStatsReq struct {
	PortNo uint16 /* All ports if OFPT_ALL. */
	//uint8_t pad[2];          /* Align to 32-bits. */
	QueueID uint32 /* All queues if OFPQ_ALL. */
}

// OfpQueueStats represents the queue stats info
type OfpQueueStats struct {
	PortNo uint16
	//uint8_t pad[2];          /* Align to 32-bits. */
	QueueID   uint32 /* Queue i.d */
	TxBytes   uint64 /* Number of transmitted bytes. */
	TxPackets uint64 /* Number of transmitted packets. */
	TxErrors  uint64 /* Number of packets dropped due to overrun. */
}
<commit_msg>Fixed the name conflict<commit_after>package ofp10

import "github.com/kopwei/goof/protocols/ofpgeneral"

//enum ofp_stats_types {
const (
	/* Description of this OpenFlow switch.
	 * The request body is empty.
	 * The reply body is struct ofp_desc_stats. */
	OfpStatsTypeDesc = iota

	/* Individual flow statistics.
	 * The request body is struct ofp_flow_stats_request.
	 * The reply body is an array of struct ofp_flow_stats. */
	OfpStatsTypeFlow

	/* Aggregate flow statistics.
	 * The request body is struct ofp_aggregate_stats_request.
	 * The reply body is struct ofp_aggregate_stats_reply. */
	OfpStatsTypeAggregate

	/* Flow table statistics.
	 * The request body is empty.
	 * The reply body is an array of struct ofp_table_stats. */
	OfpStatsTypeTable

	/* Physical port statistics.
	 * The request body is struct ofp_port_stats_request.
	 * The reply body is an array of struct ofp_port_stats. */
	OfpStatsTypePort

	/* Queue statistics for a port
	 * The request body defines the port
	 * The reply body is an array of struct ofp_queue_stats */
	OfpStatsTypeQueue

	/* Vendor extension.
	 * The request and reply bodies begin with a 32-bit vendor ID, which takes
	 * the same form as in "struct ofp_vendor_header".  The request and reply
	 * bodies are otherwise vendor-defined. */
	OfpStatsTypeVendor = 0xffff
)

// enum ofp_stats_reply_flags {
const (
	OfpStatsReplyMore = iota /* More replies to follow. */
)

const (
	descStrLen   = 256
	serialNumLen = 32
)

// OfpStatsReqMsg represents the structure of Stats request msg
type OfpStatsReqMsg struct {
	Header ofpgeneral.OfpHeader
	Type   uint16 /* One of the OFPST_* constants. */
	Flags  uint16 /* OFPSF_REQ_* flags (none yet defined). */
	Body   []byte /* Body of the request. */
}

// OfpStatsReplyMsg represents the structure of stats reply msg
type OfpStatsReplyMsg struct {
	Header ofpgeneral.OfpHeader
	Type   uint16 /* One of the OFPST_* constants. */
	Flags  uint16 /* OFPSF_REPLY_* flags. */
	Body   []byte /* Body of the reply. */
}

// OfpDescStats represents the structure of descriptive stats
type OfpDescStats struct {
	ManufacurerDesc [descStrLen]byte   /* Manufacturer description. */
	HwDesc          [descStrLen]byte   /* Hardware description. */
	SwDesc          [descStrLen]byte   /* Software description. */
	SerialNum       [serialNumLen]byte /* Serial number. */
	DatapathDesc    [descStrLen]byte   /* Human readable description of datapath. */
}

// OfpFlowStatsReq represents the structure body for ofp_stats_request of type OFPST_FLOW.
type OfpFlowStatsReq struct {
	Match   OfpMatch // Fields to match.
	TableID uint8    //  ID of table to read (from ofp_table_stats), 0xff for all tables or 0xfe for emergency.
	//uint8_t pad;              /* Align to 32 bits. */
	OutPort uint16 // Require matching entries to include this as an output port.  A value of OFPP_NONE indicates no restriction.
}

// OfpFlowStats represents the structure body of reply to OFPST_FLOW request.
type OfpFlowStats struct {
	Length  uint16 /* Length of this entry. */
	TableID uint8  /* ID of table flow came from. */
	//uint8_t pad;
	Match           OfpMatch /* Description of fields. */
	DurationSec     uint32   /* Time flow has been alive in seconds. */
	DurationNanoSec uint32   /* Time flow has been alive in nanoseconds beyond
	   duration_sec. */
	Priority uint16 /* Priority of the entry. Only meaningful
	   when this is not an exact-match entry. */
	IdleTimeout uint16 /* Number of seconds idle before expiration. */
	HardTimeout uint16 /* Number of seconds before expiration. */
	//uint8_t pad2[6];          /* Align to 64-bits. */
	Cookie      uint64            /* Opaque controller-issued identifier. */
	PacketCount uint64            /* Number of packets in flow. */
	ByteCount   uint64            /* Number of bytes in flow. */
	Actions     []OfpActionHeader /* Actions. */
}

// OfpAggStatsRequest represents the structure body for ofp_stats_request of type OFPST_AGGREGATE.
type OfpAggStatsRequest struct {
	Match   OfpMatch /* Fields to match. */
	TableID uint8    /* ID of table to read (from ofp_table_stats) 0xff for all tables or 0xfe for emergency. */
	//uint8_t pad;              /* Align to 32 bits. */
	OutPort uint16 /* Require matching entries to include this as an output port.  A value of OFPP_NONE
	   indicates no restriction. */
}

// OfpAggStatsReply represents the structure body of reply to OFPST_AGGREGATE request. */
type OfpAggStatsReply struct {
	PacketCount uint64 /* Number of packets in flows. */
	ByteCount   uint64 /* Number of bytes in flows. */
	FlowCount   uint32 /* Number of flows. */
	//uint8_t pad[4];           /* Align to 64 bits. */
}

// OfpTableStats represents the structure body of reply to OFPST_TABLE request.
type OfpTableStats struct {
	TableID uint8 // Identifier of table.  Lower numbered tables are consulted first.
	//uint8_t pad[3];          /* Align to 32-bits. */
	Name         [32]byte
	WildCards    uint32 /* Bitmap of OFPFW_* wildcards that are supported by the table. */
	MaxEntries   uint32 /* Max number of entries supported. */
	ActiveCount  uint32 /* Number of active entries. */
	LookupCount  uint64 /* Number of packets looked up in table. */
	MatchedCount uint64 /* Number of packets that hit table. */
}

// OfpPortStatsRequest represents structure body for ofp_stats_request of type OFPST_PORT.
type OfpPortStatsRequest struct {
	// PortNo is the OFPST_PORT message must request statistics
	// either for a single port (specified in
	// port_no) or for all ports (if port_no ==
	// OFPP_NONE).
	PortNo uint16
	//uint8_t pad[6];
}

// OfpPortStats represents the structure body of reply to OFPST_PORT request. If a counter is unsupported, set
// the field to all ones.
type OfpPortStats struct {
	PortNo uint16
	//uint8_t pad[6];          /* Align to 64-bits. */
	RxPackets uint64 /* Number of received packets. */
	TxPackets uint64 /* Number of transmitted packets. */
	RxBytes   uint64 /* Number of received bytes. */
	TxBytes   uint64 /* Number of transmitted bytes. */
	RxDropped uint64 /* Number of packets dropped by RX. */
	TxDropped uint64 /* Number of packets dropped by TX. */
	RxErrors  uint64 /* Number of receive errors.  This is a super-set
	   of more specific receive errors and should be
	   greater than or equal to the sum of all
	   rx_*_err values. */
	TxErrors uint64 /* Number of transmit errors.  This is a super-set
	   of more specific transmit errors and should be
	   greater than or equal to the sum of all
	   tx_*_err values (none currently defined.) */
	RxFrameErr uint64 /* Number of frame alignment errors. */
	RxOverErr  uint64 /* Number of packets with RX overrun. */
	RxCrcErr   uint64 /* Number of CRC errors. */
	Collisions uint64 /* Number of collisions. */
}

// OfpVendorHeader represents the header structure of Vendor extension.
type OfpVendorHeader struct {
	Header ofpgeneral.OfpHeader /* Type OFPT_VENDOR. */
	Vendor uint32               /* Vendor ID:
	 * - MSB 0: low-order bytes are IEEE OUI.
	 * - MSB != 0: defined by OpenFlow
	 *   consortium. */
	/* Vendor-defined arbitrary additional data. */
}

// OfpQueueStatsReq represents the ofp queue stats query structure
type OfpQueueStatsReq struct {
	PortNo uint16 /* All ports if OFPT_ALL. */
	//uint8_t pad[2];          /* Align to 32-bits. */
	QueueID uint32 /* All queues if OFPQ_ALL. */
}

// OfpQueueStatsInfo represents the queue stats info
type OfpQueueStatsInfo struct {
	PortNo uint16
	//uint8_t pad[2];          /* Align to 32-bits. */
	QueueID   uint32 /* Queue i.d */
	TxBytes   uint64 /* Number of transmitted bytes. */
	TxPackets uint64 /* Number of transmitted packets. */
	TxErrors  uint64 /* Number of packets dropped due to overrun. */
}
<|endoftext|>


<commit_before>package algorithm_test

import (
	. "github.com/onsi/ginkgo/extensions/table"
)

var _ = DescribeTable("Input resolving",
	(Example).Run,

	Entry("can fan-in", Example{
		DB: DB{
			// pass a and b
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},

			// pass a but not b
			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{
				Name:     "resource-x",
				Resource: "resource-x",
				Passed:   []string{"simple-a", "simple-b"},
			},
		},

		// no v2 as it hasn't passed b
		Result: Result{"resource-x": "rxv1"},
	}),

	Entry("propagates resources together", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a"}},
		},

		Result: Result{
			"resource-x": "rxv1",
			"resource-y": "ryv1",
		},
	}),

	Entry("correlates inputs by build, allowing resources to skip jobs", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			{Job: "fan-in", BuildID: 3, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},

			{Job: "simple-a", BuildID: 4, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Job: "simple-a", BuildID: 4, Resource: "resource-y", Version: "ryv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a", "fan-in"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a"}},
		},

		Result: Result{
			"resource-x": "rxv1",

			// not ryv2, as it didn't make it through build relating simple-a to fan-in
			"resource-y": "ryv1",
		},
	}),

	Entry("finds only versions that passed through together", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv3", CheckOrder: 2},
			{Job: "simple-a", BuildID: 3, Resource: "resource-y", Version: "ryv3", CheckOrder: 2},
			{Job: "simple-b", BuildID: 4, Resource: "resource-x", Version: "rxv3", CheckOrder: 2},
			{Job: "simple-b", BuildID: 4, Resource: "resource-y", Version: "ryv3", CheckOrder: 2},

			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv2", CheckOrder: 1},
			{Job: "simple-a", BuildID: 3, Resource: "resource-y", Version: "ryv4", CheckOrder: 1},

			{Job: "simple-b", BuildID: 4, Resource: "resource-x", Version: "rxv4", CheckOrder: 1},
			{Job: "simple-b", BuildID: 4, Resource: "resource-y", Version: "rxv4", CheckOrder: 1},

			{Job: "simple-b", BuildID: 5, Resource: "resource-x", Version: "rxv4", CheckOrder: 1},
			{Job: "simple-b", BuildID: 5, Resource: "resource-y", Version: "rxv2", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a", "simple-b"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a", "simple-b"}},
		},

		Result: Result{
			"resource-x": "rxv3",
			"resource-y": "ryv3",
		},
	}),

	Entry("can collect distinct versions of resources without correlating by job", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{Name: "simple-a-resource-x", Resource: "resource-x", Passed: []string{"simple-a"}},
			{Name: "simple-b-resource-x", Resource: "resource-x", Passed: []string{"simple-b"}},
		},

		Result: Result{
			"simple-a-resource-x": "rxv1",
			"simple-b-resource-x": "rxv2",
		},
	}),

	Entry("resolves passed constraints with common jobs", Example{
		DB: DB{
			{Job: "shared-job", BuildID: 1, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "shared-job", BuildID: 1, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
			{Job: "job-1", BuildID: 2, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "job-2", BuildID: 3, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
		},

		Inputs: Inputs{
			{
				Name:     "input-1",
				Resource: "resource-1",
				Passed:   []string{"shared-job", "job-1"},
			},
			{
				Name:     "input-2",
				Resource: "resource-2",
				Passed:   []string{"shared-job", "job-2"},
			},
		},

		Result: Result{
			"input-1": "r1-common-to-shared-and-j1",
			"input-2": "r2-common-to-shared-and-j2",
		},
	}),

	Entry("resolves passed constraints with common jobs, skipping versions that are not common to builds of all jobs", Example{
		DB: DB{
			{Job: "shared-job", BuildID: 1, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "shared-job", BuildID: 1, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
			{Job: "job-1", BuildID: 2, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "job-2", BuildID: 3, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},

			{Job: "shared-job", BuildID: 4, Resource: "resource-1", Version: "new-r1-common-to-shared-and-j1", CheckOrder: 2},
			{Job: "shared-job", BuildID: 4, Resource: "resource-2", Version: "new-r2-common-to-shared-and-j2", CheckOrder: 2},
			{Job: "job-1", BuildID: 5, Resource: "resource-1", Version: "new-r1-common-to-shared-and-j1", CheckOrder: 2},
		},

		Inputs: Inputs{
			{
				Name:     "input-1",
				Resource: "resource-1",
				Passed:   []string{"shared-job", "job-1"},
			},
			{
				Name:     "input-2",
				Resource: "resource-2",
				Passed:   []string{"shared-job", "job-2"},
			},
		},

		Result: Result{
			"input-1": "r1-common-to-shared-and-j1",
			"input-2": "r2-common-to-shared-and-j2",
		},
	}),

	Entry("finds the latest version for inputs with no passed constraints", Example{
		DB: DB{
			// build outputs
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			// the versions themselves
			// note: normally there's one of these for each version, including ones
			// that appear as outputs
			{Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Resource: "resource-y", Version: "ryv2", CheckOrder: 2},
			{Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Resource: "resource-y", Version: "ryv3", CheckOrder: 3},
			{Resource: "resource-x", Version: "rxv3", CheckOrder: 3},
			{Resource: "resource-y", Version: "ryv4", CheckOrder: 4},
			{Resource: "resource-x", Version: "rxv4", CheckOrder: 4},
			{Resource: "resource-y", Version: "ryv5", CheckOrder: 5},
			{Resource: "resource-x", Version: "rxv5", CheckOrder: 5},
		},

		Inputs: Inputs{
			{
				Name:     "resource-x",
				Resource: "resource-x",
				Passed:   []string{"simple-a"},
			},
			{
				Name:     "resource-x-unconstrained",
				Resource: "resource-x",
			},
			{
				Name:     "resource-y-unconstrained",
				Resource: "resource-y",
			},
		},

		Result: Result{
			"resource-x":               "rxv1",
			"resource-x-unconstrained": "rxv5",
			"resource-y-unconstrained": "ryv5",
		},
	}),

	Entry("check orders take precedence over version ID", Example{
		DB: DB{
			{Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x"},
		},

		Result: Result{
			"resource-x": "rxv2",
		},
	}),

	Entry("bosh memory leak regression test", Example{
		LoadDB: "testdata/bosh-versions.json",

		Inputs: Inputs{
			{
				Name:     "bosh-src",
				Resource: "bosh-src",
				Passed: []string{
					"unit-1.9",
					"unit-2.1",
					"integration-2.1-mysql",
					"integration-1.9-postgres",
					"integration-2.1-postgres",
				},
			},
			{
				Name:     "bosh-load-tests",
				Resource: "bosh-load-tests",
			},
		},

		Result: Result{
			"bosh-src":        "imported-r88v9814",
			"bosh-load-tests": "imported-r89v7204",
		},
	}),

	Entry("concourse deploy high cpi regression test", Example{
		LoadDB: "testdata/concourse-versions-high-cpu-deploy.json",

		Inputs: Inputs{
			{
				Name:     "concourse",
				Resource: "concourse",
				Passed: []string{
					"testflight",
					"bin-testflight",
				},
			},
			{
				Name:     "version",
				Resource: "version",
				Passed: []string{
					"testflight",
					"bin-testflight",
				},
			},
			{
				Name:     "candidate-release",
				Resource: "candidate-release",
				Passed: []string{
					"testflight",
				},
			},
			{
				Name:     "garden-linux-release",
				Resource: "garden-linux",
				Passed: []string{
					"testflight",
				},
			},
			{
				Name:     "bin-rc",
				Resource: "bin-rc",
				Passed: []string{
					"bin-testflight",
				},
			},
			{
				Name:     "bosh-stemcell",
				Resource: "aws-stemcell",
			},
			{
				Name:     "deployments",
				Resource: "deployments",
			},
		},

		Result: Result{
			"candidate-release":    "imported-r238v448886",
			"deployments":          "imported-r45v448469",
			"bosh-stemcell":        "imported-r48v443997",
			"bin-rc":               "imported-r765v448889",
			"garden-linux-release": "imported-r17v443811",
			"version":              "imported-r12v448884",
			"concourse":            "imported-r62v448881",
		},
	}),
)
<commit_msg>fix typo<commit_after>package algorithm_test

import (
	. "github.com/onsi/ginkgo/extensions/table"
)

var _ = DescribeTable("Input resolving",
	(Example).Run,

	Entry("can fan-in", Example{
		DB: DB{
			// pass a and b
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},

			// pass a but not b
			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{
				Name:     "resource-x",
				Resource: "resource-x",
				Passed:   []string{"simple-a", "simple-b"},
			},
		},

		// no v2 as it hasn't passed b
		Result: Result{"resource-x": "rxv1"},
	}),

	Entry("propagates resources together", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a"}},
		},

		Result: Result{
			"resource-x": "rxv1",
			"resource-y": "ryv1",
		},
	}),

	Entry("correlates inputs by build, allowing resources to skip jobs", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			{Job: "fan-in", BuildID: 3, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},

			{Job: "simple-a", BuildID: 4, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Job: "simple-a", BuildID: 4, Resource: "resource-y", Version: "ryv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a", "fan-in"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a"}},
		},

		Result: Result{
			"resource-x": "rxv1",

			// not ryv2, as it didn't make it through build relating simple-a to fan-in
			"resource-y": "ryv1",
		},
	}),

	Entry("finds only versions that passed through together", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv3", CheckOrder: 2},
			{Job: "simple-a", BuildID: 3, Resource: "resource-y", Version: "ryv3", CheckOrder: 2},
			{Job: "simple-b", BuildID: 4, Resource: "resource-x", Version: "rxv3", CheckOrder: 2},
			{Job: "simple-b", BuildID: 4, Resource: "resource-y", Version: "ryv3", CheckOrder: 2},

			{Job: "simple-a", BuildID: 3, Resource: "resource-x", Version: "rxv2", CheckOrder: 1},
			{Job: "simple-a", BuildID: 3, Resource: "resource-y", Version: "ryv4", CheckOrder: 1},

			{Job: "simple-b", BuildID: 4, Resource: "resource-x", Version: "rxv4", CheckOrder: 1},
			{Job: "simple-b", BuildID: 4, Resource: "resource-y", Version: "rxv4", CheckOrder: 1},

			{Job: "simple-b", BuildID: 5, Resource: "resource-x", Version: "rxv4", CheckOrder: 1},
			{Job: "simple-b", BuildID: 5, Resource: "resource-y", Version: "rxv2", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x", Passed: []string{"simple-a", "simple-b"}},
			{Name: "resource-y", Resource: "resource-y", Passed: []string{"simple-a", "simple-b"}},
		},

		Result: Result{
			"resource-x": "rxv3",
			"resource-y": "ryv3",
		},
	}),

	Entry("can collect distinct versions of resources without correlating by job", Example{
		DB: DB{
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-b", BuildID: 2, Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
		},

		Inputs: Inputs{
			{Name: "simple-a-resource-x", Resource: "resource-x", Passed: []string{"simple-a"}},
			{Name: "simple-b-resource-x", Resource: "resource-x", Passed: []string{"simple-b"}},
		},

		Result: Result{
			"simple-a-resource-x": "rxv1",
			"simple-b-resource-x": "rxv2",
		},
	}),

	Entry("resolves passed constraints with common jobs", Example{
		DB: DB{
			{Job: "shared-job", BuildID: 1, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "shared-job", BuildID: 1, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
			{Job: "job-1", BuildID: 2, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "job-2", BuildID: 3, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
		},

		Inputs: Inputs{
			{
				Name:     "input-1",
				Resource: "resource-1",
				Passed:   []string{"shared-job", "job-1"},
			},
			{
				Name:     "input-2",
				Resource: "resource-2",
				Passed:   []string{"shared-job", "job-2"},
			},
		},

		Result: Result{
			"input-1": "r1-common-to-shared-and-j1",
			"input-2": "r2-common-to-shared-and-j2",
		},
	}),

	Entry("resolves passed constraints with common jobs, skipping versions that are not common to builds of all jobs", Example{
		DB: DB{
			{Job: "shared-job", BuildID: 1, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "shared-job", BuildID: 1, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},
			{Job: "job-1", BuildID: 2, Resource: "resource-1", Version: "r1-common-to-shared-and-j1", CheckOrder: 1},
			{Job: "job-2", BuildID: 3, Resource: "resource-2", Version: "r2-common-to-shared-and-j2", CheckOrder: 1},

			{Job: "shared-job", BuildID: 4, Resource: "resource-1", Version: "new-r1-common-to-shared-and-j1", CheckOrder: 2},
			{Job: "shared-job", BuildID: 4, Resource: "resource-2", Version: "new-r2-common-to-shared-and-j2", CheckOrder: 2},
			{Job: "job-1", BuildID: 5, Resource: "resource-1", Version: "new-r1-common-to-shared-and-j1", CheckOrder: 2},
		},

		Inputs: Inputs{
			{
				Name:     "input-1",
				Resource: "resource-1",
				Passed:   []string{"shared-job", "job-1"},
			},
			{
				Name:     "input-2",
				Resource: "resource-2",
				Passed:   []string{"shared-job", "job-2"},
			},
		},

		Result: Result{
			"input-1": "r1-common-to-shared-and-j1",
			"input-2": "r2-common-to-shared-and-j2",
		},
	}),

	Entry("finds the latest version for inputs with no passed constraints", Example{
		DB: DB{
			// build outputs
			{Job: "simple-a", BuildID: 1, Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Job: "simple-a", BuildID: 1, Resource: "resource-y", Version: "ryv1", CheckOrder: 1},

			// the versions themselves
			// note: normally there's one of these for each version, including ones
			// that appear as outputs
			{Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
			{Resource: "resource-y", Version: "ryv2", CheckOrder: 2},
			{Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Resource: "resource-y", Version: "ryv3", CheckOrder: 3},
			{Resource: "resource-x", Version: "rxv3", CheckOrder: 3},
			{Resource: "resource-y", Version: "ryv4", CheckOrder: 4},
			{Resource: "resource-x", Version: "rxv4", CheckOrder: 4},
			{Resource: "resource-y", Version: "ryv5", CheckOrder: 5},
			{Resource: "resource-x", Version: "rxv5", CheckOrder: 5},
		},

		Inputs: Inputs{
			{
				Name:     "resource-x",
				Resource: "resource-x",
				Passed:   []string{"simple-a"},
			},
			{
				Name:     "resource-x-unconstrained",
				Resource: "resource-x",
			},
			{
				Name:     "resource-y-unconstrained",
				Resource: "resource-y",
			},
		},

		Result: Result{
			"resource-x":               "rxv1",
			"resource-x-unconstrained": "rxv5",
			"resource-y-unconstrained": "ryv5",
		},
	}),

	Entry("check orders take precedence over version ID", Example{
		DB: DB{
			{Resource: "resource-x", Version: "rxv2", CheckOrder: 2},
			{Resource: "resource-x", Version: "rxv1", CheckOrder: 1},
		},

		Inputs: Inputs{
			{Name: "resource-x", Resource: "resource-x"},
		},

		Result: Result{
			"resource-x": "rxv2",
		},
	}),

	Entry("bosh memory leak regression test", Example{
		LoadDB: "testdata/bosh-versions.json",

		Inputs: Inputs{
			{
				Name:     "bosh-src",
				Resource: "bosh-src",
				Passed: []string{
					"unit-1.9",
					"unit-2.1",
					"integration-2.1-mysql",
					"integration-1.9-postgres",
					"integration-2.1-postgres",
				},
			},
			{
				Name:     "bosh-load-tests",
				Resource: "bosh-load-tests",
			},
		},

		Result: Result{
			"bosh-src":        "imported-r88v9814",
			"bosh-load-tests": "imported-r89v7204",
		},
	}),

	Entry("concourse deploy high cpu regression test", Example{
		LoadDB: "testdata/concourse-versions-high-cpu-deploy.json",

		Inputs: Inputs{
			{
				Name:     "concourse",
				Resource: "concourse",
				Passed: []string{
					"testflight",
					"bin-testflight",
				},
			},
			{
				Name:     "version",
				Resource: "version",
				Passed: []string{
					"testflight",
					"bin-testflight",
				},
			},
			{
				Name:     "candidate-release",
				Resource: "candidate-release",
				Passed: []string{
					"testflight",
				},
			},
			{
				Name:     "garden-linux-release",
				Resource: "garden-linux",
				Passed: []string{
					"testflight",
				},
			},
			{
				Name:     "bin-rc",
				Resource: "bin-rc",
				Passed: []string{
					"bin-testflight",
				},
			},
			{
				Name:     "bosh-stemcell",
				Resource: "aws-stemcell",
			},
			{
				Name:     "deployments",
				Resource: "deployments",
			},
		},

		Result: Result{
			"candidate-release":    "imported-r238v448886",
			"deployments":          "imported-r45v448469",
			"bosh-stemcell":        "imported-r48v443997",
			"bin-rc":               "imported-r765v448889",
			"garden-linux-release": "imported-r17v443811",
			"version":              "imported-r12v448884",
			"concourse":            "imported-r62v448881",
		},
	}),
)
<|endoftext|>


<commit_before>package critters

type Slimupation uint64

const (
	SlimupationMage Slimupation = iota
	SlimupationBrute
	SlimupationSticky

	slimupationCount
)

var slimupationInfo = [slimupationCount]struct {
	title  string
	flavor string
}{
	SlimupationMage: {
		title:  "mage",
		flavor: "with magical properties."},
	SlimupationBrute: {
		title:  "brute",
		flavor: "that can bench press a whole hero."},
	SlimupationSticky: {
		title:  "citizen",
		flavor: "that's a little stickier than most."},
}
<commit_msg>whoops, please kill me<commit_after>package critter

type Slimupation uint64

const (
	SlimupationMage Slimupation = iota
	SlimupationBrute
	SlimupationSticky

	slimupationCount
)

var slimupationInfo = [slimupationCount]struct {
	title  string
	flavor string
}{
	SlimupationMage: {
		title:  "mage",
		flavor: "with magical properties."},
	SlimupationBrute: {
		title:  "brute",
		flavor: "that can bench press a whole hero."},
	SlimupationSticky: {
		title:  "citizen",
		flavor: "that's a little stickier than most."},
}
<|endoftext|>


<commit_before>package cron

import (
	"fmt"
	"github.com/Cepave/alarm/g"
	"github.com/Cepave/common/model"
	"github.com/Cepave/common/utils"
)

func BuildCommonSMSContent(event *model.Event) string {
	return fmt.Sprintf(
		"[P%d][%s][%s][][%s %s %s %s %s%s%s][O%d %s]",
		event.Priority(),
		event.Status,
		event.Endpoint,
		event.Note(),
		event.Func(),
		event.Metric(),
		utils.SortedTags(event.PushedTags),
		utils.ReadableFloat(event.LeftValue),
		event.Operator(),
		utils.ReadableFloat(event.RightValue()),
		event.CurrentStep,
		event.FormattedTime(),
	)
}

func BuildCommonMailContent(event *model.Event) string {
	link := g.Link(event)
	tdtl := `style="border: 1px solid #ccc; background: #FFF4F4;"`
	tdtr := `style="border: 1px solid #ccc; border-left: none;"`
	tdl  := `style="border: 1px solid #ccc; border-top:  none; background: #FFF4F4;"`
	tdr  := `style="border: 1px solid #ccc; border-top:  none; border-left: none;"`
	return fmt.Sprintf(
		`<meta charset="utf-8">

                                        %s
                                        %d

                                        Endpoint:
                                        %s

                                        Metric:
                                        %s

                                        Tags:
                                        %s

                                        %s
                                        %s%s%s

                                        Note:
                                        %s

                                        Max:
                                        %d

                                        Current:
                                        %d

                                        Timesramp:
                                        %s

			%s
		`,

		tdtl, event.Status, tdtr, event.Priority(),
		tdl, tdr, event.Endpoint,
		tdl, tdr, event.Metric(),
		tdl, tdr, utils.SortedTags(event.PushedTags),
		tdl, event.Func(), tdr, utils.ReadableFloat(event.LeftValue), event.Operator(),	utils.ReadableFloat(event.RightValue()),
		tdl, tdr, event.Note(),
		tdl, tdr, event.MaxStep(),
		tdl, tdr, event.CurrentStep,
		tdl, tdr, event.FormattedTime(),
		link,
		link,
	)
}

func BuildCommonQQContent(event *model.Event) string {
	link := g.Link(event)
	return fmt.Sprintf(
		"%s\r\nP%d\r\nEndpoint:%s\r\nMetric:%s\r\nTags:%s\r\n%s: %s%s%s\r\nNote:%s\r\nMax:%d, Current:%d\r\nTimestamp:%s\r\n%s\r\n",
		event.Status,
		event.Priority(),
		event.Endpoint,
		event.Metric(),
		utils.SortedTags(event.PushedTags),
		event.Func(),
		utils.ReadableFloat(event.LeftValue),
		event.Operator(),
		utils.ReadableFloat(event.RightValue()),
		event.Note(),
		event.MaxStep(),
		event.CurrentStep,
		event.FormattedTime(),
		link,
	)
}

func GenerateSmsContent(event *model.Event) string {
	return BuildCommonSMSContent(event)
}

func GenerateMailContent(event *model.Event) string {
	return BuildCommonMailContent(event)
}

func GenerateQQContent(event *model.Event) string {
	return BuildCommonQQContent(event)
}

func GenerateServerchanContent(event *model.Event) string {
	return BuildCommonQQContent(event)
}<commit_msg>Fix mail content in sometime can't see table.<commit_after>package cron

import (
	"fmt"
	"github.com/Cepave/alarm/g"
	"github.com/Cepave/common/model"
	"github.com/Cepave/common/utils"
)

func BuildCommonSMSContent(event *model.Event) string {
	return fmt.Sprintf(
		"[P%d][%s][%s][][%s %s %s %s %s%s%s][O%d %s]",
		event.Priority(),
		event.Status,
		event.Endpoint,
		event.Note(),
		event.Func(),
		event.Metric(),
		utils.SortedTags(event.PushedTags),
		utils.ReadableFloat(event.LeftValue),
		event.Operator(),
		utils.ReadableFloat(event.RightValue()),
		event.CurrentStep,
		event.FormattedTime(),
	)
}

func BuildCommonMailContent(event *model.Event) string {
	link := g.Link(event)
	tdtl := `style="border: 1px solid #ccc; background: #FFF4F4;"`
	tdtr := `style="border: 1px solid #ccc; border-left: none;"`
	tdl  := `style="border: 1px solid #ccc; border-top:  none; background: #FFF4F4;"`
	tdr  := `style="border: 1px solid #ccc; border-top:  none; border-left: none;"`
	return fmt.Sprintf(
		`<meta charset="utf-8">

                                        %s
                                        %d

                                        Endpoint:
                                        %s

                                        Metric:
                                        %s

                                        Tags:
                                        %s

                                        %s
                                        %s%s%s

                                        Note:
                                        %s

                                        Max:
                                        %d

                                        Current:
                                        %d

                                        Timesramp:
                                        %s

			%s
		`,

		tdtl, event.Status, tdtr, event.Priority(),
		tdl, tdr, event.Endpoint,
		tdl, tdr, event.Metric(),
		tdl, tdr, utils.SortedTags(event.PushedTags),
		tdl, event.Func(), tdr, utils.ReadableFloat(event.LeftValue), event.Operator(),	utils.ReadableFloat(event.RightValue()),
		tdl, tdr, event.Note(),
		tdl, tdr, event.MaxStep(),
		tdl, tdr, event.CurrentStep,
		tdl, tdr, event.FormattedTime(),
		link,
		link,
	)
}

func BuildCommonQQContent(event *model.Event) string {
	link := g.Link(event)
	return fmt.Sprintf(
		"%s\r\nP%d\r\nEndpoint:%s\r\nMetric:%s\r\nTags:%s\r\n%s: %s%s%s\r\nNote:%s\r\nMax:%d, Current:%d\r\nTimestamp:%s\r\n%s\r\n",
		event.Status,
		event.Priority(),
		event.Endpoint,
		event.Metric(),
		utils.SortedTags(event.PushedTags),
		event.Func(),
		utils.ReadableFloat(event.LeftValue),
		event.Operator(),
		utils.ReadableFloat(event.RightValue()),
		event.Note(),
		event.MaxStep(),
		event.CurrentStep,
		event.FormattedTime(),
		link,
	)
}

func GenerateSmsContent(event *model.Event) string {
	return BuildCommonSMSContent(event)
}

func GenerateMailContent(event *model.Event) string {
	return BuildCommonMailContent(event)
}

func GenerateQQContent(event *model.Event) string {
	return BuildCommonQQContent(event)
}

func GenerateServerchanContent(event *model.Event) string {
	return BuildCommonQQContent(event)
}
<|endoftext|>


<commit_before>package shadowsocks

import (
	"sync"

	"v2ray.com/core/app"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/bufio"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/proxy"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/ray"
)

type Client struct {
	serverPicker protocol.ServerPicker
	meta         *proxy.OutboundHandlerMeta
}

func NewClient(config *ClientConfig, space app.Space, meta *proxy.OutboundHandlerMeta) (*Client, error) {
	serverList := protocol.NewServerList()
	for _, rec := range config.Server {
		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
	}
	client := &Client{
		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
		meta:         meta,
	}

	return client, nil
}

func (v *Client) Dispatch(destination v2net.Destination, payload *buf.Buffer, ray ray.OutboundRay) {
	defer payload.Release()
	defer ray.OutboundInput().Release()
	defer ray.OutboundOutput().Close()

	network := destination.Network

	var server *protocol.ServerSpec
	var conn internet.Connection

	err := retry.ExponentialBackoff(5, 100).On(func() error {
		server = v.serverPicker.PickServer()
		dest := server.Destination()
		dest.Network = network
		rawConn, err := internet.Dial(v.meta.Address, dest, v.meta.GetDialerOptions())
		if err != nil {
			return err
		}
		conn = rawConn

		return nil
	})
	if err != nil {
		log.Warning("Shadowsocks|Client: Failed to find an available destination:", err)
		return
	}
	log.Info("Shadowsocks|Client: Tunneling request to ", destination, " via ", server.Destination())

	conn.SetReusable(false)

	request := &protocol.RequestHeader{
		Version: Version,
		Address: destination.Address,
		Port:    destination.Port,
	}
	if destination.Network == v2net.Network_TCP {
		request.Command = protocol.RequestCommandTCP
	} else {
		request.Command = protocol.RequestCommandUDP
	}

	user := server.PickUser()
	rawAccount, err := user.GetTypedAccount()
	if err != nil {
		log.Warning("Shadowsocks|Client: Failed to get a valid user account: ", err)
		return
	}
	account := rawAccount.(*ShadowsocksAccount)
	request.User = user

	if account.OneTimeAuth == Account_Auto || account.OneTimeAuth == Account_Enabled {
		request.Option |= RequestOptionOneTimeAuth
	}

	if request.Command == protocol.RequestCommandTCP {
		bufferedWriter := bufio.NewWriter(conn)
		defer bufferedWriter.Release()

		bodyWriter, err := WriteTCPRequest(request, bufferedWriter)
		defer bodyWriter.Release()

		if err != nil {
			log.Info("Shadowsock|Client: Failed to write request: ", err)
			return
		}

		if err := bodyWriter.Write(payload); err != nil {
			log.Info("Shadowsocks|Client: Failed to write payload: ", err)
			return
		}

		var responseMutex sync.Mutex
		responseMutex.Lock()

		go func() {
			defer responseMutex.Unlock()

			responseReader, err := ReadTCPResponse(user, conn)
			if err != nil {
				log.Warning("Shadowsocks|Client: Failed to read response: ", err)
				return
			}

			if err := buf.PipeUntilEOF(responseReader, ray.OutboundOutput()); err != nil {
				log.Info("Shadowsocks|Client: Failed to transport all TCP response: ", err)
			}
		}()

		bufferedWriter.SetCached(false)
		if err := buf.PipeUntilEOF(ray.OutboundInput(), bodyWriter); err != nil {
			log.Info("Shadowsocks|Client: Failed to trasnport all TCP request: ", err)
		}

		responseMutex.Lock()
	}

	if request.Command == protocol.RequestCommandUDP {
		timedReader := v2net.NewTimeOutReader(16, conn)
		var responseMutex sync.Mutex
		responseMutex.Lock()

		go func() {
			defer responseMutex.Unlock()

			reader := &UDPReader{
				Reader: timedReader,
				User:   user,
			}

			if err := buf.PipeUntilEOF(reader, ray.OutboundOutput()); err != nil {
				log.Info("Shadowsocks|Client: Failed to transport all UDP response: ", err)
			}
		}()

		writer := &UDPWriter{
			Writer:  conn,
			Request: request,
		}
		if !payload.IsEmpty() {
			if err := writer.Write(payload); err != nil {
				log.Info("Shadowsocks|Client: Failed to write payload: ", err)
				return
			}
		}
		if err := buf.PipeUntilEOF(ray.OutboundInput(), writer); err != nil {
			log.Info("Shadowsocks|Client: Failed to transport all UDP request: ", err)
		}

		responseMutex.Lock()
	}
}

type ClientFactory struct{}

func (v *ClientFactory) StreamCapability() v2net.NetworkList {
	return v2net.NetworkList{
		Network: []v2net.Network{v2net.Network_TCP, v2net.Network_RawTCP},
	}
}

func (v *ClientFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) {
	return NewClient(rawConfig.(*ClientConfig), space, meta)
}
<commit_msg>Fix shadowsocks client handling proxied connection<commit_after>package shadowsocks

import (
	"sync"

	"v2ray.com/core/app"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/bufio"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/proxy"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/ray"
)

type Client struct {
	serverPicker protocol.ServerPicker
	meta         *proxy.OutboundHandlerMeta
}

func NewClient(config *ClientConfig, space app.Space, meta *proxy.OutboundHandlerMeta) (*Client, error) {
	serverList := protocol.NewServerList()
	for _, rec := range config.Server {
		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
	}
	client := &Client{
		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
		meta:         meta,
	}

	return client, nil
}

func (v *Client) Dispatch(destination v2net.Destination, payload *buf.Buffer, ray ray.OutboundRay) {
	defer payload.Release()
	defer ray.OutboundInput().Release()
	defer ray.OutboundOutput().Close()

	network := destination.Network

	var server *protocol.ServerSpec
	var conn internet.Connection

	err := retry.ExponentialBackoff(5, 100).On(func() error {
		server = v.serverPicker.PickServer()
		dest := server.Destination()
		dest.Network = network
		rawConn, err := internet.Dial(v.meta.Address, dest, v.meta.GetDialerOptions())
		if err != nil {
			return err
		}
		conn = rawConn

		return nil
	})
	if err != nil {
		log.Warning("Shadowsocks|Client: Failed to find an available destination:", err)
		return
	}
	log.Info("Shadowsocks|Client: Tunneling request to ", destination, " via ", server.Destination())

	conn.SetReusable(false)

	request := &protocol.RequestHeader{
		Version: Version,
		Address: destination.Address,
		Port:    destination.Port,
	}
	if destination.Network == v2net.Network_TCP {
		request.Command = protocol.RequestCommandTCP
	} else {
		request.Command = protocol.RequestCommandUDP
	}

	user := server.PickUser()
	rawAccount, err := user.GetTypedAccount()
	if err != nil {
		log.Warning("Shadowsocks|Client: Failed to get a valid user account: ", err)
		return
	}
	account := rawAccount.(*ShadowsocksAccount)
	request.User = user

	if account.OneTimeAuth == Account_Auto || account.OneTimeAuth == Account_Enabled {
		request.Option |= RequestOptionOneTimeAuth
	}

	if request.Command == protocol.RequestCommandTCP {
		bufferedWriter := bufio.NewWriter(conn)
		defer bufferedWriter.Release()

		bodyWriter, err := WriteTCPRequest(request, bufferedWriter)
		defer bodyWriter.Release()

		if err != nil {
			log.Info("Shadowsock|Client: Failed to write request: ", err)
			return
		}

		if !payload.IsEmpty() {
			if err := bodyWriter.Write(payload); err != nil {
				log.Info("Shadowsocks|Client: Failed to write payload: ", err)
				return
			}
		}

		var responseMutex sync.Mutex
		responseMutex.Lock()

		go func() {
			defer responseMutex.Unlock()

			responseReader, err := ReadTCPResponse(user, conn)
			if err != nil {
				log.Warning("Shadowsocks|Client: Failed to read response: ", err)
				return
			}

			if err := buf.PipeUntilEOF(responseReader, ray.OutboundOutput()); err != nil {
				log.Info("Shadowsocks|Client: Failed to transport all TCP response: ", err)
			}
		}()

		bufferedWriter.SetCached(false)
		if err := buf.PipeUntilEOF(ray.OutboundInput(), bodyWriter); err != nil {
			log.Info("Shadowsocks|Client: Failed to trasnport all TCP request: ", err)
		}

		responseMutex.Lock()
	}

	if request.Command == protocol.RequestCommandUDP {
		timedReader := v2net.NewTimeOutReader(16, conn)
		var responseMutex sync.Mutex
		responseMutex.Lock()

		go func() {
			defer responseMutex.Unlock()

			reader := &UDPReader{
				Reader: timedReader,
				User:   user,
			}

			if err := buf.PipeUntilEOF(reader, ray.OutboundOutput()); err != nil {
				log.Info("Shadowsocks|Client: Failed to transport all UDP response: ", err)
			}
		}()

		writer := &UDPWriter{
			Writer:  conn,
			Request: request,
		}
		if !payload.IsEmpty() {
			if err := writer.Write(payload); err != nil {
				log.Info("Shadowsocks|Client: Failed to write payload: ", err)
				return
			}
		}
		if err := buf.PipeUntilEOF(ray.OutboundInput(), writer); err != nil {
			log.Info("Shadowsocks|Client: Failed to transport all UDP request: ", err)
		}

		responseMutex.Lock()
	}
}

type ClientFactory struct{}

func (v *ClientFactory) StreamCapability() v2net.NetworkList {
	return v2net.NetworkList{
		Network: []v2net.Network{v2net.Network_TCP, v2net.Network_RawTCP},
	}
}

func (v *ClientFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) {
	return NewClient(rawConfig.(*ClientConfig), space, meta)
}
<|endoftext|>


<commit_before>package plugins

import (
	"strings"

	"fmt"
	"time"

	"bytes"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
)

type dmAction func(args []string, in *discordgo.Message, out **discordgo.MessageSend) (next dmAction)

type DM struct{}

const (
	DMReceiveChannelIDKey = "dm:receive:channel-id"
)

func (dm *DM) Commands() []string {
	return []string{
		"dm",
		"dms",
	}
}

func (dm *DM) Init(session *discordgo.Session) {

}

func (dm *DM) Uninit(session *discordgo.Session) {

}

func (dm *DM) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	defer helpers.Recover()

	session.ChannelTyping(msg.ChannelID)

	var result *discordgo.MessageSend
	args := strings.Fields(content)

	action := dm.actionStart
	for action != nil {
		action = action(args, msg, &result)
	}
}

func (dm *DM) actionStart(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	cache.GetSession().ChannelTyping(in.ChannelID)

	if len(args) < 1 {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}

	switch args[0] {
	case "send":
		return dm.actionSend
	case "receive":
		return dm.actionReceive
	}

	*out = dm.newMsg("bot.arguments.invalid")
	return dm.actionFinish
}

func (dm *DM) actionSend(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	if !helpers.IsRobyulMod(in.Author.ID) {
		*out = dm.newMsg("robyulmod.no_permission")
		return dm.actionFinish
	}

	if !(len(args) >= 3 || (len(args) >= 2 && len(in.Attachments) > 0)) {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}

	targetUser, err := helpers.GetUserFromMention(args[1])
	if err != nil {
		*out = dm.newMsg("bot.arguments.invalid")
		return dm.actionFinish
	}

	dmChannel, err := cache.GetSession().UserChannelCreate(targetUser.ID)
	helpers.Relax(err)

	parts := strings.Split(in.Content, args[1])
	if len(parts) < 2 {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}
	dmMessage := strings.TrimSpace(strings.Join(parts[1:], args[1]))

	dmMessageSend := &discordgo.MessageSend{
		Content: dmMessage,
	}
	var dmAttachmentUrl string
	if len(in.Attachments) > 0 {
		dmAttachmentUrl = in.Attachments[0].URL
		dmFile := helpers.NetGet(dmAttachmentUrl)
		dmMessageSend.File = &discordgo.File{Name: in.Attachments[0].Filename, Reader: bytes.NewReader(dmFile)}
	}

	_, err = helpers.SendComplex(dmChannel.ID, dmMessageSend)
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok && errD.Message.Code == discordgo.ErrCodeCannotSendMessagesToThisUser {
			*out = dm.newMsg("plugins.dm.send-error-cannot-dm")
			return dm.actionFinish
		}
	}
	helpers.Relax(err)
	dm.logger().WithField("RecipientUserID", args[1]).WithField("AuthorUserID", in.Author.ID).
		Info("send a DM: " + dmMessage + " Attachment: " + dmAttachmentUrl)

	*out = dm.newMsg(helpers.GetTextF("plugins.dm.send-success", targetUser.Username))
	return dm.actionFinish
}

func (dm *DM) actionReceive(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	if !helpers.IsRobyulMod(in.Author.ID) {
		*out = dm.newMsg("robyulmod.no_permission")
		return dm.actionFinish
	}

	var err error
	var targetChannel *discordgo.Channel
	if len(args) >= 2 {
		targetChannel, err = helpers.GetChannelFromMention(in, args[1])
		helpers.Relax(err)
	}

	if targetChannel != nil && targetChannel.ID != "" {
		err = helpers.SetBotConfigString(DMReceiveChannelIDKey, targetChannel.ID)
	} else {
		err = helpers.SetBotConfigString(DMReceiveChannelIDKey, "")
	}

	*out = dm.newMsg("plugins.dm.receive-success")
	return dm.actionFinish
}

func (dm *DM) actionFinish(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	_, err := helpers.SendComplex(in.ChannelID, *out)
	helpers.Relax(err)

	return nil
}

func (dm *DM) newMsg(content string) *discordgo.MessageSend {
	return &discordgo.MessageSend{Content: helpers.GetText(content)}
}

func (dm *DM) logger() *logrus.Entry {
	return cache.GetLogger().WithField("module", "dm")
}

func (dm *DM) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {
	if msg.Author.Bot == true {
		return
	}

	channel, err := helpers.GetChannel(msg.ChannelID)
	helpers.Relax(err)

	if channel.Type != discordgo.ChannelTypeDM {
		return
	}

	dmChannelID, _ := helpers.GetBotConfigString(DMReceiveChannelIDKey)
	if dmChannelID != "" {
		err = dm.repostDM(dmChannelID, msg)
		helpers.RelaxLog(err)
	}
}

func (dm *DM) repostDM(channelID string, message *discordgo.Message) (err error) {
	received, err := message.Timestamp.Parse()
	if err != nil {
		received = time.Now()
	}

	channel, err := helpers.GetChannel(channelID)
	if err != nil {
		return err
	}

	content := message.Content
	for _, attachment := range message.Attachments {
		content += "\n" + attachment.URL
	}

	embed := &discordgo.MessageEmbed{
		Author: &discordgo.MessageEmbedAuthor{
			Name: fmt.Sprintf("@%s DM'd Robyul:", message.Author.Username),
		},
		Description: content,
		Color:       0x0FADED,
		Footer: &discordgo.MessageEmbedFooter{
			Text: fmt.Sprintf("User ID: %s | Received at %s",
				message.Author.ID, received.Format(time.ANSIC)),
		},
		Fields: []*discordgo.MessageEmbedField{
			{
				Name: "Reply:",
				Value: fmt.Sprintf("`%sdm send %s <your message>`",
					helpers.GetPrefixForServer(channel.GuildID), message.Author.ID),
				Inline: false,
			},
		},
	}
	if message.Author.Avatar != "" {
		embed.Author.IconURL = message.Author.AvatarURL("128")
	}

	_, err = helpers.SendEmbed(channel.ID, embed)
	return err
}

func (dm *DM) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {

}

func (dm *DM) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {

}

func (dm *DM) OnMessageDelete(msg *discordgo.MessageDelete, session *discordgo.Session) {

}

func (dm *DM) OnReactionAdd(reaction *discordgo.MessageReactionAdd, session *discordgo.Session) {

}

func (dm *DM) OnReactionRemove(reaction *discordgo.MessageReactionRemove, session *discordgo.Session) {

}

func (dm *DM) OnGuildBanAdd(user *discordgo.GuildBanAdd, session *discordgo.Session) {

}

func (dm *DM) OnGuildBanRemove(user *discordgo.GuildBanRemove, session *discordgo.Session) {

}
<commit_msg>[dm] don't post DMs with empty content<commit_after>package plugins

import (
	"strings"

	"fmt"
	"time"

	"bytes"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Sirupsen/logrus"
	"github.com/bwmarrin/discordgo"
)

type dmAction func(args []string, in *discordgo.Message, out **discordgo.MessageSend) (next dmAction)

type DM struct{}

const (
	DMReceiveChannelIDKey = "dm:receive:channel-id"
)

func (dm *DM) Commands() []string {
	return []string{
		"dm",
		"dms",
	}
}

func (dm *DM) Init(session *discordgo.Session) {

}

func (dm *DM) Uninit(session *discordgo.Session) {

}

func (dm *DM) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	defer helpers.Recover()

	session.ChannelTyping(msg.ChannelID)

	var result *discordgo.MessageSend
	args := strings.Fields(content)

	action := dm.actionStart
	for action != nil {
		action = action(args, msg, &result)
	}
}

func (dm *DM) actionStart(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	cache.GetSession().ChannelTyping(in.ChannelID)

	if len(args) < 1 {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}

	switch args[0] {
	case "send":
		return dm.actionSend
	case "receive":
		return dm.actionReceive
	}

	*out = dm.newMsg("bot.arguments.invalid")
	return dm.actionFinish
}

func (dm *DM) actionSend(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	if !helpers.IsRobyulMod(in.Author.ID) {
		*out = dm.newMsg("robyulmod.no_permission")
		return dm.actionFinish
	}

	if !(len(args) >= 3 || (len(args) >= 2 && len(in.Attachments) > 0)) {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}

	targetUser, err := helpers.GetUserFromMention(args[1])
	if err != nil {
		*out = dm.newMsg("bot.arguments.invalid")
		return dm.actionFinish
	}

	dmChannel, err := cache.GetSession().UserChannelCreate(targetUser.ID)
	helpers.Relax(err)

	parts := strings.Split(in.Content, args[1])
	if len(parts) < 2 {
		*out = dm.newMsg("bot.arguments.too-few")
		return dm.actionFinish
	}
	dmMessage := strings.TrimSpace(strings.Join(parts[1:], args[1]))

	dmMessageSend := &discordgo.MessageSend{
		Content: dmMessage,
	}
	var dmAttachmentUrl string
	if len(in.Attachments) > 0 {
		dmAttachmentUrl = in.Attachments[0].URL
		dmFile := helpers.NetGet(dmAttachmentUrl)
		dmMessageSend.File = &discordgo.File{Name: in.Attachments[0].Filename, Reader: bytes.NewReader(dmFile)}
	}

	_, err = helpers.SendComplex(dmChannel.ID, dmMessageSend)
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok && errD.Message.Code == discordgo.ErrCodeCannotSendMessagesToThisUser {
			*out = dm.newMsg("plugins.dm.send-error-cannot-dm")
			return dm.actionFinish
		}
	}
	helpers.Relax(err)
	dm.logger().WithField("RecipientUserID", args[1]).WithField("AuthorUserID", in.Author.ID).
		Info("send a DM: " + dmMessage + " Attachment: " + dmAttachmentUrl)

	*out = dm.newMsg(helpers.GetTextF("plugins.dm.send-success", targetUser.Username))
	return dm.actionFinish
}

func (dm *DM) actionReceive(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	if !helpers.IsRobyulMod(in.Author.ID) {
		*out = dm.newMsg("robyulmod.no_permission")
		return dm.actionFinish
	}

	var err error
	var targetChannel *discordgo.Channel
	if len(args) >= 2 {
		targetChannel, err = helpers.GetChannelFromMention(in, args[1])
		helpers.Relax(err)
	}

	if targetChannel != nil && targetChannel.ID != "" {
		err = helpers.SetBotConfigString(DMReceiveChannelIDKey, targetChannel.ID)
	} else {
		err = helpers.SetBotConfigString(DMReceiveChannelIDKey, "")
	}

	*out = dm.newMsg("plugins.dm.receive-success")
	return dm.actionFinish
}

func (dm *DM) actionFinish(args []string, in *discordgo.Message, out **discordgo.MessageSend) dmAction {
	_, err := helpers.SendComplex(in.ChannelID, *out)
	helpers.Relax(err)

	return nil
}

func (dm *DM) newMsg(content string) *discordgo.MessageSend {
	return &discordgo.MessageSend{Content: helpers.GetText(content)}
}

func (dm *DM) logger() *logrus.Entry {
	return cache.GetLogger().WithField("module", "dm")
}

func (dm *DM) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {
	if msg.Author.Bot == true {
		return
	}

	channel, err := helpers.GetChannel(msg.ChannelID)
	helpers.Relax(err)

	if channel.Type != discordgo.ChannelTypeDM {
		return
	}

	dmChannelID, _ := helpers.GetBotConfigString(DMReceiveChannelIDKey)
	if dmChannelID != "" {
		err = dm.repostDM(dmChannelID, msg)
		helpers.RelaxLog(err)
	}
}

func (dm *DM) repostDM(channelID string, message *discordgo.Message) (err error) {
	received, err := message.Timestamp.Parse()
	if err != nil {
		received = time.Now()
	}

	channel, err := helpers.GetChannel(channelID)
	if err != nil {
		return err
	}

	content := message.Content
	for _, attachment := range message.Attachments {
		content += "\n" + attachment.URL
	}
	content = strings.TrimSpace(content)

	if content == "" {
		return nil
	}

	embed := &discordgo.MessageEmbed{
		Author: &discordgo.MessageEmbedAuthor{
			Name: fmt.Sprintf("@%s DM'd Robyul:", message.Author.Username),
		},
		Description: content,
		Color:       0x0FADED,
		Footer: &discordgo.MessageEmbedFooter{
			Text: fmt.Sprintf("User ID: %s | Received at %s",
				message.Author.ID, received.Format(time.ANSIC)),
		},
		Fields: []*discordgo.MessageEmbedField{
			{
				Name: "Reply:",
				Value: fmt.Sprintf("`%sdm send %s <your message>`",
					helpers.GetPrefixForServer(channel.GuildID), message.Author.ID),
				Inline: false,
			},
		},
	}
	if message.Author.Avatar != "" {
		embed.Author.IconURL = message.Author.AvatarURL("128")
	}

	_, err = helpers.SendEmbed(channel.ID, embed)
	return err
}

func (dm *DM) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {

}

func (dm *DM) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {

}

func (dm *DM) OnMessageDelete(msg *discordgo.MessageDelete, session *discordgo.Session) {

}

func (dm *DM) OnReactionAdd(reaction *discordgo.MessageReactionAdd, session *discordgo.Session) {

}

func (dm *DM) OnReactionRemove(reaction *discordgo.MessageReactionRemove, session *discordgo.Session) {

}

func (dm *DM) OnGuildBanAdd(user *discordgo.GuildBanAdd, session *discordgo.Session) {

}

func (dm *DM) OnGuildBanRemove(user *discordgo.GuildBanRemove, session *discordgo.Session) {

}
<|endoftext|>