<commit_before>package s3

import (
	"context"
	"errors"
	"fmt"
	"log"
	"io/ioutil"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/aws/smithy-go"
	"github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/env"
	"github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/sentinel"
)

type Client struct {
	s3 *s3.Client
	bucket string
	region string
}

func getRegion(ctx context.Context) (string, error) {
	if region := os.Getenv("AWS_DEFAULT_REGION"); len(region) > 0 {
		return region, nil
	}

	imdsClient := imds.New(imds.Options{})
	if result, err := imdsClient.GetRegion(ctx, nil); err == nil {
		if len(result.Region) > 0 {
			return result.Region, nil
		}
	}

	return "", errors.New("Unknown current region")
}

func New(log *log.Logger, bucket string, regionHint string) (*Client, error) {
	ctx := context.Background()

	var awsConfig aws.Config
	var err error

	if regionHint != "" {
		// If there is a region hint provided, we use it unconditionally
		awsConfig, err = config.LoadDefaultConfig(ctx,
			config.WithRegion(regionHint),
		)
		if err != nil {
			return nil, fmt.Errorf("Could not load the AWS SDK config (%v)", err)
		}
	} else {
		// Otherwise, use the current region (or a guess) to dynamically find
		// where the bucket lives.
		region, err := getRegion(ctx)
		if err != nil {
			// Ignore error and fallback to us-east-1 for bucket lookup
			region = "us-east-1"
		}

		awsConfig, err = config.LoadDefaultConfig(ctx,
			config.WithRegion(region),
		)
		if err != nil {
			return nil, fmt.Errorf("Could not load the AWS SDK config (%v)", err)
		}

		log.Printf("Discovered current region as %q\n", awsConfig.Region)

		bucketRegion, err := manager.GetBucketRegion(ctx, s3.NewFromConfig(awsConfig), bucket)
		if err == nil && bucketRegion != "" {
			log.Printf("Discovered bucket region as %q\n", bucketRegion)
			awsConfig.Region = bucketRegion
		} else {
			log.Printf("Could not discover bucket region for %q. Using the %q region as a fallback, configure a bucket region using the %q environment variable. (%v)\n", bucket, awsConfig.Region, env.EnvRegion, err)
		}
	}

	return &Client{
		s3: s3.NewFromConfig(awsConfig),
		bucket: bucket,
		region: awsConfig.Region,
	}, nil
}

func (c *Client) Bucket() (string) {
	return c.bucket
}

func (c *Client) Region() (string) {
	return c.region
}

// Get downloads an object from S3.
// Intended for small files; object is fully read into memory.
// sentinel.ErrNotFound and sentinel.ErrForbidden are returned for those cases.
// Other errors are returned verbatim.
func (c *Client) Get(key string) ([]byte, error) {
	out, err := c.s3.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: &c.bucket,
		Key:    &key,
	})
	if err != nil {
		var noSuchKey *types.NoSuchKey
		if errors.As(err, &noSuchKey) {
			return nil, sentinel.ErrNotFound
		}

		// Possible values can be found at https://docs.aws.amazon.com/AmazonS3/latest/API/API_Error.html
		var apiErr smithy.APIError
		if errors.As(err, &apiErr) {
			code := apiErr.ErrorCode()
			if code == "AccessDenied" {
				return nil, sentinel.ErrForbidden
			}
		}

		return nil, fmt.Errorf("Could not GetObject (%s) in bucket (%s). Ensure your IAM Identity has s3:GetObject permission for this key and bucket. (%v)", key, c.bucket, err)
	}
	defer out.Body.Close()
	// we probably should return io.Reader or io.ReadCloser rather than []byte,
	// maybe somebody should refactor that (and all the tests etc) one day.
	return ioutil.ReadAll(out.Body)
}

// BucketExists returns whether the bucket exists.
// 200 OK returns true without error.
// 404 Not Found and 403 Forbidden return false without error.
// Other errors result in false with an error.
func (c *Client) BucketExists() (bool, error) {
	if _, err := c.s3.HeadBucket(context.TODO(), &s3.HeadBucketInput{Bucket: &c.bucket}); err != nil {
		return false, fmt.Errorf("Could not HeadBucket (%s). Ensure your IAM Identity has s3:ListBucket permission for this bucket. (%v)", c.bucket, err)
	}
	return true, nil
}
<commit_msg>Improve errors in s3.go<commit_after>package s3

import (
	"context"
	"errors"
	"fmt"
	"log"
	"io/ioutil"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/aws/smithy-go"
	"github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/env"
	"github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/sentinel"
)

type Client struct {
	s3 *s3.Client
	bucket string
	region string
}

func getCurrentRegion(ctx context.Context) (string, error) {
	if region := os.Getenv("AWS_DEFAULT_REGION"); len(region) > 0 {
		return region, nil
	}

	imdsClient := imds.New(imds.Options{})
	if result, err := imdsClient.GetRegion(ctx, nil); err == nil {
		if len(result.Region) > 0 {
			return result.Region, nil
		}
	}

	return "", errors.New("Unknown current region")
}

func New(log *log.Logger, bucket string, regionHint string) (*Client, error) {
	ctx := context.Background()

	var awsConfig aws.Config
	var err error

	if regionHint != "" {
		// If there is a region hint provided, we use it unconditionally
		awsConfig, err = config.LoadDefaultConfig(ctx,
			config.WithRegion(regionHint),
		)
		if err != nil {
			return nil, fmt.Errorf("Could not load the AWS SDK config (%v)", err)
		}
	} else {
		// Otherwise, use the current region (or a guess) to dynamically find
		// where the bucket lives.
		region, err := getCurrentRegion(ctx)
		if err != nil {
			// Ignore error and fallback to us-east-1 for bucket lookup
			region = "us-east-1"
		}

		awsConfig, err = config.LoadDefaultConfig(ctx,
			config.WithRegion(region),
		)
		if err != nil {
			return nil, fmt.Errorf("Could not load the AWS SDK config (%v)", err)
		}

		log.Printf("Discovered current region as %q\n", awsConfig.Region)

		bucketRegion, err := manager.GetBucketRegion(ctx, s3.NewFromConfig(awsConfig), bucket)
		if err == nil && bucketRegion != "" {
			log.Printf("Discovered bucket region as %q\n", bucketRegion)
			awsConfig.Region = bucketRegion
		} else {
			log.Printf("Could not discover region for bucket %q. Using the %q region as a fallback, if this is not correct configure a bucket region using the %q environment variable. (%v)\n", bucket, awsConfig.Region, env.EnvRegion, err)
		}
	}

	return &Client{
		s3: s3.NewFromConfig(awsConfig),
		bucket: bucket,
		region: awsConfig.Region,
	}, nil
}

func (c *Client) Bucket() (string) {
	return c.bucket
}

func (c *Client) Region() (string) {
	return c.region
}

// Get downloads an object from S3.
// Intended for small files; object is fully read into memory.
// sentinel.ErrNotFound and sentinel.ErrForbidden are returned for those cases.
// Other errors are returned verbatim.
func (c *Client) Get(key string) ([]byte, error) {
	out, err := c.s3.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: &c.bucket,
		Key:    &key,
	})
	if err != nil {
		var noSuchKey *types.NoSuchKey
		if errors.As(err, &noSuchKey) {
			return nil, sentinel.ErrNotFound
		}

		// Possible values can be found at https://docs.aws.amazon.com/AmazonS3/latest/API/API_Error.html
		var apiErr smithy.APIError
		if errors.As(err, &apiErr) {
			code := apiErr.ErrorCode()
			if code == "AccessDenied" {
				return nil, sentinel.ErrForbidden
			}
		}

		return nil, fmt.Errorf("Could not GetObject (%s) in bucket (%s). Ensure your IAM Identity has s3:GetObject permission for this key and bucket. (%v)", key, c.bucket, err)
	}
	defer out.Body.Close()
	// we probably should return io.Reader or io.ReadCloser rather than []byte,
	// maybe somebody should refactor that (and all the tests etc) one day.
	return ioutil.ReadAll(out.Body)
}

// BucketExists returns whether the bucket exists.
// 200 OK returns true without error.
// 404 Not Found and 403 Forbidden return false without error.
// Other errors result in false with an error.
func (c *Client) BucketExists() (bool, error) {
	if _, err := c.s3.HeadBucket(context.TODO(), &s3.HeadBucketInput{Bucket: &c.bucket}); err != nil {
		return false, fmt.Errorf("Could not HeadBucket (%s). Ensure your IAM Identity has s3:ListBucket permission for this bucket. (%v)", c.bucket, err)
	}
	return true, nil
}
<|endoftext|>


<commit_before>package app

import (
	"bytes"
	"code.google.com/p/go.tools/go/types"
	"fmt"
	"gnd.la/gen/genutil"
	"gnd.la/loaders"
	"gnd.la/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"path/filepath"
	"regexp"
	"strings"
)

const appFilename = "app.yaml"

type Templates struct {
	Path  string            `yaml:"path"`
	Hooks map[string]string `yaml:"hooks"`
}

type App struct {
	Dir       string
	Name      string            `yaml:"name"`
	Handlers  map[string]string `yaml:"handlers"`
	Vars      map[string]string `yaml:"vars"`
	Templates *Templates        `yaml:"templates"`
	Assets    string            `yaml:"assets"`
}

func (app *App) writeLoader(buf *bytes.Buffer, dir string, release bool) error {
	if release {
		return loaders.Bake(buf, dir, nil, loaders.CompressTgz)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	fmt.Fprintf(buf, "loaders.FSLoader(%q)\n", abs)
	return nil
}

func (app *App) Gen(release bool) error {
	pkg, err := genutil.NewPackage(app.Dir)
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "package %s\n\n", pkg.Name())
	buf.WriteString(genutil.AutogenString())
	buf.WriteString("import (\n\"gnd.la/app\"\n\"gnd.la/loaders\"\n\"gnd.la/template\"\n\"gnd.la/template/assets\"\n)\n")
	buf.WriteString("var _ = loaders.FSLoader\n")
	buf.WriteString("var _ = template.New\n")
	buf.WriteString("var _ = assets.NewManager\n")
	fmt.Fprintf(&buf, "var (\n App *app.App\n)\n")
	buf.WriteString("func init() {\n")
	buf.WriteString("App = app.New()\n")
	fmt.Fprintf(&buf, "App.SetName(%q)\n", app.Name)
	if app.Assets != "" {
		buf.WriteString("assetsLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Assets), release); err != nil {
			return err
		}
		buf.WriteString("const prefix = \"/assets/\"\n")
		buf.WriteString("manager := assets.NewManager(assetsLoader, prefix)\n")
		buf.WriteString("App.SetAssetsManager(manager)\n")
		buf.WriteString("assetsHandler := assets.Handler(manager)\n")
		buf.WriteString("App.Handle(\"^\"+prefix, func(ctx *app.Context) { assetsHandler(ctx, ctx.R) })\n")
	}
	scope := pkg.Scope()
	if len(app.Vars) > 0 {
		buf.WriteString("App.AddTemplateVars(map[string]interface{}{\n")
		for k, v := range app.Vars {
			ident := k
			name := v
			if name == "" {
				name = ident
			}
			obj := scope.Lookup(ident)
			if obj == nil {
				return fmt.Errorf("could not find identifier named %q", ident)
			}
			rhs := ident
			if va, ok := obj.(*types.Var); ok {
				tn := va.Type().String()
				if strings.Contains(tn, ".") {
					tn = "interface{}"
				}
				rhs = fmt.Sprintf("func() %s { return %s }", tn, ident)
			}
			fmt.Fprintf(&buf, "%q: %s,\n", name, rhs)
		}
		buf.WriteString("})\n")
	}
	if app.Templates != nil && app.Templates.Path != "" {
		buf.WriteString("templatesLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Templates.Path), release); err != nil {
			return err
		}
		buf.WriteString("App.SetTemplatesLoader(templatesLoader)\n")
		re := regexp.MustCompile("\\W")
		for k, v := range app.Templates.Hooks {
			var pos string
			switch strings.ToLower(v) {
			case "top":
				pos = "assets.Top"
			case "bottom":
				pos = "assets.Bottom"
			case "none":
				pos = "assets.None"
			default:
				return fmt.Errorf("invalid hook position %q", v)
			}
			suffix := re.ReplaceAllString(k, "_")
			fmt.Fprintf(&buf, "tmpl_%s, err := App.LoadTemplate(%q)\n", suffix, k)
			buf.WriteString("if err != nil {\npanic(err)\n}\n")
			fmt.Fprintf(&buf, "App.AddHook(&template.Hook{Template: tmpl_%s.Template(), Position: %s})\n", suffix, pos)
		}
	}
	for k, v := range app.Handlers {
		obj := scope.Lookup(k)
		if obj == nil {
			return fmt.Errorf("could not find handler named %q", k)
		}
		if _, err := regexp.Compile(v); err != nil {
			return fmt.Errorf("invalid pattern %q: %s", v, err)
		}
		switch obj.Type().String() {
		case "*gnd.la/app.HandlerInfo", "gnd.la/app.HandlerInfo":
			fmt.Fprintf(&buf, "App.HandleOptions(%q, %s.Handler, %s.Options)\n", v, obj.Name(), obj.Name())
		default:
			return fmt.Errorf("invalid handler type %s", obj.Type())
		}
	}
	buf.WriteString("}\n")
	out := filepath.Join(pkg.Dir(), "gondola_app.go")
	log.Debugf("Writing Gondola app to %s", out)
	return genutil.WriteAutogen(out, buf.Bytes())
}

func Parse(dir string) (*App, error) {
	appFile := filepath.Join(dir, appFilename)
	data, err := ioutil.ReadFile(appFile)
	if err != nil {
		return nil, fmt.Errorf("error reading %s: %s", appFilename, err)
	}
	var app *App
	if err := goyaml.Unmarshal(data, &app); err != nil {
		return nil, err
	}
	app.Dir = dir
	return app, nil
}
<commit_msg>Set up the handlers before initializing any hooks<commit_after>package app

import (
	"bytes"
	"code.google.com/p/go.tools/go/types"
	"fmt"
	"gnd.la/gen/genutil"
	"gnd.la/loaders"
	"gnd.la/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"path/filepath"
	"regexp"
	"strings"
)

const appFilename = "app.yaml"

type Templates struct {
	Path  string            `yaml:"path"`
	Hooks map[string]string `yaml:"hooks"`
}

type App struct {
	Dir       string
	Name      string            `yaml:"name"`
	Handlers  map[string]string `yaml:"handlers"`
	Vars      map[string]string `yaml:"vars"`
	Templates *Templates        `yaml:"templates"`
	Assets    string            `yaml:"assets"`
}

func (app *App) writeLoader(buf *bytes.Buffer, dir string, release bool) error {
	if release {
		return loaders.Bake(buf, dir, nil, loaders.CompressTgz)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		return err
	}
	fmt.Fprintf(buf, "loaders.FSLoader(%q)\n", abs)
	return nil
}

func (app *App) Gen(release bool) error {
	pkg, err := genutil.NewPackage(app.Dir)
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "package %s\n\n", pkg.Name())
	buf.WriteString(genutil.AutogenString())
	buf.WriteString("import (\n\"gnd.la/app\"\n\"gnd.la/loaders\"\n\"gnd.la/template\"\n\"gnd.la/template/assets\"\n)\n")
	buf.WriteString("var _ = loaders.FSLoader\n")
	buf.WriteString("var _ = template.New\n")
	buf.WriteString("var _ = assets.NewManager\n")
	fmt.Fprintf(&buf, "var (\n App *app.App\n)\n")
	buf.WriteString("func init() {\n")
	buf.WriteString("App = app.New()\n")
	fmt.Fprintf(&buf, "App.SetName(%q)\n", app.Name)
	if app.Assets != "" {
		buf.WriteString("assetsLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Assets), release); err != nil {
			return err
		}
		buf.WriteString("const prefix = \"/assets/\"\n")
		buf.WriteString("manager := assets.NewManager(assetsLoader, prefix)\n")
		buf.WriteString("App.SetAssetsManager(manager)\n")
		buf.WriteString("assetsHandler := assets.Handler(manager)\n")
		buf.WriteString("App.Handle(\"^\"+prefix, func(ctx *app.Context) { assetsHandler(ctx, ctx.R) })\n")
	}
	scope := pkg.Scope()
	if len(app.Vars) > 0 {
		buf.WriteString("App.AddTemplateVars(map[string]interface{}{\n")
		for k, v := range app.Vars {
			ident := k
			name := v
			if name == "" {
				name = ident
			}
			obj := scope.Lookup(ident)
			if obj == nil {
				return fmt.Errorf("could not find identifier named %q", ident)
			}
			rhs := ident
			if va, ok := obj.(*types.Var); ok {
				tn := va.Type().String()
				if strings.Contains(tn, ".") {
					tn = "interface{}"
				}
				rhs = fmt.Sprintf("func() %s { return %s }", tn, ident)
			}
			fmt.Fprintf(&buf, "%q: %s,\n", name, rhs)
		}
		buf.WriteString("})\n")
	}
	for k, v := range app.Handlers {
		obj := scope.Lookup(k)
		if obj == nil {
			return fmt.Errorf("could not find handler named %q", k)
		}
		if _, err := regexp.Compile(v); err != nil {
			return fmt.Errorf("invalid pattern %q: %s", v, err)
		}
		switch obj.Type().String() {
		case "*gnd.la/app.HandlerInfo", "gnd.la/app.HandlerInfo":
			fmt.Fprintf(&buf, "App.HandleOptions(%q, %s.Handler, %s.Options)\n", v, obj.Name(), obj.Name())
		default:
			return fmt.Errorf("invalid handler type %s", obj.Type())
		}
	}
	if app.Templates != nil && app.Templates.Path != "" {
		buf.WriteString("templatesLoader := ")
		if err := app.writeLoader(&buf, filepath.Join(app.Dir, app.Templates.Path), release); err != nil {
			return err
		}
		buf.WriteString("App.SetTemplatesLoader(templatesLoader)\n")
		re := regexp.MustCompile("\\W")
		for k, v := range app.Templates.Hooks {
			var pos string
			switch strings.ToLower(v) {
			case "top":
				pos = "assets.Top"
			case "bottom":
				pos = "assets.Bottom"
			case "none":
				pos = "assets.None"
			default:
				return fmt.Errorf("invalid hook position %q", v)
			}
			suffix := re.ReplaceAllString(k, "_")
			fmt.Fprintf(&buf, "tmpl_%s, err := App.LoadTemplate(%q)\n", suffix, k)
			buf.WriteString("if err != nil {\npanic(err)\n}\n")
			fmt.Fprintf(&buf, "App.AddHook(&template.Hook{Template: tmpl_%s.Template(), Position: %s})\n", suffix, pos)
		}
	}
	buf.WriteString("}\n")
	out := filepath.Join(pkg.Dir(), "gondola_app.go")
	log.Debugf("Writing Gondola app to %s", out)
	return genutil.WriteAutogen(out, buf.Bytes())
}

func Parse(dir string) (*App, error) {
	appFile := filepath.Join(dir, appFilename)
	data, err := ioutil.ReadFile(appFile)
	if err != nil {
		return nil, fmt.Errorf("error reading %s: %s", appFilename, err)
	}
	var app *App
	if err := goyaml.Unmarshal(data, &app); err != nil {
		return nil, err
	}
	app.Dir = dir
	return app, nil
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build integration
// +build integration

package gsakeysecretgenerator

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dynamic"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/tf"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
	testgcp "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/gcp"
	testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
	testmain "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/main"
	testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable"
	testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader"
	tfprovider "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/provider"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"

	"github.com/ghodss/yaml"
	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"golang.org/x/sync/semaphore"
	"google.golang.org/api/iam/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/wait"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var (
	mgr            manager.Manager
	expectedResult = reconcile.Result{}
)

func TestServiceAccountKey(t *testing.T) {
	t.Helper()
	kubeClient := mgr.GetClient()
	provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
	ctx := context.TODO()
	uuid := testvariable.NewUniqueId()
	project := testgcp.GetDefaultProjectID(t)
	iamClient := testgcp.NewIAMClient(t)
	testcontroller.SetupNamespaceForDefaultProject(t, kubeClient, project)

	saReconciler := newTestReconciler(t, mgr, repo.GetServiceAccountCRDPath(), provider)
	sakReconciler := newTestReconciler(t, mgr, repo.GetServiceAccountKeyCRDPath(), provider)
	generator := newSecretGenerator(t, mgr, repo.GetServiceAccountKeyCRDPath())
	// create the dependent service account
	sa := convertToUnstructAndReplaceName(t, uuid, project, "", "testdata/gsa.yaml")
	if err := kubeClient.Create(ctx, sa); err != nil {
		t.Fatalf("couldn't create google service account %v: %v", sa.GetName(), err)
	}
	saName := k8s.GetNamespacedName(sa)
	saRequest := reconcile.Request{NamespacedName: saName}
	if _, err := saReconciler.Reconcile(context.TODO(), saRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccount %v: %v", saName, err)
	}
	if err := kubeClient.Get(ctx, saName, sa); err != nil {
		t.Fatalf("error getting iamserviceaccount %v: %v", saName, err)
	}
	// create a service account key sample
	gsakey := convertToUnstructAndReplaceName(t, uuid, project, sa.GetName(), "testdata/gsakey.yaml")
	if err := kubeClient.Create(ctx, gsakey); err != nil {
		t.Fatalf("couldn't create google service account key %v: %v", sa.GetName(), err)
	}
	sakName := k8s.GetNamespacedName(gsakey)
	sakRequest := reconcile.Request{NamespacedName: sakName}
	if _, err := sakReconciler.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v: %v", sakName, err)
	}
	if err := kubeClient.Get(ctx, sakName, gsakey); err != nil {
		t.Fatalf("error getting iamserviceaccountkey %v: %v", sakName, err)
	}
	// check the status and make sure the underlying resource is created
	keyName, found, err := unstructured.NestedString(gsakey.Object, "status", "name")
	if !found || err != nil {
		t.Fatalf("couldn't find name from %v status: %v", gsakey.GetName(), err)
	}
	if _, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyName).Do(); err != nil {
		t.Fatalf("error calling iam service to get the service account key %v: %v", keyName, err)
	}
	// invoke the secret generator
	if _, err := generator.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v to create a secret: %v", sakName, err)
	}
	// check the event about the service account key
	secretCreated := false
	eventList := &v1.EventList{}
	if err := kubeClient.List(context.TODO(), eventList, &client.ListOptions{Namespace: gsakey.GetNamespace()}); err != nil {
		t.Fatalf("unable to list objects: %v", err)
	}
	events := testcontroller.CollectEvents(t, mgr.GetConfig(), gsakey.GetNamespace(), 5, 5*time.Second)
	for _, e := range events {
		obj := &e.InvolvedObject
		if (obj.Kind == gsakey.GetKind()) && (obj.Namespace == gsakey.GetNamespace()) && (obj.Name == gsakey.GetName()) {
			if e.Reason == "Created" && e.Message == fmt.Sprintf("secret %v in namespace %v Successfully created", gsakey.GetName(), gsakey.GetNamespace()) {
				secretCreated = true
				break
			}
		}
	}
	if !secretCreated {
		t.Fatalf("no event found to show the secret %v is created", gsakey.GetName())
	}
	// delete the service account key object
	testk8s.RemoveDeletionDefenderFinalizerForUnstructured(t, gsakey, kubeClient)
	if err := kubeClient.Delete(ctx, gsakey); err != nil {
		t.Fatalf("error deleting iamserviceaccountkey %v: %v", gsakey.GetName(), err)
	}
	if _, err := sakReconciler.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v: %v", sakName, err)
	}
	verifyGSAKeyRemoved(t, iamClient, keyName)

	// clean up the dependent service account
	accountName := fmt.Sprintf("projects/%v/serviceAccounts/%v@%v.iam.gserviceaccount.com", project, sa.GetName(), project)
	iamClient.Projects.ServiceAccounts.Delete(accountName).Do()
}

func verifyGSAKeyRemoved(t *testing.T, iamClient *iam.Service, keyName string) {
	// iam is eventually consistent so poll until the key is not found
	err := wait.PollImmediate(10*time.Second, 2*time.Minute, func() (done bool, err error) {
		if _, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyName).Do(); err == nil || !gcp.IsNotFoundError(err) {
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		t.Fatalf("the underlying gsa key %v doesn't get removed: %v", keyName, err)
	}
}

func newTestReconciler(t *testing.T, mgr manager.Manager, crdPath string, provider *tfschema.Provider) reconcile.Reconciler {
	crdPath, err := filepath.Abs(crdPath)
	if err != nil {
		t.Fatalf("error getting path to CRD: %v", err)
	}
	crd := dynamic.UnmarshalFileToCRD(t, crdPath)
	smLoader := testservicemappingloader.New(t)
	// Set 'immediateReconcileRequests' and 'resourceWatcherRoutines'
	// to nil to disable reconciler's ability to create asynchronous
	// watches on unready dependencies. This feature of the reconciler
	// is unnecessary for our integration tests since we reconcile
	// each dependency first before the resource under test is
	// reconciled. Overall, the feature adds risk of complications
	// due to it's multi-threaded nature.
	var immediateReconcileRequests chan event.GenericEvent = nil
	var resourceWatcherRoutines *semaphore.Weighted = nil

	reconciler, err := tf.NewReconciler(mgr, crd, provider, smLoader, immediateReconcileRequests, resourceWatcherRoutines)
	if err != nil {
		t.Fatalf("error creating reconciler: %v", err)
	}
	return reconciler
}

func newSecretGenerator(t *testing.T, mgr manager.Manager, crdPath string) reconcile.Reconciler {
	crdPath, err := filepath.Abs(crdPath)
	if err != nil {
		t.Fatalf("error getting path to CRD: %v", err)
	}
	crd := dynamic.UnmarshalFileToCRD(t, crdPath)

	reconciler := newReconciler(mgr, crd)
	return reconciler
}

func convertToUnstructAndReplaceName(t *testing.T, testId, testNamespace, sa string, fileName string) *unstructured.Unstructured {
	b, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("error reading file '%v': %v", fileName, err)
	}
	s := string(b)
	s = strings.Replace(s, "${uniqueId}", testId, -1)
	s = strings.Replace(s, "${projectId}", testNamespace, -1)
	s = strings.Replace(s, "${IAMServiceAccount}", sa, -1)
	b = []byte(s)

	// Convert new bytes to unstructured object
	u := &unstructured.Unstructured{}
	err = yaml.Unmarshal(b, u)
	if err != nil {
		t.Fatalf("error unmarshalling bytes to CRD: %v", err)
	}
	return u
}

func TestMain(m *testing.M) {
	testmain.TestMainForIntegrationTests(m, &mgr)
}
<commit_msg>Automated Config Connector import.<commit_after>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build integration
// +build integration

package gsakeysecretgenerator

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dynamic"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/tf"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
	testgcp "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/gcp"
	testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
	testmain "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/main"
	testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable"
	testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader"
	tfprovider "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/provider"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"

	"github.com/ghodss/yaml"
	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"golang.org/x/sync/semaphore"
	"google.golang.org/api/iam/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/wait"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var (
	mgr            manager.Manager
	expectedResult = reconcile.Result{}
)

func TestServiceAccountKey(t *testing.T) {
	t.Helper()
	kubeClient := mgr.GetClient()
	provider := tfprovider.NewOrLogFatal(tfprovider.DefaultConfig)
	ctx := context.TODO()
	uuid := testvariable.NewUniqueId()
	project := testgcp.GetDefaultProjectID(t)
	iamClient := testgcp.NewIAMClient(t)
	testcontroller.SetupNamespaceForDefaultProject(t, kubeClient, project)

	saReconciler := newTestReconciler(t, mgr, repo.GetServiceAccountCRDPath(), provider)
	sakReconciler := newTestReconciler(t, mgr, repo.GetServiceAccountKeyCRDPath(), provider)
	generator := newSecretGenerator(t, mgr, repo.GetServiceAccountKeyCRDPath())
	// create the dependent service account
	sa := convertToUnstructAndReplaceName(t, uuid, project, "", "testdata/gsa.yaml")
	if err := kubeClient.Create(ctx, sa); err != nil {
		t.Fatalf("couldn't create google service account %v: %v", sa.GetName(), err)
	}
	saName := k8s.GetNamespacedName(sa)
	saRequest := reconcile.Request{NamespacedName: saName}
	if _, err := saReconciler.Reconcile(context.TODO(), saRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccount %v: %v", saName, err)
	}
	if err := kubeClient.Get(ctx, saName, sa); err != nil {
		t.Fatalf("error getting iamserviceaccount %v: %v", saName, err)
	}
	// create a service account key sample
	gsakey := convertToUnstructAndReplaceName(t, uuid, project, sa.GetName(), "testdata/gsakey.yaml")
	if err := kubeClient.Create(ctx, gsakey); err != nil {
		t.Fatalf("couldn't create google service account key %v: %v", sa.GetName(), err)
	}
	sakName := k8s.GetNamespacedName(gsakey)
	sakRequest := reconcile.Request{NamespacedName: sakName}
	if _, err := sakReconciler.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v: %v", sakName, err)
	}
	if err := kubeClient.Get(ctx, sakName, gsakey); err != nil {
		t.Fatalf("error getting iamserviceaccountkey %v: %v", sakName, err)
	}
	// check the status and make sure the underlying resource is created
	keyName, found, err := unstructured.NestedString(gsakey.Object, "status", "name")
	if !found || err != nil {
		t.Fatalf("couldn't find name from %v status: %v", gsakey.GetName(), err)
	}

	// Wait for key to propagate
	// Per https://cloud.google.com/iam/docs/creating-managing-service-account-keys:
	// "After you create a key, you might need to wait for 60 seconds or more before you perform another operation with the key."
	var lastErr error
	if err := wait.PollImmediate(10*time.Second, 2*time.Minute, func() (bool, error) {
		if _, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyName).Do(); err != nil {
			lastErr = err
			return false, nil
		} else {
			return true, nil
		}
	}); err != nil {
		t.Fatalf("error calling iam service to get the service account key %v (despite polling, lastErr=%v): %v", keyName, lastErr, err)
	}

	// invoke the secret generator
	if _, err := generator.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v to create a secret: %v", sakName, err)
	}
	// check the event about the service account key
	secretCreated := false
	eventList := &v1.EventList{}
	if err := kubeClient.List(context.TODO(), eventList, &client.ListOptions{Namespace: gsakey.GetNamespace()}); err != nil {
		t.Fatalf("unable to list objects: %v", err)
	}
	events := testcontroller.CollectEvents(t, mgr.GetConfig(), gsakey.GetNamespace(), 5, 5*time.Second)
	for _, e := range events {
		obj := &e.InvolvedObject
		if (obj.Kind == gsakey.GetKind()) && (obj.Namespace == gsakey.GetNamespace()) && (obj.Name == gsakey.GetName()) {
			if e.Reason == "Created" && e.Message == fmt.Sprintf("secret %v in namespace %v Successfully created", gsakey.GetName(), gsakey.GetNamespace()) {
				secretCreated = true
				break
			}
		}
	}
	if !secretCreated {
		t.Fatalf("no event found to show the secret %v is created", gsakey.GetName())
	}
	// delete the service account key object
	testk8s.RemoveDeletionDefenderFinalizerForUnstructured(t, gsakey, kubeClient)
	if err := kubeClient.Delete(ctx, gsakey); err != nil {
		t.Fatalf("error deleting iamserviceaccountkey %v: %v", gsakey.GetName(), err)
	}
	if _, err := sakReconciler.Reconcile(context.TODO(), sakRequest); err != nil {
		t.Fatalf("error reconciling iamserviceaccountkey %v: %v", sakName, err)
	}
	verifyGSAKeyRemoved(t, iamClient, keyName)

	// clean up the dependent service account
	accountName := fmt.Sprintf("projects/%v/serviceAccounts/%v@%v.iam.gserviceaccount.com", project, sa.GetName(), project)
	iamClient.Projects.ServiceAccounts.Delete(accountName).Do()
}

func verifyGSAKeyRemoved(t *testing.T, iamClient *iam.Service, keyName string) {
	// iam is eventually consistent so poll until the key is not found
	err := wait.PollImmediate(10*time.Second, 2*time.Minute, func() (done bool, err error) {
		if _, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyName).Do(); err == nil || !gcp.IsNotFoundError(err) {
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		t.Fatalf("the underlying gsa key %v doesn't get removed: %v", keyName, err)
	}
}

func newTestReconciler(t *testing.T, mgr manager.Manager, crdPath string, provider *tfschema.Provider) reconcile.Reconciler {
	crdPath, err := filepath.Abs(crdPath)
	if err != nil {
		t.Fatalf("error getting path to CRD: %v", err)
	}
	crd := dynamic.UnmarshalFileToCRD(t, crdPath)
	smLoader := testservicemappingloader.New(t)
	// Set 'immediateReconcileRequests' and 'resourceWatcherRoutines'
	// to nil to disable reconciler's ability to create asynchronous
	// watches on unready dependencies. This feature of the reconciler
	// is unnecessary for our integration tests since we reconcile
	// each dependency first before the resource under test is
	// reconciled. Overall, the feature adds risk of complications
	// due to it's multi-threaded nature.
	var immediateReconcileRequests chan event.GenericEvent = nil
	var resourceWatcherRoutines *semaphore.Weighted = nil

	reconciler, err := tf.NewReconciler(mgr, crd, provider, smLoader, immediateReconcileRequests, resourceWatcherRoutines)
	if err != nil {
		t.Fatalf("error creating reconciler: %v", err)
	}
	return reconciler
}

func newSecretGenerator(t *testing.T, mgr manager.Manager, crdPath string) reconcile.Reconciler {
	crdPath, err := filepath.Abs(crdPath)
	if err != nil {
		t.Fatalf("error getting path to CRD: %v", err)
	}
	crd := dynamic.UnmarshalFileToCRD(t, crdPath)

	reconciler := newReconciler(mgr, crd)
	return reconciler
}

func convertToUnstructAndReplaceName(t *testing.T, testId, testNamespace, sa string, fileName string) *unstructured.Unstructured {
	b, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("error reading file '%v': %v", fileName, err)
	}
	s := string(b)
	s = strings.Replace(s, "${uniqueId}", testId, -1)
	s = strings.Replace(s, "${projectId}", testNamespace, -1)
	s = strings.Replace(s, "${IAMServiceAccount}", sa, -1)
	b = []byte(s)

	// Convert new bytes to unstructured object
	u := &unstructured.Unstructured{}
	err = yaml.Unmarshal(b, u)
	if err != nil {
		t.Fatalf("error unmarshalling bytes to CRD: %v", err)
	}
	return u
}

func TestMain(m *testing.M) {
	testmain.TestMainForIntegrationTests(m, &mgr)
}
<|endoftext|>


<commit_before>package aqua

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/carbocation/interpose"
	"github.com/gorilla/mux"
	"github.com/tolexo/aero/auth"
	"github.com/tolexo/aero/cache"
	"github.com/tolexo/aero/conf"
	monit "github.com/tolexo/aero/monit"
	"github.com/tolexo/aero/panik"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type endPoint struct {
	caller     MethodInvoker
	info       Fixture
	httpMethod string

	isStdHttpHandler bool
	needsJarInput    bool

	muxUrl    string
	muxVars   []string
	modules   []func(http.Handler) http.Handler
	stash     cache.Cacher
	serviceId string
}

func NewEndPoint(inv MethodInvoker, f Fixture, matchUrl string, httpMethod string, mods map[string]func(http.Handler) http.Handler,
	caches map[string]cache.Cacher, serviceId string) endPoint {

	out := endPoint{
		caller:           inv,
		info:             f,
		isStdHttpHandler: false,
		needsJarInput:    false,
		muxUrl:           matchUrl,
		muxVars:          extractRouteVars(matchUrl),
		httpMethod:       httpMethod,
		modules:          make([]func(http.Handler) http.Handler, 0),
		stash:            nil,
		serviceId:        serviceId,
	}

	if f.Stub == "" {
		out.isStdHttpHandler = out.signatureMatchesDefaultHttpHandler()
		out.needsJarInput = out.needsVariableJar()

		out.validateMuxVarsMatchFuncInputs()
		out.validateFuncInputsAreOfRightType()
		out.validateFuncOutputsAreCorrect()
	}

	// Tag modules used by this endpoint
	if mods != nil && f.Modules != "" {
		names := strings.Split(f.Modules, ",")
		out.modules = make([]func(http.Handler) http.Handler, 0)
		for _, name := range names {
			name = strings.TrimSpace(name)
			fn, found := mods[name]
			if !found {
				panic(fmt.Sprintf("Module:%s not found", name))
			}
			out.modules = append(out.modules, fn)
		}
	}

	// Tag the cache
	if c, ok := caches[f.Cache]; ok {
		out.stash = c
	} else if f.Cache != "" {
		panic("Cache not found: " + f.Cache + " for " + matchUrl)
	}

	return out
}

func (me *endPoint) signatureMatchesDefaultHttpHandler() bool {
	return me.caller.outCount == 0 &&
		me.caller.inpCount == 2 &&
		me.caller.inpParams[0] == "i:net/http.ResponseWriter" &&
		me.caller.inpParams[1] == "*st:net/http.Request"
}

func (me *endPoint) needsVariableJar() bool {
	// needs jar input as the last parameter
	for i := 0; i < len(me.caller.inpParams)-1; i++ {
		if me.caller.inpParams[i] == "st:github.com/tolexo/aqua.Jar" {
			panic("Jar parameter should be the last one: " + me.caller.name)
		}
	}
	return me.caller.inpCount > 0 && me.caller.inpParams[me.caller.inpCount-1] == "st:github.com/tolexo/aqua.Jar"
}

func (me *endPoint) validateMuxVarsMatchFuncInputs() {
	// for non-standard http handlers, the mux vars count should match
	// the count of inputs to the user's method
	if !me.isStdHttpHandler {
		inputs := me.caller.inpCount
		if me.needsJarInput {
			inputs += -1
		}
		if len(me.muxVars) != inputs {
			panic(fmt.Sprintf("%s has %d inputs, but the func (%s) has %d",
				me.muxUrl, len(me.muxVars), me.caller.name, inputs))
		}
	}
}

func (me *endPoint) validateFuncInputsAreOfRightType() {
	if !me.isStdHttpHandler {
		for _, s := range me.caller.inpParams {
			switch s {
			case "st:github.com/tolexo/aqua.Jar":
			case "int":
			case "string":
			default:
				panic("Func input params should be 'int' or 'string'. Observed: " + s + " in: " + me.caller.name)
			}
		}
	}
}

func (me *endPoint) validateFuncOutputsAreCorrect() {

	var accepts = make(map[string]bool)
	accepts["string"] = true
	accepts["map"] = true
	accepts["st:github.com/tolexo/aqua.Sac"] = true
	accepts["*st:github.com/tolexo/aqua.Sac"] = true

	if !me.isStdHttpHandler {
		switch me.caller.outCount {
		case 1:
			_, found := accepts[me.caller.outParams[0]]
			if !found && !strings.HasPrefix(me.caller.outParams[0], "st:") {
				fmt.Println(me.caller.outParams[0])
				panic("Incorrect return type found in: " + me.caller.name)
			}
		case 2:
			if me.caller.outParams[0] != "int" {
				panic("When a func returns two params, the first must be an int (http status code) : " + me.caller.name)
			}
			_, found := accepts[me.caller.outParams[1]]
			if !found && !strings.HasPrefix(me.caller.outParams[1], "st:") {
				panic("Incorrect return type for second return param found in: " + me.caller.name)
			}
		default:
			panic("Incorrect number of returns for Func: " + me.caller.name)
		}
	}
}

// func middleman(next http.Handler) http.Handler {
// 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 		fmt.Println("In the middle >>>>")
// 		next.ServeHTTP(w, r)
// 		fmt.Println("And leaving middle <<<<")
// 	})
// }

func (me *endPoint) setupMuxHandlers(mux *mux.Router) {

	fn := handleIncoming(me)

	m := interpose.New()
	for i, _ := range me.modules {
		m.Use(me.modules[i])
		//fmt.Println("using module:", me.modules[i], reflect.TypeOf(me.modules[i]))
	}
	m.UseHandler(http.HandlerFunc(fn))

	if me.info.Version == "*" {
		mux.Handle(me.muxUrl, m).Methods(me.httpMethod)
	} else {
		urlWithVersion := cleanUrl(me.info.Prefix, "v"+me.info.Version, me.muxUrl)
		urlWithoutVersion := cleanUrl(me.info.Prefix, me.muxUrl)

		// versioned url
		mux.Handle(urlWithVersion, m).Methods(me.httpMethod)

		// content type (style1)
		header1 := fmt.Sprintf("application/%s-v%s+json", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header1)

		// content type (style2)
		header2 := fmt.Sprintf("application/%s+json;version=%s", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header2)
	}
}

func handleIncoming(e *endPoint) func(http.ResponseWriter, *http.Request) {

	// return stub
	if e.info.Stub != "" {
		return func(w http.ResponseWriter, r *http.Request) {
			d, err := getContent(e.info.Stub)
			if err == nil {
				fmt.Fprintf(w, "%s", d)
			} else {
				w.WriteHeader(400)
				fmt.Fprintf(w, "{ message: \"%s\"}", "Stub path not found")
			}
		}
	}

	return func(w http.ResponseWriter, r *http.Request) {

		cacheHit := false

		// TODO: create less local variables
		// TODO: move vars to closure level

		var (
			out     []reflect.Value
			logFp   *os.File
			fileErr error
		)
		//TODO: capture this using instrumentation handler
		defer func(reqStartTime time.Time) {
			go func() {
				if r := recover(); r != nil {
					var err error
					fmt.Println("reached Aqua")
					path := conf.String("logs.panic_log", "panic_log")
					path = fmt.Sprintf("%s.log", path)
					if logFp, fileErr = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666); fileErr == nil {
						switch panicError := r.(type) {
						case string:
							err = errors.New(panicError)
						case error:
							err = panicError
						default:
							err = errors.New("Unknown panic type")
						}
						logFp.WriteString(err.Error())
					} else {
						fmt.Println("Could not create the panic log file")
					}
					panic(r)
				}
				if e.serviceId != "" {
					respTime := time.Since(reqStartTime).Seconds() * 1000
					var responseCode int64 = 200
					if out != nil && len(out) == 2 && e.caller.outParams[0] == "int" {
						responseCode = out[0].Int()
					}
					monitorParams := monit.MonitorParams{
						ServiceId:    e.serviceId,
						RespTime:     respTime,
						ResponseCode: responseCode,
						CacheHit:     cacheHit,
					}
					monit.MonitorMe(monitorParams)
				}
			}()
		}(time.Now())

		//check authentication
		if e.info.Auth != "" {
			ok, errMsg := auth.AuthenticateRequest(r, e.info.Auth)
			if !ok { //print authentication error
				w.WriteHeader(401)
				w.Header().Set("Content-Type", "application/json")
				w.Header().Set("Content-Length", strconv.Itoa(len(errMsg)))
				fmt.Fprintf(w, "%s", errMsg)
				return
			}
		}

		var useCache bool = false
		var ttl time.Duration = 0 * time.Second
		var val []byte
		var err error

		if e.info.Ttl != "" {
			ttl, err = time.ParseDuration(e.info.Ttl)
			panik.On(err)
		}
		useCache = r.Method == "GET" && ttl > 0 && e.stash != nil

		muxVals := mux.Vars(r)
		params := make([]string, len(e.muxVars))
		for i, v := range e.muxVars {
			params[i] = muxVals[v]
		}

		if e.isStdHttpHandler {
			//TODO: caching of standard handler
			e.caller.Do([]reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)})
		} else {
			ref := convertToType(params, e.caller.inpParams)
			if e.needsJarInput {
				ref = append(ref, reflect.ValueOf(NewJar(r)))
			}

			if useCache {
				val, err = e.stash.Get(r.RequestURI)
				if err == nil {
					cacheHit = true
					// fmt.Print(".")
					out = decomposeCachedValues(val, e.caller.outParams)
				} else {
					out = e.caller.Do(ref)
					if len(out) == 2 && e.caller.outParams[0] == "int" {
						code := out[0].Int()
						if code < 200 || code > 299 {
							useCache = false
						}
					}
					if useCache {
						bytes := prepareForCaching(out, e.caller.outParams)
						e.stash.Set(r.RequestURI, bytes, ttl)
						// fmt.Print(":", len(bytes), r.RequestURI)
					}

				}
			} else {
				out = e.caller.Do(ref)
				// fmt.Print("!")
			}
			writeOutput(w, e.caller.outParams, out, e.info.Pretty)
		}
	}
}

func prepareForCaching(r []reflect.Value, outputParams []string) []byte {

	var err error
	buf := new(bytes.Buffer)
	encd := json.NewEncoder(buf)

	for i, _ := range r {
		switch outputParams[i] {
		case "int":
			err = encd.Encode(r[i].Int())
			panik.On(err)
		case "map":
			err = encd.Encode(r[i].Interface().(map[string]interface{}))
			panik.On(err)
		case "string":
			err = encd.Encode(r[i].String())
			panik.On(err)
		case "*st:github.com/tolexo/aqua.Sac":
			err = encd.Encode(r[i].Elem().Interface().(Sac).Data)
		default:
			panic("Unknown type of output to be sent to endpoint cache: " + outputParams[i])
		}
	}

	return buf.Bytes()
}

func decomposeCachedValues(data []byte, outputParams []string) []reflect.Value {

	var err error
	buf := bytes.NewBuffer(data)
	decd := json.NewDecoder(buf)
	out := make([]reflect.Value, len(outputParams))

	for i, o := range outputParams {
		switch o {
		case "int":
			var j int
			err = decd.Decode(&j)
			panik.On(err)
			out[i] = reflect.ValueOf(j)
		case "map":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			out[i] = reflect.ValueOf(m)
		case "string":
			var s string
			err = decd.Decode(&s)
			panik.On(err)
			out[i] = reflect.ValueOf(s)
		case "*st:github.com/tolexo/aqua.Sac":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			s := NewSac()
			s.Data = m
			out[i] = reflect.ValueOf(s)
		default:
			panic("Unknown type of output to be decoded from endpoint cache:" + o)
		}
	}

	return out

}
<commit_msg>TTA-226 moved recover to monit.go<commit_after>package aqua

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/carbocation/interpose"
	"github.com/gorilla/mux"
	"github.com/tolexo/aero/auth"
	"github.com/tolexo/aero/cache"
	monit "github.com/tolexo/aero/monit"
	"github.com/tolexo/aero/panik"
)

type endPoint struct {
	caller     MethodInvoker
	info       Fixture
	httpMethod string

	isStdHttpHandler bool
	needsJarInput    bool

	muxUrl    string
	muxVars   []string
	modules   []func(http.Handler) http.Handler
	stash     cache.Cacher
	serviceId string
}

func NewEndPoint(inv MethodInvoker, f Fixture, matchUrl string, httpMethod string, mods map[string]func(http.Handler) http.Handler,
	caches map[string]cache.Cacher, serviceId string) endPoint {

	out := endPoint{
		caller:           inv,
		info:             f,
		isStdHttpHandler: false,
		needsJarInput:    false,
		muxUrl:           matchUrl,
		muxVars:          extractRouteVars(matchUrl),
		httpMethod:       httpMethod,
		modules:          make([]func(http.Handler) http.Handler, 0),
		stash:            nil,
		serviceId:        serviceId,
	}

	if f.Stub == "" {
		out.isStdHttpHandler = out.signatureMatchesDefaultHttpHandler()
		out.needsJarInput = out.needsVariableJar()

		out.validateMuxVarsMatchFuncInputs()
		out.validateFuncInputsAreOfRightType()
		out.validateFuncOutputsAreCorrect()
	}

	// Tag modules used by this endpoint
	if mods != nil && f.Modules != "" {
		names := strings.Split(f.Modules, ",")
		out.modules = make([]func(http.Handler) http.Handler, 0)
		for _, name := range names {
			name = strings.TrimSpace(name)
			fn, found := mods[name]
			if !found {
				panic(fmt.Sprintf("Module:%s not found", name))
			}
			out.modules = append(out.modules, fn)
		}
	}

	// Tag the cache
	if c, ok := caches[f.Cache]; ok {
		out.stash = c
	} else if f.Cache != "" {
		panic("Cache not found: " + f.Cache + " for " + matchUrl)
	}

	return out
}

func (me *endPoint) signatureMatchesDefaultHttpHandler() bool {
	return me.caller.outCount == 0 &&
		me.caller.inpCount == 2 &&
		me.caller.inpParams[0] == "i:net/http.ResponseWriter" &&
		me.caller.inpParams[1] == "*st:net/http.Request"
}

func (me *endPoint) needsVariableJar() bool {
	// needs jar input as the last parameter
	for i := 0; i < len(me.caller.inpParams)-1; i++ {
		if me.caller.inpParams[i] == "st:github.com/tolexo/aqua.Jar" {
			panic("Jar parameter should be the last one: " + me.caller.name)
		}
	}
	return me.caller.inpCount > 0 && me.caller.inpParams[me.caller.inpCount-1] == "st:github.com/tolexo/aqua.Jar"
}

func (me *endPoint) validateMuxVarsMatchFuncInputs() {
	// for non-standard http handlers, the mux vars count should match
	// the count of inputs to the user's method
	if !me.isStdHttpHandler {
		inputs := me.caller.inpCount
		if me.needsJarInput {
			inputs += -1
		}
		if len(me.muxVars) != inputs {
			panic(fmt.Sprintf("%s has %d inputs, but the func (%s) has %d",
				me.muxUrl, len(me.muxVars), me.caller.name, inputs))
		}
	}
}

func (me *endPoint) validateFuncInputsAreOfRightType() {
	if !me.isStdHttpHandler {
		for _, s := range me.caller.inpParams {
			switch s {
			case "st:github.com/tolexo/aqua.Jar":
			case "int":
			case "string":
			default:
				panic("Func input params should be 'int' or 'string'. Observed: " + s + " in: " + me.caller.name)
			}
		}
	}
}

func (me *endPoint) validateFuncOutputsAreCorrect() {

	var accepts = make(map[string]bool)
	accepts["string"] = true
	accepts["map"] = true
	accepts["st:github.com/tolexo/aqua.Sac"] = true
	accepts["*st:github.com/tolexo/aqua.Sac"] = true

	if !me.isStdHttpHandler {
		switch me.caller.outCount {
		case 1:
			_, found := accepts[me.caller.outParams[0]]
			if !found && !strings.HasPrefix(me.caller.outParams[0], "st:") {
				fmt.Println(me.caller.outParams[0])
				panic("Incorrect return type found in: " + me.caller.name)
			}
		case 2:
			if me.caller.outParams[0] != "int" {
				panic("When a func returns two params, the first must be an int (http status code) : " + me.caller.name)
			}
			_, found := accepts[me.caller.outParams[1]]
			if !found && !strings.HasPrefix(me.caller.outParams[1], "st:") {
				panic("Incorrect return type for second return param found in: " + me.caller.name)
			}
		default:
			panic("Incorrect number of returns for Func: " + me.caller.name)
		}
	}
}

// func middleman(next http.Handler) http.Handler {
// 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 		fmt.Println("In the middle >>>>")
// 		next.ServeHTTP(w, r)
// 		fmt.Println("And leaving middle <<<<")
// 	})
// }

func (me *endPoint) setupMuxHandlers(mux *mux.Router) {

	fn := handleIncoming(me)

	m := interpose.New()
	for i, _ := range me.modules {
		m.Use(me.modules[i])
		//fmt.Println("using module:", me.modules[i], reflect.TypeOf(me.modules[i]))
	}
	m.UseHandler(http.HandlerFunc(fn))

	if me.info.Version == "*" {
		mux.Handle(me.muxUrl, m).Methods(me.httpMethod)
	} else {
		urlWithVersion := cleanUrl(me.info.Prefix, "v"+me.info.Version, me.muxUrl)
		urlWithoutVersion := cleanUrl(me.info.Prefix, me.muxUrl)

		// versioned url
		mux.Handle(urlWithVersion, m).Methods(me.httpMethod)

		// content type (style1)
		header1 := fmt.Sprintf("application/%s-v%s+json", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header1)

		// content type (style2)
		header2 := fmt.Sprintf("application/%s+json;version=%s", me.info.Vendor, me.info.Version)
		mux.Handle(urlWithoutVersion, m).Methods(me.httpMethod).Headers("Accept", header2)
	}
}

func handleIncoming(e *endPoint) func(http.ResponseWriter, *http.Request) {

	// return stub
	if e.info.Stub != "" {
		return func(w http.ResponseWriter, r *http.Request) {
			d, err := getContent(e.info.Stub)
			if err == nil {
				fmt.Fprintf(w, "%s", d)
			} else {
				w.WriteHeader(400)
				fmt.Fprintf(w, "{ message: \"%s\"}", "Stub path not found")
			}
		}
	}

	return func(w http.ResponseWriter, r *http.Request) {

		cacheHit := false

		// TODO: create less local variables
		// TODO: move vars to closure level

		var (
			out []reflect.Value
		)
		//TODO: capture this using instrumentation handler
		defer func(reqStartTime time.Time) {
			go func() {
				if e.serviceId != "" {
					respTime := time.Since(reqStartTime).Seconds() * 1000
					var responseCode int64 = 200
					if out != nil && len(out) == 2 && e.caller.outParams[0] == "int" {
						responseCode = out[0].Int()
					}
					monitorParams := monit.MonitorParams{
						ServiceId:    e.serviceId,
						RespTime:     respTime,
						ResponseCode: responseCode,
						CacheHit:     cacheHit,
					}
					monit.MonitorMe(monitorParams)
				}
				if r := recover(); r != nil {
					monit.PanicLogger(r)
				}
			}()
		}(time.Now())

		//check authentication
		if e.info.Auth != "" {
			ok, errMsg := auth.AuthenticateRequest(r, e.info.Auth)
			if !ok { //print authentication error
				w.WriteHeader(401)
				w.Header().Set("Content-Type", "application/json")
				w.Header().Set("Content-Length", strconv.Itoa(len(errMsg)))
				fmt.Fprintf(w, "%s", errMsg)
				return
			}
		}

		var useCache bool = false
		var ttl time.Duration = 0 * time.Second
		var val []byte
		var err error

		if e.info.Ttl != "" {
			ttl, err = time.ParseDuration(e.info.Ttl)
			panik.On(err)
		}
		useCache = r.Method == "GET" && ttl > 0 && e.stash != nil

		muxVals := mux.Vars(r)
		params := make([]string, len(e.muxVars))
		for i, v := range e.muxVars {
			params[i] = muxVals[v]
		}

		if e.isStdHttpHandler {
			//TODO: caching of standard handler
			e.caller.Do([]reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)})
		} else {
			ref := convertToType(params, e.caller.inpParams)
			if e.needsJarInput {
				ref = append(ref, reflect.ValueOf(NewJar(r)))
			}

			if useCache {
				val, err = e.stash.Get(r.RequestURI)
				if err == nil {
					cacheHit = true
					// fmt.Print(".")
					out = decomposeCachedValues(val, e.caller.outParams)
				} else {
					out = e.caller.Do(ref)
					if len(out) == 2 && e.caller.outParams[0] == "int" {
						code := out[0].Int()
						if code < 200 || code > 299 {
							useCache = false
						}
					}
					if useCache {
						bytes := prepareForCaching(out, e.caller.outParams)
						e.stash.Set(r.RequestURI, bytes, ttl)
						// fmt.Print(":", len(bytes), r.RequestURI)
					}

				}
			} else {
				out = e.caller.Do(ref)
				// fmt.Print("!")
			}
			writeOutput(w, e.caller.outParams, out, e.info.Pretty)
		}
	}
}

func prepareForCaching(r []reflect.Value, outputParams []string) []byte {

	var err error
	buf := new(bytes.Buffer)
	encd := json.NewEncoder(buf)

	for i, _ := range r {
		switch outputParams[i] {
		case "int":
			err = encd.Encode(r[i].Int())
			panik.On(err)
		case "map":
			err = encd.Encode(r[i].Interface().(map[string]interface{}))
			panik.On(err)
		case "string":
			err = encd.Encode(r[i].String())
			panik.On(err)
		case "*st:github.com/tolexo/aqua.Sac":
			err = encd.Encode(r[i].Elem().Interface().(Sac).Data)
		default:
			panic("Unknown type of output to be sent to endpoint cache: " + outputParams[i])
		}
	}

	return buf.Bytes()
}

func decomposeCachedValues(data []byte, outputParams []string) []reflect.Value {

	var err error
	buf := bytes.NewBuffer(data)
	decd := json.NewDecoder(buf)
	out := make([]reflect.Value, len(outputParams))

	for i, o := range outputParams {
		switch o {
		case "int":
			var j int
			err = decd.Decode(&j)
			panik.On(err)
			out[i] = reflect.ValueOf(j)
		case "map":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			out[i] = reflect.ValueOf(m)
		case "string":
			var s string
			err = decd.Decode(&s)
			panik.On(err)
			out[i] = reflect.ValueOf(s)
		case "*st:github.com/tolexo/aqua.Sac":
			var m map[string]interface{}
			err = decd.Decode(&m)
			panik.On(err)
			s := NewSac()
			s.Data = m
			out[i] = reflect.ValueOf(s)
		default:
			panic("Unknown type of output to be decoded from endpoint cache:" + o)
		}
	}

	return out

}
<|endoftext|>


<commit_before>package gli

import (
	"errors"
	"fmt"
	"image"
	"image/draw"
	"os"

	"github.com/go-gl/gl/v2.1/gl"
)

type Texture struct {
	id   uint32
	size image.Point
}

func LoadImage(file string) (*image.RGBA, error) {
	imgFile, err := os.Open(file)
	if err != nil {
		return nil, fmt.Errorf("texture %q not found on disk: %v", file, err)
	}
	img, _, err := image.Decode(imgFile)
	if err != nil {
		return nil, err
	}
	return FixImage(img), nil
}

func FixImage(img image.Image) *image.RGBA {
	rgba := image.NewRGBA(img.Bounds())
	draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
	return rgba
}

func (texture *Texture) Id() uint32 {
	return texture.id
}

func (texture *Texture) Use(unit int) {
	gl.ActiveTexture(gl.TEXTURE0 + uint32(unit))
	gl.BindTexture(gl.TEXTURE_2D, texture.id)
}

func (texture *Texture) Size() image.Point {
	return texture.size
}

func (texture *Texture) Delete() {
	gl.DeleteTextures(1, &texture.id)
}

func NewTexture(img *image.RGBA, opts ...TextureOption) (*Texture, error) {
	if img.Stride != img.Rect.Size().X*4 {
		return nil, errors.New("unsupported stride in texture image")
	}
	opt := textureOption{
		filterMin: LINEAR,
		filterMag: LINEAR,
		wrap_s:    REPEAT,
		wrap_t:    REPEAT,
	}
	for _, o := range opts {
		o(&opt)
	}

	var id uint32
	gl.GenTextures(1, &id)
	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, id)
	defer gl.BindTexture(gl.TEXTURE_2D, 0)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int32(opt.filterMin))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int32(opt.filterMag))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, int32(opt.wrap_s))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, int32(opt.wrap_t))
	gl.TexImage2D(
		gl.TEXTURE_2D,
		0,
		gl.RGBA,
		int32(img.Rect.Size().X),
		int32(img.Rect.Size().Y),
		0,
		gl.RGBA,
		gl.UNSIGNED_BYTE,
		gl.Ptr(img.Pix))
	return &Texture{
		id:   id,
		size: img.Rect.Size(),
	}, nil
}

type textureOption struct {
	filterMin TextureFilterEnum
	filterMag TextureFilterEnum
	wrap_s    TextureWrapEnum
	wrap_t    TextureWrapEnum
}

type TextureOption func(opt *textureOption)

func TextureFilter(min, mag TextureFilterEnum) TextureOption {
	return func(opt *textureOption) {
		opt.filterMin = min
		opt.filterMag = mag
	}
}

type TextureFilterEnum uint32

const (
	NEAREST TextureFilterEnum = gl.NEAREST
	LINEAR  TextureFilterEnum = gl.LINEAR
)

func TextureWrap(wrap_s, wrap_t TextureWrapEnum) TextureOption {
	return func(opt *textureOption) {
		opt.wrap_s = wrap_s
		opt.wrap_t = wrap_t
	}
}

type TextureWrapEnum uint32

const (
	CLAMP_TO_EDGE   TextureWrapEnum = gl.CLAMP_TO_EDGE
	REPEAT          TextureWrapEnum = gl.REPEAT
	MIRRORED_REPEAT TextureWrapEnum = gl.MIRRORED_REPEAT
)
<commit_msg>Remove activetexture<commit_after>package gli

import (
	"errors"
	"fmt"
	"image"
	"image/draw"
	"os"

	"github.com/go-gl/gl/v2.1/gl"
)

type Texture struct {
	id   uint32
	size image.Point
}

func LoadImage(file string) (*image.RGBA, error) {
	imgFile, err := os.Open(file)
	if err != nil {
		return nil, fmt.Errorf("texture %q not found on disk: %v", file, err)
	}
	img, _, err := image.Decode(imgFile)
	if err != nil {
		return nil, err
	}
	return FixImage(img), nil
}

func FixImage(img image.Image) *image.RGBA {
	rgba := image.NewRGBA(img.Bounds())
	draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
	return rgba
}

func (texture *Texture) Id() uint32 {
	return texture.id
}

func (texture *Texture) Use(unit int) {
	gl.ActiveTexture(gl.TEXTURE0 + uint32(unit))
	gl.BindTexture(gl.TEXTURE_2D, texture.id)
}

func (texture *Texture) Size() image.Point {
	return texture.size
}

func (texture *Texture) Delete() {
	gl.DeleteTextures(1, &texture.id)
}

func NewTexture(img *image.RGBA, opts ...TextureOption) (*Texture, error) {
	if img.Stride != img.Rect.Size().X*4 {
		return nil, errors.New("unsupported stride in texture image")
	}
	opt := textureOption{
		filterMin: LINEAR,
		filterMag: LINEAR,
		wrap_s:    REPEAT,
		wrap_t:    REPEAT,
	}
	for _, o := range opts {
		o(&opt)
	}

	var id uint32
	gl.GenTextures(1, &id)
	gl.BindTexture(gl.TEXTURE_2D, id)
	defer gl.BindTexture(gl.TEXTURE_2D, 0)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int32(opt.filterMin))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int32(opt.filterMag))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, int32(opt.wrap_s))
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, int32(opt.wrap_t))
	gl.TexImage2D(
		gl.TEXTURE_2D,
		0,
		gl.RGBA,
		int32(img.Rect.Size().X),
		int32(img.Rect.Size().Y),
		0,
		gl.RGBA,
		gl.UNSIGNED_BYTE,
		gl.Ptr(img.Pix))
	return &Texture{
		id:   id,
		size: img.Rect.Size(),
	}, nil
}

type textureOption struct {
	filterMin TextureFilterEnum
	filterMag TextureFilterEnum
	wrap_s    TextureWrapEnum
	wrap_t    TextureWrapEnum
}

type TextureOption func(opt *textureOption)

func TextureFilter(min, mag TextureFilterEnum) TextureOption {
	return func(opt *textureOption) {
		opt.filterMin = min
		opt.filterMag = mag
	}
}

type TextureFilterEnum uint32

const (
	NEAREST TextureFilterEnum = gl.NEAREST
	LINEAR  TextureFilterEnum = gl.LINEAR
)

func TextureWrap(wrap_s, wrap_t TextureWrapEnum) TextureOption {
	return func(opt *textureOption) {
		opt.wrap_s = wrap_s
		opt.wrap_t = wrap_t
	}
}

type TextureWrapEnum uint32

const (
	CLAMP_TO_EDGE   TextureWrapEnum = gl.CLAMP_TO_EDGE
	REPEAT          TextureWrapEnum = gl.REPEAT
	MIRRORED_REPEAT TextureWrapEnum = gl.MIRRORED_REPEAT
)
<|endoftext|>


<commit_before>package outputprometheus

import (
	"context"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tsaikd/gogstash/config"
	"github.com/tsaikd/gogstash/config/goglog"
	"github.com/tsaikd/gogstash/config/logevent"
)

func init() {
	goglog.Logger.SetLevel(logrus.DebugLevel)
	config.RegistOutputHandler(ModuleName, InitHandler)
}

func Test_output_prometheus_module(t *testing.T) {
	assert := assert.New(t)
	assert.NotNil(assert)
	require := require.New(t)
	require.NotNil(require)

	ctx := context.Background()
	conf, err := config.LoadFromYAML([]byte(strings.TrimSpace(`
debugch: true
output:
  - type: prometheus
    address: "127.0.0.1:8080"
	`)))
	require.NoError(err)
	require.NoError(conf.Start(ctx))

	time.Sleep(1000 * time.Millisecond)

	// sending 1st event
	conf.TestInputEvent(logevent.LogEvent{
		Timestamp: time.Now(),
		Message:   "output prometheus test message",
	})
	value, err := getMetric()
	require.NoError(err)
	require.Equal("processed_messages_total 1.0", value)

	// sending second event
	conf.TestInputEvent(logevent.LogEvent{
		Timestamp: time.Now(),
		Message:   "output prometheus test message",
	})
	time.Sleep(500 * time.Millisecond)
	value, err = getMetric()
	require.NoError(err)
	require.Equal("processed_messages_total 2.0", value)
}

func getMetric() (string, error) {
	resp, err := http.Get("http://127.0.0.1:8080/metrics")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	lines := strings.Split(string(body), "\n")
	return lines[len(lines)-2], nil
}
<commit_msg>Revert "output/prometheus: update test case for latest lib"<commit_after>package outputprometheus

import (
	"context"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tsaikd/gogstash/config"
	"github.com/tsaikd/gogstash/config/goglog"
	"github.com/tsaikd/gogstash/config/logevent"
)

func init() {
	goglog.Logger.SetLevel(logrus.DebugLevel)
	config.RegistOutputHandler(ModuleName, InitHandler)
}

func Test_output_prometheus_module(t *testing.T) {
	assert := assert.New(t)
	assert.NotNil(assert)
	require := require.New(t)
	require.NotNil(require)

	ctx := context.Background()
	conf, err := config.LoadFromYAML([]byte(strings.TrimSpace(`
debugch: true
output:
  - type: prometheus
    address: "127.0.0.1:8080"
	`)))
	require.NoError(err)
	require.NoError(conf.Start(ctx))

	time.Sleep(1000 * time.Millisecond)

	// sending 1st event
	conf.TestInputEvent(logevent.LogEvent{
		Timestamp: time.Now(),
		Message:   "output prometheus test message",
	})
	value, err := getMetric()
	require.NoError(err)
	require.Equal("processed_messages_total 1", value)

	// sending second event
	conf.TestInputEvent(logevent.LogEvent{
		Timestamp: time.Now(),
		Message:   "output prometheus test message",
	})
	time.Sleep(500 * time.Millisecond)
	value, err = getMetric()
	require.NoError(err)
	require.Equal("processed_messages_total 2", value)
}

func getMetric() (string, error) {
	resp, err := http.Get("http://127.0.0.1:8080/metrics")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	lines := strings.Split(string(body), "\n")
	return lines[len(lines)-2], nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path"

	"github.com/mattn/go-gtk/gdk"
	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

var (
	menubar *gtk.Widget
	footer  *gtk.HBox
	findbar *gtk.Entry
	btnReg  *gtk.ToggleButton
)

func CreateMenu(w *gtk.Window, vbox *gtk.VBox) {
	action_group := gtk.NewActionGroup("my_group")
	ui_manager := CreateUIManager()
	accel_group := ui_manager.GetAccelGroup()
	w.AddAccelGroup(accel_group)
	AddFileMenuActions(action_group)
	AddEditMenuActions(action_group)
	AddChoicesMenuActions(action_group)

	ui_manager.InsertActionGroup(action_group, 0)
	menubar = ui_manager.GetWidget("/MenuBar")

	vbox.PackStart(menubar, false, false, 0)

	vbox.PackEnd(CreateFooter(), false, false, 0)
}

func CreateFooter() *gtk.HBox {
	footer = gtk.NewHBox(false, 0)

	btnReg = gtk.NewToggleButton()
	labelReg := gtk.NewLabel("Re")
	btnReg.Add(labelReg)
	labelReg.ModifyFG(gtk.STATE_ACTIVE, gdk.NewColor("red"))
	btnReg.Connect("toggled", OnFindInput)
	footer.PackStart(btnReg, false, true, 1)

	// btnReg.SetBorderWidth(4)

	ebuff := gtk.NewEntryBuffer("")
	findbar = gtk.NewEntryWithBuffer(ebuff)
	findbar.Connect("changed", OnFindInput)
	footer.PackStart(findbar, true, true, 1)

	btnNext := gtk.NewButton()
	btnNext.SetSizeRequest(20, 20)
	btnNext.Add(gtk.NewArrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE))
	btnNext.Clicked(OnFindNext)
	footer.PackStart(btnNext, false, true, 1)

	btnPrev := gtk.NewButton()
	btnPrev.SetSizeRequest(20, 20)
	btnPrev.Add(gtk.NewArrow(gtk.ARROW_UP, gtk.SHADOW_NONE))
	btnPrev.Clicked(OnFindPrev)
	footer.PackStart(btnPrev, false, true, 1)

	btnClose := gtk.NewButton()
	btnClose.SetSizeRequest(20, 20)
	btnClose.Add(gtk.NewImageFromStock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON))
	btnClose.Clicked(OnMenuFind)
	footer.PackStart(btnClose, false, true, 1)

	return footer
}

func OnFindInput() {
	currentTab().Find(findbar.GetText())
}

func OnFindNext() {
	currentTab().FindNext(true)
}

func OnFindPrev() {
	currentTab().FindNext(false)
}

func CreateUIManager() *gtk.UIManager {
	UI_INFO := `
<ui>
  <menubar name='MenuBar'>
    <menu action='FileMenu'>
      <menuitem action='NewTab' />
      <menuitem action='CloseTab' />
      <menuitem action='FileOpen' />
      <menuitem action='FileSave' />
      <menuitem action='FileSaveAs' />
      <separator />
      <menuitem action='FileQuit' />
    </menu>
    <menu action='EditMenu'>
      <menuitem action='Find'/>
    </menu>
    <menu action='ChoicesMenu'>
      <menuitem action='ChoiceOne'/>
      <menuitem action='ChoiceTwo'/>
      <menuitem action='ChoiceThree'/>
      <separator />
      <menuitem action='ChoiceToggle'/>
    </menu>
  </menubar>
</ui>
`
	ui_manager := gtk.NewUIManager()
	ui_manager.AddUIFromString(UI_INFO)
	return ui_manager
}

func OnMenuFileQuit() {
	exit()
}

func OnMenuFileOpen() {
	dialog := gtk.NewFileChooserDialog("open", window, gtk.FILE_CHOOSER_ACTION_OPEN, "open file", gtk.RESPONSE_OK)

	dialog.Run()

	filename := dialog.GetFilename()

	dialog.Destroy()

	if len(filename) > 0 {
		NewTab(filename)
	}
}

func OnMenuFileSave() {
	// n := notebook.GetCurrentPage()
	t := currentTab()
	if t == nil {
		return
	}
	if t.File == nil {
		filename := dialogSave()
		if len(filename) == 0 {
			return
		}

		t.Filename = filename
		t.label.SetText(path.Base(filename))
		t.label.SetTooltipText(filename)
	}
	t.Save()
}

func OnMenuFileSaveAs() {
	t := currentTab()

	filename := dialogSave()
	if len(filename) == 0 {
		return
	}

	t.Filename = filename
	t.label.SetText(path.Base(filename))
	t.label.SetTooltipText(filename)
}

func dialogSave() string {
	dialog := gtk.NewFileChooserDialog("save", window, gtk.FILE_CHOOSER_ACTION_SAVE, "save file", gtk.RESPONSE_OK)
	dialog.Run()
	filename := dialog.GetFilename()
	dialog.Destroy()

	return filename
}

func OnMenuNewTab() {
	NewTab("")
	fmt.Println(len(tabs))
}

func OnMenuCloseTab() {
	closeCurrentTab()

	if len(tabs) == 0 {
		exit()
	}
}

func OnMenuFind() {
	if footer.GetVisible() {
		footer.SetVisible(false)
		currentTab().sourceview.GrabFocus()
	} else {
		footer.SetVisible(true)
		findbar.GrabFocus()
	}
}

func AddFileMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("FileMenu", "File", "", ""))

	action_newtab := gtk.NewAction("NewTab", "New Tab", "", "")
	action_newtab.Connect("activate", OnMenuNewTab)
	action_group.AddActionWithAccel(action_newtab, "<control>t")

	action_closetab := gtk.NewAction("CloseTab", "Close Tab", "", "")
	action_closetab.Connect("activate", OnMenuCloseTab)
	action_group.AddActionWithAccel(action_closetab, "<control>w")

	action_fileopen := gtk.NewAction("FileOpen", "", "", gtk.STOCK_OPEN)
	action_fileopen.Connect("activate", OnMenuFileOpen)
	action_group.AddActionWithAccel(action_fileopen, "")

	action_filesave := gtk.NewAction("FileSave", "", "", gtk.STOCK_SAVE)
	action_filesave.Connect("activate", OnMenuFileSave)
	action_group.AddActionWithAccel(action_filesave, "")

	action_filesaveas := gtk.NewAction("FileSaveAs", "", "", gtk.STOCK_SAVE_AS)
	action_filesaveas.Connect("activate", OnMenuFileSaveAs)
	action_group.AddActionWithAccel(action_filesaveas, "")

	action_filequit := gtk.NewAction("FileQuit", "", "", gtk.STOCK_QUIT)
	action_filequit.Connect("activate", OnMenuFileQuit)
	action_group.AddActionWithAccel(action_filequit, "")
}

func AddEditMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("EditMenu", "Edit", "", ""))

	action_find := gtk.NewAction("Find", "Find...", "", gtk.STOCK_FIND)
	action_find.Connect("activate", OnMenuFind)
	action_group.AddActionWithAccel(action_find, "")
}

func AddChoicesMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("ChoicesMenu", "Choices", "", ""))

	var ra_list []*gtk.RadioAction
	ra_one := gtk.NewRadioAction("ChoiceOne", "One", "", "", 1)
	ra_list = append(ra_list, ra_one)

	ra_two := gtk.NewRadioAction("ChoiceTwo", "Two", "", "", 2)
	ra_list = append(ra_list, ra_two)

	ra_three := gtk.NewRadioAction("ChoiceThree", "Three", "", "", 2)
	ra_list = append(ra_list, ra_three)

	var sl *glib.SList
	for _, ra := range ra_list {
		ra.SetGroup(sl)
		sl = ra.GetGroup()
		action_group.AddAction(ra)
	}

	ra_last := gtk.NewToggleAction("ChoiceToggle", "Toggle", "", "")
	ra_last.SetActive(true)
	action_group.AddAction(ra_last)
}
<commit_msg>added hotkeys for next/prev finded<commit_after>package main

import (
	"fmt"
	"path"

	"github.com/mattn/go-gtk/gdk"
	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

var (
	menubar *gtk.Widget
	footer  *gtk.HBox
	findbar *gtk.Entry
	btnReg  *gtk.ToggleButton
)

func CreateMenu(w *gtk.Window, vbox *gtk.VBox) {
	action_group := gtk.NewActionGroup("my_group")
	ui_manager := CreateUIManager()
	accel_group := ui_manager.GetAccelGroup()
	w.AddAccelGroup(accel_group)
	AddFileMenuActions(action_group)
	AddEditMenuActions(action_group)
	AddChoicesMenuActions(action_group)

	ui_manager.InsertActionGroup(action_group, 0)
	menubar = ui_manager.GetWidget("/MenuBar")

	vbox.PackStart(menubar, false, false, 0)

	vbox.PackEnd(CreateFooter(), false, false, 0)
}

func CreateFooter() *gtk.HBox {
	footer = gtk.NewHBox(false, 0)

	btnReg = gtk.NewToggleButton()
	labelReg := gtk.NewLabel("Re")
	btnReg.Add(labelReg)
	labelReg.ModifyFG(gtk.STATE_ACTIVE, gdk.NewColor("red"))
	btnReg.Connect("toggled", OnFindInput)
	footer.PackStart(btnReg, false, true, 1)

	// btnReg.SetBorderWidth(4)

	ebuff := gtk.NewEntryBuffer("")
	findbar = gtk.NewEntryWithBuffer(ebuff)
	findbar.Connect("changed", OnFindInput)
	footer.PackStart(findbar, true, true, 1)

	btnNext := gtk.NewButton()
	btnNext.SetSizeRequest(20, 20)
	btnNext.Add(gtk.NewArrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE))
	btnNext.Clicked(OnFindNext)
	footer.PackStart(btnNext, false, true, 1)

	btnPrev := gtk.NewButton()
	btnPrev.SetSizeRequest(20, 20)
	btnPrev.Add(gtk.NewArrow(gtk.ARROW_UP, gtk.SHADOW_NONE))
	btnPrev.Clicked(OnFindPrev)
	footer.PackStart(btnPrev, false, true, 1)

	btnClose := gtk.NewButton()
	btnClose.SetSizeRequest(20, 20)
	btnClose.Add(gtk.NewImageFromStock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON))
	btnClose.Clicked(OnMenuFind)
	footer.PackStart(btnClose, false, true, 1)

	return footer
}

func OnFindInput() {
	currentTab().Find(findbar.GetText())
}

func OnFindNext() {
	currentTab().FindNext(true)
}

func OnFindPrev() {
	currentTab().FindNext(false)
}

func CreateUIManager() *gtk.UIManager {
	UI_INFO := `
<ui>
  <menubar name='MenuBar'>
    <menu action='FileMenu'>
      <menuitem action='NewTab' />
      <menuitem action='CloseTab' />
      <menuitem action='FileOpen' />
      <menuitem action='FileSave' />
      <menuitem action='FileSaveAs' />
      <separator />
      <menuitem action='FileQuit' />
    </menu>
    <menu action='EditMenu'>
      <menuitem action='Find'/>
      <menuitem action='FindNext'/>
      <menuitem action='FindPrev'/>
    </menu>
    <menu action='ChoicesMenu'>
      <menuitem action='ChoiceOne'/>
      <menuitem action='ChoiceTwo'/>
      <menuitem action='ChoiceThree'/>
      <separator />
      <menuitem action='ChoiceToggle'/>
    </menu>
  </menubar>
</ui>
`
	ui_manager := gtk.NewUIManager()
	ui_manager.AddUIFromString(UI_INFO)
	return ui_manager
}

func OnMenuFileQuit() {
	exit()
}

func OnMenuFileOpen() {
	dialog := gtk.NewFileChooserDialog("open", window, gtk.FILE_CHOOSER_ACTION_OPEN, "open file", gtk.RESPONSE_OK)

	dialog.Run()

	filename := dialog.GetFilename()

	dialog.Destroy()

	if len(filename) > 0 {
		NewTab(filename)
	}
}

func OnMenuFileSave() {
	// n := notebook.GetCurrentPage()
	t := currentTab()
	if t == nil {
		return
	}
	if t.File == nil {
		filename := dialogSave()
		if len(filename) == 0 {
			return
		}

		t.Filename = filename
		t.label.SetText(path.Base(filename))
		t.label.SetTooltipText(filename)
	}
	t.Save()
}

func OnMenuFileSaveAs() {
	t := currentTab()

	filename := dialogSave()
	if len(filename) == 0 {
		return
	}

	t.Filename = filename
	t.label.SetText(path.Base(filename))
	t.label.SetTooltipText(filename)
}

func dialogSave() string {
	dialog := gtk.NewFileChooserDialog("save", window, gtk.FILE_CHOOSER_ACTION_SAVE, "save file", gtk.RESPONSE_OK)
	dialog.Run()
	filename := dialog.GetFilename()
	dialog.Destroy()

	return filename
}

func OnMenuNewTab() {
	NewTab("")
	fmt.Println(len(tabs))
}

func OnMenuCloseTab() {
	closeCurrentTab()

	if len(tabs) == 0 {
		exit()
	}
}

func OnMenuFind() {
	if footer.GetVisible() {
		footer.SetVisible(false)
		currentTab().sourceview.GrabFocus()
	} else {
		footer.SetVisible(true)
		findbar.GrabFocus()
	}
}

func AddFileMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("FileMenu", "File", "", ""))

	action_newtab := gtk.NewAction("NewTab", "New Tab", "", "")
	action_newtab.Connect("activate", OnMenuNewTab)
	action_group.AddActionWithAccel(action_newtab, "<control>t")

	action_closetab := gtk.NewAction("CloseTab", "Close Tab", "", "")
	action_closetab.Connect("activate", OnMenuCloseTab)
	action_group.AddActionWithAccel(action_closetab, "<control>w")

	action_fileopen := gtk.NewAction("FileOpen", "", "", gtk.STOCK_OPEN)
	action_fileopen.Connect("activate", OnMenuFileOpen)
	action_group.AddActionWithAccel(action_fileopen, "")

	action_filesave := gtk.NewAction("FileSave", "", "", gtk.STOCK_SAVE)
	action_filesave.Connect("activate", OnMenuFileSave)
	action_group.AddActionWithAccel(action_filesave, "")

	action_filesaveas := gtk.NewAction("FileSaveAs", "", "", gtk.STOCK_SAVE_AS)
	action_filesaveas.Connect("activate", OnMenuFileSaveAs)
	action_group.AddActionWithAccel(action_filesaveas, "")

	action_filequit := gtk.NewAction("FileQuit", "", "", gtk.STOCK_QUIT)
	action_filequit.Connect("activate", OnMenuFileQuit)
	action_group.AddActionWithAccel(action_filequit, "")
}

func AddEditMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("EditMenu", "Edit", "", ""))

	action_find := gtk.NewAction("Find", "Find...", "", gtk.STOCK_FIND)
	action_find.Connect("activate", OnMenuFind)
	action_group.AddActionWithAccel(action_find, "")

	action_findnext := gtk.NewAction("FindNext", "Find Next", "", "")
	action_findnext.Connect("activate", OnFindNext)
	action_group.AddActionWithAccel(action_findnext, "F3")

	action_findprev := gtk.NewAction("FindPrev", "Find Previus", "", "")
	action_findprev.Connect("activate", OnFindPrev)
	action_group.AddActionWithAccel(action_findprev, "<shift>F3")
}

func AddChoicesMenuActions(action_group *gtk.ActionGroup) {
	action_group.AddAction(gtk.NewAction("ChoicesMenu", "Choices", "", ""))

	var ra_list []*gtk.RadioAction
	ra_one := gtk.NewRadioAction("ChoiceOne", "One", "", "", 1)
	ra_list = append(ra_list, ra_one)

	ra_two := gtk.NewRadioAction("ChoiceTwo", "Two", "", "", 2)
	ra_list = append(ra_list, ra_two)

	ra_three := gtk.NewRadioAction("ChoiceThree", "Three", "", "", 2)
	ra_list = append(ra_list, ra_three)

	var sl *glib.SList
	for _, ra := range ra_list {
		ra.SetGroup(sl)
		sl = ra.GetGroup()
		action_group.AddAction(ra)
	}

	ra_last := gtk.NewToggleAction("ChoiceToggle", "Toggle", "", "")
	ra_last.SetActive(true)
	action_group.AddAction(ra_last)
}
<|endoftext|>


<commit_before>package goebi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"

	"github.com/kyokomi/goebi/goebi/notice"
)

// TODO: とりあえずgobrake参考
var defaultHTTPClient = &http.Client{
	Transport: &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: func(network, addr string) (net.Conn, error) {
			return net.DialTimeout(network, addr, 3*time.Second)
		},
		ResponseHeaderTimeout: 5 * time.Second,
	},
	Timeout: 10 * time.Second,
}

// Client is an errbit client.
type Client struct {
	client    *http.Client
	noticeURL string
	options   Options
}

// New errbitのClientを生成します
func New(opt Options) *Client {

	c := Client{}
	c.client = defaultHTTPClient
	c.noticeURL = opt.createNoticeBaseURL()
	c.options = opt

	return &c
}

// SendNotice エラー通知します
func (c Client) SendNotice(n *notice.Notice) error {

	data, err := json.Marshal(n)
	if err != nil {
		return err
	}

	u := c.options.createNoticeBaseURL()

	res, err := c.client.Post(u, "application/json", bytes.NewReader(data))
	if err != nil {
		return err
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		data, _ := ioutil.ReadAll(res.Body)

		return fmt.Errorf("error response code %d %s", res.StatusCode, string(data))
	}

	return nil
}
<commit_msg>引数ポインタをやめる<commit_after>package goebi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"

	"github.com/kyokomi/goebi/goebi/notice"
)

// TODO: とりあえずgobrake参考
var defaultHTTPClient = &http.Client{
	Transport: &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: func(network, addr string) (net.Conn, error) {
			return net.DialTimeout(network, addr, 3*time.Second)
		},
		ResponseHeaderTimeout: 5 * time.Second,
	},
	Timeout: 10 * time.Second,
}

// Client is an errbit client.
type Client struct {
	client    *http.Client
	noticeURL string
	options   Options
}

// New errbitのClientを生成します
func New(opt Options) *Client {

	c := Client{}
	c.client = defaultHTTPClient
	c.noticeURL = opt.createNoticeBaseURL()
	c.options = opt

	return &c
}

// SendNotice エラー通知します
func (c Client) SendNotice(n notice.Notice) error {

	data, err := json.Marshal(n)
	if err != nil {
		return err
	}

	u := c.options.createNoticeBaseURL()

	res, err := c.client.Post(u, "application/json", bytes.NewReader(data))
	if err != nil {
		return err
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		data, _ := ioutil.ReadAll(res.Body)

		return fmt.Errorf("error response code %d %s", res.StatusCode, string(data))
	}

	return nil
}
<|endoftext|>


<commit_before>package googlespeak

import (
	"errors"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"unicode/utf8"
)

var langs = []string{"af", "ar", "ca", "cs", "cy", "da", "de", "el", "en",
	"es", "fi", "fr", "hi", "hr", "ht", "hu", "hy", "id", "is", "it",
	"ja", "ko", "la", "lv", "mk", "nl", "no", "pl", "pt", "ro", "ru",
	"sk", "sq", "sr", "sv", "sw", "ta", "tr", "vi", "zh"}

func Say(s string, args ...string) error {
	lang := "en"
	if len(args) > 0 {
		lang = args[0]
	}

	err := validateParams(s, lang)
	if err != nil {
		return err
	}

	log.Printf("Lang: %s, say: %s", lang, s)
	err = speak(s, lang)
	return err
}

func validateParams(s, lang string) error {
	if utf8.RuneCountInString(s) > 100 {
		return errors.New("Text exceeds max char limit (100)!")
	}

	if !isValidLang(lang) {
		return errors.New("Invalid language code!")
	}
	return nil
}

func isValidLang(s string) bool {
	for _, l := range langs {
		if l == s {
			return true
		}
	}
	log.Printf("Invalid language: %s", s)
	return false
}

func getAudio(s, lang string) (io.ReadCloser, error) {
	resp, err := http.Get("http://translate.google.com/translate_tts" +
		"?ie=UTF-8&tl=" + lang + "&q=" + url.QueryEscape(s))
	if err != nil {
		return nil, err
	}

	return resp.Body, nil
}

func play(audio io.Reader) error {
	mplayer := exec.Command("mplayer", "-cache", "8092", "-")
	mplayer.Stdin = audio
	return mplayer.Run()
}

func getFromCache(s, lang string) (io.ReadCloser, error) {
	cached, err := os.Open(getCacheDir() + "/" + lang + "/" + s + ".mp3")
	return cached, err
}

func cacheAudio(stream io.Reader, s, lang string) (io.ReadCloser, error) {
	langCacheDir := getCacheDir() + "/" + lang
	dir, err := os.Open(langCacheDir)
	if os.IsNotExist(err) {
		_ = os.MkdirAll(langCacheDir, 0700)
	}
	defer dir.Close()

	filename := s + ".mp3"

	f, err := os.Open(langCacheDir + "/" + filename)
	if os.IsNotExist(err) {
		f, err = os.Create(langCacheDir + "/" + filename)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(f, stream)
		return f, err
	}
	return f, err
}

func speak(s, lang string) error {
	log.Printf("Get from cache %s/%s", lang, s)
	audio, err := getFromCache(s, lang)
	if err != nil {
		log.Printf("Cache for %s/%s not found. Trying to get audio from Google", lang, s)
		stream, err := getAudio(s, lang)
		if err == nil {
			log.Printf("Caching stream for %s/%s", lang, s)
			audio, _ = cacheAudio(stream, s, lang)
		}
	}
	defer audio.Close()
	err = play(audio)
	if err != nil {
		return err
	}
	return nil
}

func getCacheDir() string {
	xdgCacheHome := os.Getenv("XDG_CACHE_HOME")
	if xdgCacheHome == "" {
		user, _ := user.Current()
		home := user.HomeDir
		xdgCacheHome = home + "/.cache/gspeak"
	}

	dir, err := os.Open(xdgCacheHome)
	if os.IsNotExist(err) {
		_ = os.MkdirAll(xdgCacheHome, 0700)
		dir, err = os.Open(xdgCacheHome)
	}
	return dir.Name()
}
<commit_msg>Add support for long texts.<commit_after>package googlespeak

import (
	"errors"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"strings"
	"unicode/utf8"
)

var langs = []string{"af", "ar", "ca", "cs", "cy", "da", "de", "el", "en",
	"es", "fi", "fr", "hi", "hr", "ht", "hu", "hy", "id", "is", "it",
	"ja", "ko", "la", "lv", "mk", "nl", "no", "pl", "pt", "ro", "ru",
	"sk", "sq", "sr", "sv", "sw", "ta", "tr", "vi", "zh"}

func Say(s string, args ...string) error {
	lang := "en"
	if len(args) > 0 {
		lang = args[0]
	}

	if !isValidLang(lang) {
		return errors.New("Invalid language code!")
	}

	log.Printf("Lang: %s, say: %s", lang, s)
	sentenses, err := splitSentenses(s)
	if err != nil {
		log.Print(err)
		return err
	}

	err = speak(sentenses, lang)
	if err != nil {
		log.Print(err)
		return err
	}
	return nil
}

func isValidLang(s string) bool {
	for _, l := range langs {
		if l == s {
			return true
		}
	}
	log.Printf("Invalid language: %s", s)
	return false
}

func splitSentenses(s string) ([]string, error) {
	sentenses := strings.Split(s, ".")
	var result []string
	for _, sentense := range sentenses {
		if utf8.RuneCountInString(sentense) > 100 {
			tokens := strings.Split(sentense, ",")
			for i, token := range tokens {
				tokens[i] = strings.TrimSpace(token)
				if utf8.RuneCountInString(tokens[i]) > 100 {
					return nil, errors.New("Can't split text into short tokens.")
				}
			}
			result = append(result, tokens...)
		} else {
			result = append(result, strings.TrimSpace(sentense))
		}
	}
	return result, nil
}

func getAudio(s, lang string) (io.ReadCloser, error) {
	resp, err := http.Get("http://translate.google.com/translate_tts" +
		"?ie=UTF-8&tl=" + lang + "&q=" + url.QueryEscape(s))
	if err != nil {
		return nil, err
	}

	return resp.Body, nil
}

func play(audio io.Reader) error {
	mplayer := exec.Command("mplayer", "-cache", "8092", "-")
	mplayer.Stdin = audio
	return mplayer.Run()
}

func getFromCache(s, lang string) (io.ReadCloser, error) {
	cached, err := os.Open(getCacheDir() + "/" + lang + "/" + s + ".mp3")
	return cached, err
}

func cacheAudio(stream io.Reader, s, lang string) (io.ReadCloser, error) {
	langCacheDir := getCacheDir() + "/" + lang
	dir, err := os.Open(langCacheDir)
	if os.IsNotExist(err) {
		_ = os.MkdirAll(langCacheDir, 0700)
	}
	defer dir.Close()

	filename := s + ".mp3"

	f, err := os.Open(langCacheDir + "/" + filename)
	if os.IsNotExist(err) {
		f, err = os.Create(langCacheDir + "/" + filename)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(f, stream)
		return f, err
	}
	return f, err
}

func speak(sentences []string, lang string) error {
	var streams []io.ReadCloser
	for _, s := range sentences {
		log.Printf("Get from cache %s/%s", lang, s)
		audio, err := getFromCache(s, lang)
		if err != nil {
			log.Printf("Cache for %s/%s not found. Trying to get audio from Google", lang, s)
			stream, err := getAudio(s, lang)
			if err == nil {
				log.Printf("Caching stream for %s/%s", lang, s)
				audio, _ = cacheAudio(stream, s, lang)
			}
		}
		defer audio.Close()
		streams = append(streams, audio)
	}

	for _, audio := range streams {
		err := play(audio)
		if err != nil {
			return err
		}
	}

	return nil
}

func getCacheDir() string {
	xdgCacheHome := os.Getenv("XDG_CACHE_HOME")
	if xdgCacheHome == "" {
		user, _ := user.Current()
		home := user.HomeDir
		xdgCacheHome = home + "/.cache/gspeak"
	}

	dir, err := os.Open(xdgCacheHome)
	if os.IsNotExist(err) {
		_ = os.MkdirAll(xdgCacheHome, 0700)
		dir, err = os.Open(xdgCacheHome)
	}
	return dir.Name()
}
<|endoftext|>