<commit_before>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"errors"
	"flag"
	"os"

	"github.com/sirupsen/logrus"
	"k8s.io/test-infra/prow/git/v2"
	"k8s.io/test-infra/prow/pjutil/pprof"
	"sigs.k8s.io/controller-runtime/pkg/manager"

	prowapi "k8s.io/test-infra/prow/apis/prowjobs/v1"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/config/secret"
	"k8s.io/test-infra/prow/crier"
	gcsreporter "k8s.io/test-infra/prow/crier/reporters/gcs"
	k8sgcsreporter "k8s.io/test-infra/prow/crier/reporters/gcs/kubernetes"
	gerritreporter "k8s.io/test-infra/prow/crier/reporters/gerrit"
	githubreporter "k8s.io/test-infra/prow/crier/reporters/github"
	pubsubreporter "k8s.io/test-infra/prow/crier/reporters/pubsub"
	slackreporter "k8s.io/test-infra/prow/crier/reporters/slack"
	prowflagutil "k8s.io/test-infra/prow/flagutil"
	configflagutil "k8s.io/test-infra/prow/flagutil/config"
	gerritclient "k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/interrupts"
	"k8s.io/test-infra/prow/io"
	"k8s.io/test-infra/prow/logrusutil"
	"k8s.io/test-infra/prow/metrics"
	slackclient "k8s.io/test-infra/prow/slack"
)

type options struct {
	client           prowflagutil.KubernetesOptions
	cookiefilePath   string
	gerritProjects   gerritclient.ProjectsFlag
	github           prowflagutil.GitHubOptions
	githubEnablement prowflagutil.GitHubEnablementOptions

	config configflagutil.ConfigOptions

	gerritWorkers         int
	pubsubWorkers         int
	githubWorkers         int
	slackWorkers          int
	gcsWorkers            int
	k8sGCSWorkers         int
	blobStorageWorkers    int
	k8sBlobStorageWorkers int

	slackTokenFile            string
	additionalSlackTokenFiles slackclient.HostsFlag

	storage prowflagutil.StorageClientOptions

	instrumentationOptions prowflagutil.InstrumentationOptions

	k8sReportFraction float64

	dryrun      bool
	reportAgent string
}

func (o *options) validate() error {

	// TODO(krzyzacy): gerrit && github report are actually stateful..
	// Need a better design to re-enable parallel reporting
	if o.gerritWorkers > 1 {
		logrus.Warn("gerrit reporter only supports one worker")
		o.gerritWorkers = 1
	}

	if o.gerritWorkers+o.pubsubWorkers+o.githubWorkers+o.slackWorkers+o.gcsWorkers+o.k8sGCSWorkers+o.blobStorageWorkers+o.k8sBlobStorageWorkers <= 0 {
		return errors.New("crier need to have at least one report worker to start")
	}

	if o.k8sReportFraction < 0 || o.k8sReportFraction > 1 {
		return errors.New("--kubernetes-report-fraction must be a float between 0 and 1")
	}

	if o.gerritWorkers > 0 {
		if len(o.gerritProjects) == 0 {
			return errors.New("--gerrit-projects must be set")
		}

		if o.cookiefilePath == "" {
			logrus.Info("--cookiefile is not set, using anonymous authentication")
		}
	}

	if o.githubWorkers > 0 {
		if err := o.github.Validate(o.dryrun); err != nil {
			return err
		}
	}

	if o.slackWorkers > 0 {
		if o.slackTokenFile == "" && len(o.additionalSlackTokenFiles) == 0 {
			return errors.New("one of --slack-token-file or --additional-slack-token-files must be set")
		}
	}

	if o.gcsWorkers > 0 {
		logrus.Warn("--gcs-workers is deprecated and will be removed in August 2020. Use --blob-storage-workers instead.")
		// return an error when the old and new flags are both set
		if o.blobStorageWorkers != 0 {
			return errors.New("only one of --gcs-workers or --blog-storage-workers can be set at the same time")
		}
		// use gcsWorkers if blobStorageWorkers is not set
		o.blobStorageWorkers = o.gcsWorkers
	}
	if o.k8sGCSWorkers > 0 {
		logrus.Warn("--kubernetes-gcs-workers is deprecated and will be removed in August 2020. Use --kubernetes-blob-storage-workers instead.")
		// return an error when the old and new flags are both set
		if o.k8sBlobStorageWorkers != 0 {
			return errors.New("only one of --kubernetes-gcs-workers or --kubernetes-blog-storage-workers can be set at the same time")
		}
		// use k8sGCSWorkers if k8sBlobStorageWorkers is not set
		o.k8sBlobStorageWorkers = o.k8sGCSWorkers
	}

	for _, opt := range []interface{ Validate(bool) error }{&o.client, &o.githubEnablement, &o.config} {
		if err := opt.Validate(o.dryrun); err != nil {
			return err
		}
	}

	return nil
}

func (o *options) parseArgs(fs *flag.FlagSet, args []string) error {

	o.gerritProjects = gerritclient.ProjectsFlag{}

	fs.StringVar(&o.cookiefilePath, "cookiefile", "", "Path to git http.cookiefile, leave empty for anonymous")
	fs.Var(&o.gerritProjects, "gerrit-projects", "Set of gerrit repos to monitor on a host example: --gerrit-host=https://android.googlesource.com=platform/build,toolchain/llvm, repeat flag for each host")
	fs.IntVar(&o.gerritWorkers, "gerrit-workers", 0, "Number of gerrit report workers (0 means disabled)")
	fs.IntVar(&o.pubsubWorkers, "pubsub-workers", 0, "Number of pubsub report workers (0 means disabled)")
	fs.IntVar(&o.githubWorkers, "github-workers", 0, "Number of github report workers (0 means disabled)")
	fs.IntVar(&o.slackWorkers, "slack-workers", 0, "Number of Slack report workers (0 means disabled)")
	fs.Var(&o.additionalSlackTokenFiles, "additional-slack-token-files", "Map of additional slack token files. example: --additional-slack-token-files=foo=/etc/foo-slack-tokens/token, repeat flag for each host")
	fs.IntVar(&o.gcsWorkers, "gcs-workers", 0, "Number of GCS report workers (0 means disabled)")
	fs.IntVar(&o.k8sGCSWorkers, "kubernetes-gcs-workers", 0, "Number of Kubernetes-specific GCS report workers (0 means disabled)")
	fs.IntVar(&o.blobStorageWorkers, "blob-storage-workers", 0, "Number of blob storage report workers (0 means disabled)")
	fs.IntVar(&o.k8sBlobStorageWorkers, "kubernetes-blob-storage-workers", 0, "Number of Kubernetes-specific blob storage report workers (0 means disabled)")
	fs.Float64Var(&o.k8sReportFraction, "kubernetes-report-fraction", 1.0, "Approximate portion of jobs to report pod information for, if kubernetes-gcs-workers are enabled (0 - > none, 1.0 -> all)")
	fs.StringVar(&o.slackTokenFile, "slack-token-file", "", "Path to a Slack token file")
	fs.StringVar(&o.reportAgent, "report-agent", "", "Only report specified agent - empty means report to all agents (effective for github and Slack only)")

	// TODO(krzyzacy): implement dryrun for gerrit/pubsub
	fs.BoolVar(&o.dryrun, "dry-run", false, "Run in dry-run mode, not doing actual report (effective for github and Slack only)")

	o.config.AddFlags(fs)
	o.github.AddFlags(fs)
	o.client.AddFlags(fs)
	o.storage.AddFlags(fs)
	o.instrumentationOptions.AddFlags(fs)
	o.githubEnablement.AddFlags(fs)

	fs.Parse(args)

	return o.validate()
}

func parseOptions() options {
	var o options

	if err := o.parseArgs(flag.CommandLine, os.Args[1:]); err != nil {
		logrus.WithError(err).Fatal("Invalid flag options")
	}

	return o
}

func main() {
	logrusutil.ComponentInit()

	o := parseOptions()

	defer interrupts.WaitForGracefulShutdown()

	pprof.Instrument(o.instrumentationOptions)

	configAgent, err := o.config.ConfigAgent()
	if err != nil {
		logrus.WithError(err).Fatal("Error starting config agent.")
	}
	cfg := configAgent.Config

	restCfg, err := o.client.InfrastructureClusterConfig(o.dryrun)
	if err != nil {
		logrus.WithError(err).Fatal("Failed to get kubeconfig")
	}
	mgr, err := manager.New(restCfg, manager.Options{
		Namespace:          cfg().ProwJobNamespace,
		MetricsBindAddress: "0",
	})
	if err != nil {
		logrus.WithError(err).Fatal("failed to create manager")
	}

	// The watch apimachinery doesn't support restarts, so just exit the binary if a kubeconfig changes
	// to make the kubelet restart us.
	if err := o.client.AddKubeconfigChangeCallback(func() {
		logrus.Info("Kubeconfig changed, exiting to trigger a restart")
		interrupts.Terminate()
	}); err != nil {
		logrus.WithError(err).Fatal("Failed to register kubeconfig change callback")
	}

	var hasReporter bool
	if o.slackWorkers > 0 {
		if cfg().SlackReporterConfigs == nil {
			logrus.Fatal("slackreporter is enabled but has no config")
		}
		slackConfig := func(refs *prowapi.Refs) config.SlackReporter {
			return cfg().SlackReporterConfigs.GetSlackReporter(refs)
		}
		tokensMap := make(map[string]func() []byte)
		if o.slackTokenFile != "" {
			tokensMap[slackreporter.DefaultHostName] = secret.GetTokenGenerator(o.slackTokenFile)
			if err := secret.Add(o.slackTokenFile); err != nil {
				logrus.WithError(err).Fatal("could not read slack token")
			}
		}
		hasReporter = true
		for host, additionalTokenFile := range o.additionalSlackTokenFiles {
			tokensMap[host] = secret.GetTokenGenerator(additionalTokenFile)
			if err := secret.Add(additionalTokenFile); err != nil {
				logrus.WithError(err).Fatal("could not read slack token")
			}
		}
		slackReporter := slackreporter.New(slackConfig, o.dryrun, tokensMap)
		if err := crier.New(mgr, slackReporter, o.slackWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct slack reporter controller")
		}
	}

	if o.gerritWorkers > 0 {
		gerritReporter, err := gerritreporter.NewReporter(o.cookiefilePath, o.gerritProjects, mgr.GetCache())
		if err != nil {
			logrus.WithError(err).Fatal("Error starting gerrit reporter")
		}

		hasReporter = true
		if err := crier.New(mgr, gerritReporter, o.gerritWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct gerrit reporter controller")
		}
	}

	if o.pubsubWorkers > 0 {
		hasReporter = true
		if err := crier.New(mgr, pubsubreporter.NewReporter(cfg), o.pubsubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct pubsub reporter controller")
		}
	}

	if o.githubWorkers > 0 {
		if o.github.TokenPath != "" {
			if err := secret.Add(o.github.TokenPath); err != nil {
				logrus.WithError(err).Fatal("Error reading GitHub credentials")
			}
		}

		githubClient, err := o.github.GitHubClient(o.dryrun)
		if err != nil {
			logrus.WithError(err).Fatal("Error getting GitHub client.")
		}
		g, err := o.github.GitClient(o.dryrun)
		if err != nil {
			logrus.WithError(err).Fatal("Error getting Git client.")
		}
		// This git client is only used for inrepoconfig, updating it to use the cached
		// version of git client for improved performance of inrepoconfig
		gitClient := config.NewInRepoConfigGitCache(git.ClientFactoryFrom(g))

		hasReporter = true
		githubReporter := githubreporter.NewReporter(gitClient, githubClient, cfg, prowapi.ProwJobAgent(o.reportAgent))
		if err := crier.New(mgr, githubReporter, o.githubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct github reporter controller")
		}
	}

	if o.blobStorageWorkers > 0 || o.k8sBlobStorageWorkers > 0 {
		opener, err := io.NewOpener(context.Background(), o.storage.GCSCredentialsFile, o.storage.S3CredentialsFile)
		if err != nil {
			logrus.WithError(err).Fatal("Error creating opener")
		}

		hasReporter = true
		if err := crier.New(mgr, gcsreporter.New(cfg, opener, o.dryrun), o.blobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct gcsreporter controller")
		}

		if o.k8sBlobStorageWorkers > 0 {
			coreClients, err := o.client.BuildClusterCoreV1Clients(o.dryrun)
			if err != nil {
				logrus.WithError(err).Fatal("Error building pod client sets for Kubernetes GCS workers")
			}

			k8sGcsReporter := k8sgcsreporter.New(cfg, opener, coreClients, float32(o.k8sReportFraction), o.dryrun)
			if err := crier.New(mgr, k8sGcsReporter, o.k8sBlobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
				logrus.WithError(err).Fatal("failed to construct k8sgcsreporter controller")
			}
		}
	}

	if !hasReporter {
		logrus.Fatalf("should have at least one controller to start crier.")
	}

	// Push metrics to the configured prometheus pushgateway endpoint or serve them
	metrics.ExposeMetrics("crier", cfg().PushGateway, o.instrumentationOptions.MetricsPort)

	if err := mgr.Start(interrupts.Context()); err != nil {
		logrus.WithError(err).Fatal("controller manager failed")
	}
	logrus.Info("Ended gracefully")
}
<commit_msg>Revert "Crier github reporter uses cached git client for reporting to github"<commit_after>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"errors"
	"flag"
	"os"

	"github.com/sirupsen/logrus"
	"k8s.io/test-infra/prow/git/v2"
	"k8s.io/test-infra/prow/pjutil/pprof"
	"sigs.k8s.io/controller-runtime/pkg/manager"

	prowapi "k8s.io/test-infra/prow/apis/prowjobs/v1"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/config/secret"
	"k8s.io/test-infra/prow/crier"
	gcsreporter "k8s.io/test-infra/prow/crier/reporters/gcs"
	k8sgcsreporter "k8s.io/test-infra/prow/crier/reporters/gcs/kubernetes"
	gerritreporter "k8s.io/test-infra/prow/crier/reporters/gerrit"
	githubreporter "k8s.io/test-infra/prow/crier/reporters/github"
	pubsubreporter "k8s.io/test-infra/prow/crier/reporters/pubsub"
	slackreporter "k8s.io/test-infra/prow/crier/reporters/slack"
	prowflagutil "k8s.io/test-infra/prow/flagutil"
	configflagutil "k8s.io/test-infra/prow/flagutil/config"
	gerritclient "k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/interrupts"
	"k8s.io/test-infra/prow/io"
	"k8s.io/test-infra/prow/logrusutil"
	"k8s.io/test-infra/prow/metrics"
	slackclient "k8s.io/test-infra/prow/slack"
)

type options struct {
	client           prowflagutil.KubernetesOptions
	cookiefilePath   string
	gerritProjects   gerritclient.ProjectsFlag
	github           prowflagutil.GitHubOptions
	githubEnablement prowflagutil.GitHubEnablementOptions

	config configflagutil.ConfigOptions

	gerritWorkers         int
	pubsubWorkers         int
	githubWorkers         int
	slackWorkers          int
	gcsWorkers            int
	k8sGCSWorkers         int
	blobStorageWorkers    int
	k8sBlobStorageWorkers int

	slackTokenFile            string
	additionalSlackTokenFiles slackclient.HostsFlag

	storage prowflagutil.StorageClientOptions

	instrumentationOptions prowflagutil.InstrumentationOptions

	k8sReportFraction float64

	dryrun      bool
	reportAgent string
}

func (o *options) validate() error {

	// TODO(krzyzacy): gerrit && github report are actually stateful..
	// Need a better design to re-enable parallel reporting
	if o.gerritWorkers > 1 {
		logrus.Warn("gerrit reporter only supports one worker")
		o.gerritWorkers = 1
	}

	if o.gerritWorkers+o.pubsubWorkers+o.githubWorkers+o.slackWorkers+o.gcsWorkers+o.k8sGCSWorkers+o.blobStorageWorkers+o.k8sBlobStorageWorkers <= 0 {
		return errors.New("crier need to have at least one report worker to start")
	}

	if o.k8sReportFraction < 0 || o.k8sReportFraction > 1 {
		return errors.New("--kubernetes-report-fraction must be a float between 0 and 1")
	}

	if o.gerritWorkers > 0 {
		if len(o.gerritProjects) == 0 {
			return errors.New("--gerrit-projects must be set")
		}

		if o.cookiefilePath == "" {
			logrus.Info("--cookiefile is not set, using anonymous authentication")
		}
	}

	if o.githubWorkers > 0 {
		if err := o.github.Validate(o.dryrun); err != nil {
			return err
		}
	}

	if o.slackWorkers > 0 {
		if o.slackTokenFile == "" && len(o.additionalSlackTokenFiles) == 0 {
			return errors.New("one of --slack-token-file or --additional-slack-token-files must be set")
		}
	}

	if o.gcsWorkers > 0 {
		logrus.Warn("--gcs-workers is deprecated and will be removed in August 2020. Use --blob-storage-workers instead.")
		// return an error when the old and new flags are both set
		if o.blobStorageWorkers != 0 {
			return errors.New("only one of --gcs-workers or --blog-storage-workers can be set at the same time")
		}
		// use gcsWorkers if blobStorageWorkers is not set
		o.blobStorageWorkers = o.gcsWorkers
	}
	if o.k8sGCSWorkers > 0 {
		logrus.Warn("--kubernetes-gcs-workers is deprecated and will be removed in August 2020. Use --kubernetes-blob-storage-workers instead.")
		// return an error when the old and new flags are both set
		if o.k8sBlobStorageWorkers != 0 {
			return errors.New("only one of --kubernetes-gcs-workers or --kubernetes-blog-storage-workers can be set at the same time")
		}
		// use k8sGCSWorkers if k8sBlobStorageWorkers is not set
		o.k8sBlobStorageWorkers = o.k8sGCSWorkers
	}

	for _, opt := range []interface{ Validate(bool) error }{&o.client, &o.githubEnablement, &o.config} {
		if err := opt.Validate(o.dryrun); err != nil {
			return err
		}
	}

	return nil
}

func (o *options) parseArgs(fs *flag.FlagSet, args []string) error {

	o.gerritProjects = gerritclient.ProjectsFlag{}

	fs.StringVar(&o.cookiefilePath, "cookiefile", "", "Path to git http.cookiefile, leave empty for anonymous")
	fs.Var(&o.gerritProjects, "gerrit-projects", "Set of gerrit repos to monitor on a host example: --gerrit-host=https://android.googlesource.com=platform/build,toolchain/llvm, repeat flag for each host")
	fs.IntVar(&o.gerritWorkers, "gerrit-workers", 0, "Number of gerrit report workers (0 means disabled)")
	fs.IntVar(&o.pubsubWorkers, "pubsub-workers", 0, "Number of pubsub report workers (0 means disabled)")
	fs.IntVar(&o.githubWorkers, "github-workers", 0, "Number of github report workers (0 means disabled)")
	fs.IntVar(&o.slackWorkers, "slack-workers", 0, "Number of Slack report workers (0 means disabled)")
	fs.Var(&o.additionalSlackTokenFiles, "additional-slack-token-files", "Map of additional slack token files. example: --additional-slack-token-files=foo=/etc/foo-slack-tokens/token, repeat flag for each host")
	fs.IntVar(&o.gcsWorkers, "gcs-workers", 0, "Number of GCS report workers (0 means disabled)")
	fs.IntVar(&o.k8sGCSWorkers, "kubernetes-gcs-workers", 0, "Number of Kubernetes-specific GCS report workers (0 means disabled)")
	fs.IntVar(&o.blobStorageWorkers, "blob-storage-workers", 0, "Number of blob storage report workers (0 means disabled)")
	fs.IntVar(&o.k8sBlobStorageWorkers, "kubernetes-blob-storage-workers", 0, "Number of Kubernetes-specific blob storage report workers (0 means disabled)")
	fs.Float64Var(&o.k8sReportFraction, "kubernetes-report-fraction", 1.0, "Approximate portion of jobs to report pod information for, if kubernetes-gcs-workers are enabled (0 - > none, 1.0 -> all)")
	fs.StringVar(&o.slackTokenFile, "slack-token-file", "", "Path to a Slack token file")
	fs.StringVar(&o.reportAgent, "report-agent", "", "Only report specified agent - empty means report to all agents (effective for github and Slack only)")

	// TODO(krzyzacy): implement dryrun for gerrit/pubsub
	fs.BoolVar(&o.dryrun, "dry-run", false, "Run in dry-run mode, not doing actual report (effective for github and Slack only)")

	o.config.AddFlags(fs)
	o.github.AddFlags(fs)
	o.client.AddFlags(fs)
	o.storage.AddFlags(fs)
	o.instrumentationOptions.AddFlags(fs)
	o.githubEnablement.AddFlags(fs)

	fs.Parse(args)

	return o.validate()
}

func parseOptions() options {
	var o options

	if err := o.parseArgs(flag.CommandLine, os.Args[1:]); err != nil {
		logrus.WithError(err).Fatal("Invalid flag options")
	}

	return o
}

func main() {
	logrusutil.ComponentInit()

	o := parseOptions()

	defer interrupts.WaitForGracefulShutdown()

	pprof.Instrument(o.instrumentationOptions)

	configAgent, err := o.config.ConfigAgent()
	if err != nil {
		logrus.WithError(err).Fatal("Error starting config agent.")
	}
	cfg := configAgent.Config

	restCfg, err := o.client.InfrastructureClusterConfig(o.dryrun)
	if err != nil {
		logrus.WithError(err).Fatal("Failed to get kubeconfig")
	}
	mgr, err := manager.New(restCfg, manager.Options{
		Namespace:          cfg().ProwJobNamespace,
		MetricsBindAddress: "0",
	})
	if err != nil {
		logrus.WithError(err).Fatal("failed to create manager")
	}

	// The watch apimachinery doesn't support restarts, so just exit the binary if a kubeconfig changes
	// to make the kubelet restart us.
	if err := o.client.AddKubeconfigChangeCallback(func() {
		logrus.Info("Kubeconfig changed, exiting to trigger a restart")
		interrupts.Terminate()
	}); err != nil {
		logrus.WithError(err).Fatal("Failed to register kubeconfig change callback")
	}

	var hasReporter bool
	if o.slackWorkers > 0 {
		if cfg().SlackReporterConfigs == nil {
			logrus.Fatal("slackreporter is enabled but has no config")
		}
		slackConfig := func(refs *prowapi.Refs) config.SlackReporter {
			return cfg().SlackReporterConfigs.GetSlackReporter(refs)
		}
		tokensMap := make(map[string]func() []byte)
		if o.slackTokenFile != "" {
			tokensMap[slackreporter.DefaultHostName] = secret.GetTokenGenerator(o.slackTokenFile)
			if err := secret.Add(o.slackTokenFile); err != nil {
				logrus.WithError(err).Fatal("could not read slack token")
			}
		}
		hasReporter = true
		for host, additionalTokenFile := range o.additionalSlackTokenFiles {
			tokensMap[host] = secret.GetTokenGenerator(additionalTokenFile)
			if err := secret.Add(additionalTokenFile); err != nil {
				logrus.WithError(err).Fatal("could not read slack token")
			}
		}
		slackReporter := slackreporter.New(slackConfig, o.dryrun, tokensMap)
		if err := crier.New(mgr, slackReporter, o.slackWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct slack reporter controller")
		}
	}

	if o.gerritWorkers > 0 {
		gerritReporter, err := gerritreporter.NewReporter(o.cookiefilePath, o.gerritProjects, mgr.GetCache())
		if err != nil {
			logrus.WithError(err).Fatal("Error starting gerrit reporter")
		}

		hasReporter = true
		if err := crier.New(mgr, gerritReporter, o.gerritWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct gerrit reporter controller")
		}
	}

	if o.pubsubWorkers > 0 {
		hasReporter = true
		if err := crier.New(mgr, pubsubreporter.NewReporter(cfg), o.pubsubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct pubsub reporter controller")
		}
	}

	if o.githubWorkers > 0 {
		if o.github.TokenPath != "" {
			if err := secret.Add(o.github.TokenPath); err != nil {
				logrus.WithError(err).Fatal("Error reading GitHub credentials")
			}
		}

		githubClient, err := o.github.GitHubClient(o.dryrun)
		if err != nil {
			logrus.WithError(err).Fatal("Error getting GitHub client.")
		}
		g, err := o.github.GitClient(o.dryrun)
		if err != nil {
			logrus.WithError(err).Fatal("Error getting Git client.")
		}
		gitClient := git.ClientFactoryFrom(g)

		hasReporter = true
		githubReporter := githubreporter.NewReporter(gitClient, githubClient, cfg, prowapi.ProwJobAgent(o.reportAgent))
		if err := crier.New(mgr, githubReporter, o.githubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct github reporter controller")
		}
	}

	if o.blobStorageWorkers > 0 || o.k8sBlobStorageWorkers > 0 {
		opener, err := io.NewOpener(context.Background(), o.storage.GCSCredentialsFile, o.storage.S3CredentialsFile)
		if err != nil {
			logrus.WithError(err).Fatal("Error creating opener")
		}

		hasReporter = true
		if err := crier.New(mgr, gcsreporter.New(cfg, opener, o.dryrun), o.blobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
			logrus.WithError(err).Fatal("failed to construct gcsreporter controller")
		}

		if o.k8sBlobStorageWorkers > 0 {
			coreClients, err := o.client.BuildClusterCoreV1Clients(o.dryrun)
			if err != nil {
				logrus.WithError(err).Fatal("Error building pod client sets for Kubernetes GCS workers")
			}

			k8sGcsReporter := k8sgcsreporter.New(cfg, opener, coreClients, float32(o.k8sReportFraction), o.dryrun)
			if err := crier.New(mgr, k8sGcsReporter, o.k8sBlobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
				logrus.WithError(err).Fatal("failed to construct k8sgcsreporter controller")
			}
		}
	}

	if !hasReporter {
		logrus.Fatalf("should have at least one controller to start crier.")
	}

	// Push metrics to the configured prometheus pushgateway endpoint or serve them
	metrics.ExposeMetrics("crier", cfg().PushGateway, o.instrumentationOptions.MetricsPort)

	if err := mgr.Start(interrupts.Context()); err != nil {
		logrus.WithError(err).Fatal("controller manager failed")
	}
	logrus.Info("Ended gracefully")
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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.
*/

package securitypolicy

import (
	"fmt"
	"github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
	clientset "github.com/grafeas/kritis/pkg/kritis/client/clientset/versioned"
	"github.com/grafeas/kritis/pkg/kritis/constants"
	"github.com/grafeas/kritis/pkg/kritis/kubectl/plugins/resolve"
	"github.com/grafeas/kritis/pkg/kritis/metadata"
	ca "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"
)

// ImageSecurityPolicies returns all ISP's in the specified namespaces
// Pass in an empty string to get all ISPs in all namespaces
func ImageSecurityPolicies(namespace string) ([]v1beta1.ImageSecurityPolicy, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", "")
	if err != nil {
		return nil, fmt.Errorf("error building config: %v", err)
	}

	client, err := clientset.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("error building clientset: %v", err)
	}
	list, err := client.KritisV1beta1().ImageSecurityPolicies(namespace).List(metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("error listing all image policy requirements: %v", err)
	}
	return list.Items, nil
}

// ValidateImageSecurityPolicy checks if an image satisfies ISP requirements
// It returns a list of vulnerabilites that don't pass
func ValidateImageSecurityPolicy(isp v1beta1.ImageSecurityPolicy, image string, client metadata.MetadataFetcher) ([]SecurityPolicyViolation, error) {
	// First, check if image is whitelisted
	if imageInWhitelist(isp, image) {
		return nil, nil
	}
	var violations []SecurityPolicyViolation
	// Next, check if image in qualified
	if !resolve.FullyQualifiedImage(image) {
		violations = append(violations, SecurityPolicyViolation{
			Violation: UnqualifiedImageViolation,
			Reason:    UnqualifiedImageViolationReason(image),
		})
		return violations, nil
	}
	// Now, check vulnz in the image
	vulnz, err := client.GetVulnerabilities(image)
	if err != nil {
		return nil, err
	}

	for _, v := range vulnz {
		// First, check if the vulnerability is whitelisted
		if cveInWhitelist(isp, v.CVE) {
			continue
		}
		// Check ifFixesNotAvailable
		if isp.Spec.PackageVulernerabilityRequirements.OnlyFixesNotAvailable && !v.HasFixAvailable {
			violations = append(violations, SecurityPolicyViolation{
				Vulnerability: v,
				Violation:     FixesNotAvailableViolation,
				Reason:        FixesNotAvailableViolationReason(image, v),
			})
			continue
		}
		// Next, see if the severity is below or at threshold
		if severityWithinThreshold(isp, v.Severity) {
			continue
		}
		// Else, add to list of CVEs in violation
		violations = append(violations, SecurityPolicyViolation{
			Vulnerability: v,
			Violation:     ExceedsMaxSeverityViolation,
			Reason:        ExceedsMaxSeverityViolationReason(image, v, isp),
		})
	}
	return violations, nil
}

func imageInWhitelist(isp v1beta1.ImageSecurityPolicy, image string) bool {
	for _, i := range isp.Spec.ImageWhitelist {
		if i == image {
			return true
		}
	}
	return false
}

func cveInWhitelist(isp v1beta1.ImageSecurityPolicy, cve string) bool {
	for _, w := range isp.Spec.PackageVulernerabilityRequirements.WhitelistCVEs {
		if w == cve {
			return true
		}
	}
	return false
}

func severityWithinThreshold(isp v1beta1.ImageSecurityPolicy, severity string) bool {
	maxSeverity := isp.Spec.PackageVulernerabilityRequirements.MaximumSeverity
	if maxSeverity == constants.BLOCKALL {
		return false
	}
	return ca.VulnerabilityType_Severity_value[severity] <= ca.VulnerabilityType_Severity_value[maxSeverity]
}
<commit_msg>Hardcode the incluster config to silence an error message.<commit_after>/*
Copyright 2018 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.
*/

package securitypolicy

import (
	"fmt"

	"github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
	clientset "github.com/grafeas/kritis/pkg/kritis/client/clientset/versioned"
	"github.com/grafeas/kritis/pkg/kritis/constants"
	"github.com/grafeas/kritis/pkg/kritis/kubectl/plugins/resolve"
	"github.com/grafeas/kritis/pkg/kritis/metadata"
	ca "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/rest"
)

// ImageSecurityPolicies returns all ISP's in the specified namespaces
// Pass in an empty string to get all ISPs in all namespaces
func ImageSecurityPolicies(namespace string) ([]v1beta1.ImageSecurityPolicy, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, fmt.Errorf("error building config: %v", err)
	}

	client, err := clientset.NewForConfig(config)
	if err != nil {
		return nil, fmt.Errorf("error building clientset: %v", err)
	}
	list, err := client.KritisV1beta1().ImageSecurityPolicies(namespace).List(metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("error listing all image policy requirements: %v", err)
	}
	return list.Items, nil
}

// ValidateImageSecurityPolicy checks if an image satisfies ISP requirements
// It returns a list of vulnerabilites that don't pass
func ValidateImageSecurityPolicy(isp v1beta1.ImageSecurityPolicy, image string, client metadata.MetadataFetcher) ([]SecurityPolicyViolation, error) {
	// First, check if image is whitelisted
	if imageInWhitelist(isp, image) {
		return nil, nil
	}
	var violations []SecurityPolicyViolation
	// Next, check if image in qualified
	if !resolve.FullyQualifiedImage(image) {
		violations = append(violations, SecurityPolicyViolation{
			Violation: UnqualifiedImageViolation,
			Reason:    UnqualifiedImageViolationReason(image),
		})
		return violations, nil
	}
	// Now, check vulnz in the image
	vulnz, err := client.GetVulnerabilities(image)
	if err != nil {
		return nil, err
	}

	for _, v := range vulnz {
		// First, check if the vulnerability is whitelisted
		if cveInWhitelist(isp, v.CVE) {
			continue
		}
		// Check ifFixesNotAvailable
		if isp.Spec.PackageVulernerabilityRequirements.OnlyFixesNotAvailable && !v.HasFixAvailable {
			violations = append(violations, SecurityPolicyViolation{
				Vulnerability: v,
				Violation:     FixesNotAvailableViolation,
				Reason:        FixesNotAvailableViolationReason(image, v),
			})
			continue
		}
		// Next, see if the severity is below or at threshold
		if severityWithinThreshold(isp, v.Severity) {
			continue
		}
		// Else, add to list of CVEs in violation
		violations = append(violations, SecurityPolicyViolation{
			Vulnerability: v,
			Violation:     ExceedsMaxSeverityViolation,
			Reason:        ExceedsMaxSeverityViolationReason(image, v, isp),
		})
	}
	return violations, nil
}

func imageInWhitelist(isp v1beta1.ImageSecurityPolicy, image string) bool {
	for _, i := range isp.Spec.ImageWhitelist {
		if i == image {
			return true
		}
	}
	return false
}

func cveInWhitelist(isp v1beta1.ImageSecurityPolicy, cve string) bool {
	for _, w := range isp.Spec.PackageVulernerabilityRequirements.WhitelistCVEs {
		if w == cve {
			return true
		}
	}
	return false
}

func severityWithinThreshold(isp v1beta1.ImageSecurityPolicy, severity string) bool {
	maxSeverity := isp.Spec.PackageVulernerabilityRequirements.MaximumSeverity
	if maxSeverity == constants.BLOCKALL {
		return false
	}
	return ca.VulnerabilityType_Severity_value[severity] <= ca.VulnerabilityType_Severity_value[maxSeverity]
}
<|endoftext|>


<commit_before>// Package version implements the version command for any microservice.
package version

import (
	"fmt"
	"runtime"

	"github.com/giantswarm/microerror"
	"github.com/giantswarm/versionbundle"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
)

type Config struct {
	Description    string
	GitCommit      string
	Name           string
	Source         string
	Version        string
	VersionBundles []versionbundle.Bundle
}

func New(config Config) (Command, error) {
	if config.Description == "" {
		return nil, microerror.Maskf(invalidConfigError, "description commit must not be empty")
	}
	if config.GitCommit == "" {
		return nil, microerror.Maskf(invalidConfigError, "git commit must not be empty")
	}
	if config.Name == "" {
		return nil, microerror.Maskf(invalidConfigError, "name must not be empty")
	}
	if config.Source == "" {
		return nil, microerror.Maskf(invalidConfigError, "source must not be empty")
	}
	if config.Version == "" {
		return nil, microerror.Maskf(invalidConfigError, "%T.Version must not be empty", config)
	}

	newCommand := &command{
		cobraCommand: nil,

		Description:    config.Description,
		GitCommit:      config.GitCommit,
		Name:           config.Name,
		Source:         config.Source,
		GoVersion:      runtime.Version(),
		OS:             runtime.GOOS,
		Arch:           runtime.GOARCH,
		Version:        config.Version,
		VersionBundles: config.VersionBundles,
	}

	newCommand.cobraCommand = &cobra.Command{
		Use:   "version",
		Short: "Show version information of the microservice.",
		Long:  "Show version information of the microservice.",
		Run:   newCommand.Execute,
	}

	return newCommand, nil
}

type command struct {
	cobraCommand *cobra.Command

	Description    string                 `json:"description" yaml:"description"`
	GitCommit      string                 `json:"gitCommit" yaml:"gitCommit"`
	Name           string                 `json:"name" yaml:"name"`
	Source         string                 `json:"source" yaml:"source"`
	GoVersion      string                 `json:"goVersion" yaml:"goVersion"`
	OS             string                 `json:"os" yaml:"os"`
	Arch           string                 `json:"arch" yaml:"arch"`
	Version        string                 `json:"version" yaml:"version"`
	VersionBundles []versionbundle.Bundle `json:"versionBundles" yaml:"versionBundles"`
}

func (c *command) CobraCommand() *cobra.Command {
	return c.cobraCommand
}

func (c *command) Execute(cmd *cobra.Command, args []string) {
	d, err := yaml.Marshal(c)
	if err != nil {
		fmt.Printf("Could not format version data: #%v", err)
	}

	fmt.Printf("%s", d)
}
<commit_msg>Revert "add version"<commit_after>// Package version implements the version command for any microservice.
package version

import (
	"fmt"
	"runtime"

	"github.com/giantswarm/microerror"
	"github.com/giantswarm/versionbundle"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
)

type Config struct {
	Description    string
	GitCommit      string
	Name           string
	Source         string
	VersionBundles []versionbundle.Bundle
}

func New(config Config) (Command, error) {
	if config.Description == "" {
		return nil, microerror.Maskf(invalidConfigError, "description commit must not be empty")
	}
	if config.GitCommit == "" {
		return nil, microerror.Maskf(invalidConfigError, "git commit must not be empty")
	}
	if config.Name == "" {
		return nil, microerror.Maskf(invalidConfigError, "name must not be empty")
	}
	if config.Source == "" {
		return nil, microerror.Maskf(invalidConfigError, "source must not be empty")
	}

	newCommand := &command{
		cobraCommand: nil,

		Description:    config.Description,
		GitCommit:      config.GitCommit,
		Name:           config.Name,
		Source:         config.Source,
		GoVersion:      runtime.Version(),
		OS:             runtime.GOOS,
		Arch:           runtime.GOARCH,
		VersionBundles: config.VersionBundles,
	}

	newCommand.cobraCommand = &cobra.Command{
		Use:   "version",
		Short: "Show version information of the microservice.",
		Long:  "Show version information of the microservice.",
		Run:   newCommand.Execute,
	}

	return newCommand, nil
}

type command struct {
	cobraCommand *cobra.Command

	Description    string                 `json:"description" yaml:"description"`
	GitCommit      string                 `json:"gitCommit" yaml:"gitCommit"`
	Name           string                 `json:"name" yaml:"name"`
	Source         string                 `json:"source" yaml:"source"`
	GoVersion      string                 `json:"goVersion" yaml:"goVersion"`
	OS             string                 `json:"os" yaml:"os"`
	Arch           string                 `json:"arch" yaml:"arch"`
	VersionBundles []versionbundle.Bundle `json:"versionBundles" yaml:"versionBundles"`
}

func (c *command) CobraCommand() *cobra.Command {
	return c.cobraCommand
}

func (c *command) Execute(cmd *cobra.Command, args []string) {
	d, err := yaml.Marshal(c)
	if err != nil {
		fmt.Printf("Could not format version data: #%v", err)
	}

	fmt.Printf("%s", d)
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"github.com/github/git-media/git"
	"github.com/github/git-media/gitmedia"
	"github.com/github/git-media/scanner"
	"github.com/spf13/cobra"
)

var (
	statusCmd = &cobra.Command{
		Use:   "status",
		Short: "Show information about git media files that would be pushed",
		Run:   statusCommand,
	}
	porcelain = false
)

func statusCommand(cmd *cobra.Command, args []string) {
	ref, err := gitmedia.CurrentRef()
	if err != nil {
		Panic(err, "Could not calculate status")
	}

	remoteRef, err := gitmedia.CurrentRemoteRef()
	if err != nil {
		Panic(err, "Could not calculate status")
	}

	pointers, err := scanner.Scan(ref, "^"+remoteRef)
	if err != nil {
		Panic(err, "Could not scan for git media files")
	}

	stagedPointers, err := scanner.ScanIndex()
	if err != nil {
		Panic(err, "Could not scan staging for git media files")
	}

	if porcelain {
		for _, p := range stagedPointers {
			switch p.Status {
			case "R", "C":
				Print("%s  %s -> %s %d", p.Status, p.SrcName, p.Name, p.Size)
			case "M":
				Print(" %s %s %d", p.Status, p.Name, p.Size)
			default:
				Print("%s  %s %d", p.Status, p.Name, p.Size)
			}
		}
		return
	}

	branch, err := git.CurrentBranch()
	if err != nil {
		Panic(err, "Could not get current branch")
	}
	Print("On branch %s", branch)

	remote, err := git.CurrentRemote()
	if err != nil {
		Panic(err, "Could not get current remote branch")
	}

	Print("Media file changes to be pushed to %s:\n", remote)
	for _, p := range pointers {
		Print("\t%s (%s)", p.Name, humanizeBytes(p.Size))
	}

	Print("\nMedia file changes to be committed:\n")
	for _, p := range stagedPointers {
		switch p.Status {
		case "R", "C":
			Print("\t%s -> %s (%s)", p.SrcName, p.Name, humanizeBytes(p.Size))
		case "M":
		default:
			Print("\t%s (%s)", p.Name, humanizeBytes(p.Size))
		}
	}

	Print("\nMedia file changes not staged for commit:\n")
	for _, p := range stagedPointers {
		if p.Status == "M" {
			Print("\t%s", p.Name)
		}
	}

	Print("")
}

func humanizeBytes(bytes int64) string {
	units := []string{"B", "KB", "MB", "GB", "TB"}
	size := float64(bytes)

	if bytes < 1024 {
		return fmt.Sprintf("%d B", bytes)
	}

	for _, unit := range units {
		if size < 1024.0 {
			return fmt.Sprintf("%3.1f %s", size, unit)
		}
		size /= 1024.0
	}
	return fmt.Sprintf("%d B", bytes)
}

func init() {
	statusCmd.Flags().BoolVarP(&porcelain, "porcelain", "p", false, "Give the output in an easy-to-parse format for scripts.")
	RootCmd.AddCommand(statusCmd)
}
<commit_msg>Clean up humanizeBytes a bit<commit_after>package commands

import (
	"fmt"
	"github.com/github/git-media/git"
	"github.com/github/git-media/gitmedia"
	"github.com/github/git-media/scanner"
	"github.com/spf13/cobra"
)

var (
	statusCmd = &cobra.Command{
		Use:   "status",
		Short: "Show information about git media files that would be pushed",
		Run:   statusCommand,
	}
	porcelain = false
)

func statusCommand(cmd *cobra.Command, args []string) {
	ref, err := gitmedia.CurrentRef()
	if err != nil {
		Panic(err, "Could not calculate status")
	}

	remoteRef, err := gitmedia.CurrentRemoteRef()
	if err != nil {
		Panic(err, "Could not calculate status")
	}

	pointers, err := scanner.Scan(ref, "^"+remoteRef)
	if err != nil {
		Panic(err, "Could not scan for git media files")
	}

	stagedPointers, err := scanner.ScanIndex()
	if err != nil {
		Panic(err, "Could not scan staging for git media files")
	}

	if porcelain {
		for _, p := range stagedPointers {
			switch p.Status {
			case "R", "C":
				Print("%s  %s -> %s %d", p.Status, p.SrcName, p.Name, p.Size)
			case "M":
				Print(" %s %s %d", p.Status, p.Name, p.Size)
			default:
				Print("%s  %s %d", p.Status, p.Name, p.Size)
			}
		}
		return
	}

	branch, err := git.CurrentBranch()
	if err != nil {
		Panic(err, "Could not get current branch")
	}
	Print("On branch %s", branch)

	remote, err := git.CurrentRemote()
	if err != nil {
		Panic(err, "Could not get current remote branch")
	}

	Print("Media file changes to be pushed to %s:\n", remote)
	for _, p := range pointers {
		Print("\t%s (%s)", p.Name, humanizeBytes(p.Size))
	}

	Print("\nMedia file changes to be committed:\n")
	for _, p := range stagedPointers {
		switch p.Status {
		case "R", "C":
			Print("\t%s -> %s (%s)", p.SrcName, p.Name, humanizeBytes(p.Size))
		case "M":
		default:
			Print("\t%s (%s)", p.Name, humanizeBytes(p.Size))
		}
	}

	Print("\nMedia file changes not staged for commit:\n")
	for _, p := range stagedPointers {
		if p.Status == "M" {
			Print("\t%s", p.Name)
		}
	}

	Print("")
}

var byteUnits = []string{"B", "KB", "MB", "GB", "TB"}

func humanizeBytes(bytes int64) string {
	var output string
	size := float64(bytes)

	if bytes < 1024 {
		return fmt.Sprintf("%d B", bytes)
	}

	for _, unit := range byteUnits {
		if size < 1024.0 {
			output = fmt.Sprintf("%3.1f %s", size, unit)
			break
		}
		size /= 1024.0
	}
	return output
}

func init() {
	statusCmd.Flags().BoolVarP(&porcelain, "porcelain", "p", false, "Give the output in an easy-to-parse format for scripts.")
	RootCmd.AddCommand(statusCmd)
}
<|endoftext|>


<commit_before>package status

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Process takes the raw output of `git status --porcelain -b -z` and turns it
// into a structured data type.
func Process(gitStatusOutput []byte, root string) *StatusList {
	// initialize a statuslist to hold the results
	results := NewStatusList()

	// put the output into a bufferreader+scanner so we can consume it iteratively
	scanner := bufio.NewScanner(bytes.NewReader(gitStatusOutput))

	// the scanner needs a custom split function for splitting on NUL
	scanNul := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i, b := range data {
			if b == '\x00' {
				return i + 1, data[:i], nil
			}
		}
		return 0, nil, nil
	}
	scanner.Split(scanNul)

	// branch output is first line
	if !scanner.Scan() {
		log.Println("Failed to read buffer when expecting branch status")
		log.Fatal(scanner.Err())
	}
	branchBytes := scanner.Bytes()
	results.branch = ExtractBranch(branchBytes)

	// give ProcessChanges the scanner and let it handle the rest
	// (it does complicated stuff so it needs the entire scanner)
	for _, r := range ProcessChanges(scanner, root) {
		results.groups[r.group].items = append(results.groups[r.group].items, r)
	}

	return results
}

// ExtractBranch handles parsing the branch status from `status --porcelain -b`.
//
// Examples of stuff we will want to parse:
//
// 		## Initial commit on master
// 		## master
// 		## master...origin/master
// 		## master...origin/master [ahead 1]
//
func ExtractBranch(bs []byte) *BranchInfo {
	b := BranchInfo{}

	b.name = decodeBranchName(bs)
	b.ahead, b.behind = decodeBranchPosition(bs)

	return &b
}

func decodeBranchName(bs []byte) (branch string) {
	branchRegex := regexp.MustCompile(`^## (?:Initial commit on )?(\S+?)(?:\.{3}|$)`)
	headRegex := regexp.MustCompile(`^## (HEAD \(no branch\))`)

	branchMatch := branchRegex.FindSubmatch(bs)
	if branchMatch != nil {
		branch = string(branchMatch[1])
	}

	headMatch := headRegex.FindSubmatch(bs)
	if headMatch != nil {
		branch = string(headMatch[1])
	}

	if headMatch == nil && branchMatch == nil {
		log.Fatalf("Failed to parse branch name for output: [%s]", bs)
	}

	return
}

func decodeBranchPosition(bs []byte) (ahead, behind int) {
	reA := regexp.MustCompile(`\[ahead ?(\d+).*\]`)
	reB := regexp.MustCompile(`\[.*behind ?(\d+)\]`)

	mA := reA.FindSubmatch(bs)
	if mA != nil {
		ahead, _ = strconv.Atoi(string(mA[1]))
	}

	mB := reB.FindSubmatch(bs)
	if mB != nil {
		behind, _ = strconv.Atoi(string(mB[1]))
	}

	return
}

/*
ProcessChanges takes `git status -z` output and returns all status items.

(Note: in our case, we actually use `git status -bz` and remove branch header
when we process it earlier, but the results are binary identical.)

This is a complicated process because the format is weird. Each line is a
variable length number of columns (2-3), but the separator for 1-2 is a space
(but the content of columns can contain spaces too!), and the separator for 2-3
is a NUL character (ASCII 0), *if* there is a third column. But here's where it
gets wacky: NUL is also the entry terminator (rather than a LF like in normal
porcelain mode)

Thankfully(?), column 1 which contains the status codes is a fixed length of two
bytes, and in theory the status codes contain enough secrets for us to determine
whether we should expect 2 or 3 columns (current hypothesis is we only get the
third column which is PATH2 when there is a "rename" operation). Sooo... we can
just read those two bytes and use that to determine how many NULs to scan to
until we have consumed a full entry.

We put up with this because it means no shell escaping, which should mean better
cross-platform support. Better hope some Windows people end up using it someday!
*/
func ProcessChanges(s *bufio.Scanner, root string) (results []*StatusItem) {

	// Before we process any changes, get the Current Working Directory.
	// We're going to need use to calculate absolute and relative filepaths for
	// every change, so we get it once now and pass it along.
	// If for some reason this fails (?), fallback to the git worktree root.
	wd, err := os.Getwd()
	if err != nil {
		wd = root
	}

	for s.Scan() {
		chunk := s.Bytes()
		// ...if chunk represents a rename or copy op, need to append another chunk
		// to get the full change item, with NUL manually reinserted because scanner
		// will extract past it.
		if (chunk[0] == 'R' || chunk[0] == 'C') && s.Scan() {
			chunk = append(chunk, '\x00')
			chunk = append(chunk, s.Bytes()...)
		}
		results = append(results, processChange(chunk, wd, root)...)
	}

	return
}

// process change for a single item from a `git status -z`.
//
// Takes raw bytes representing status change from `git status --porcelain -z`,
// assumes that it has already been properly split away from the rest of the
// changes.
//
// See ProcessChanges (plural) for more details on that process.
//
// Note some change items can have multiple statuses, so this returns a slice.
func processChange(chunk []byte, wd, root string) (results []*StatusItem) {

	absolutePath, relativePath := extractFile(chunk, root, wd)

	for _, c := range extractChangeCodes(chunk) {
		result := &StatusItem{
			msg:         c.msg,
			col:         c.col,
			group:       c.group,
			fileAbsPath: absolutePath,
			fileRelPath: relativePath,
		}
		results = append(results, result)
	}

	if len(results) < 1 {
		log.Fatalf(`
Failed to decode git status change code for chunk: [%s]
Please file a bug including this error message as well as the output of:

git status --porcelain

You can file the bug at: https://github.com/mroth/scmpuff/issues/
		`, chunk)
	}
	return results
}

/*
extractFile extracts the filename from a status change, and determines the
absolute and display paths.

 - root: the absolute path to the git working tree
 - wd: current working directory path
*/
func extractFile(chunk []byte, root, wd string) (absPath, relPath string) {
	// file identifier starts at pos4 and continues to EOL
	filePortion := chunk[3:]
	files := bytes.SplitN(filePortion, []byte{'\x00'}, 2)

	n := len(files)
	switch {
	case n < 1:
		log.Fatalf("tried to process a change chunk with no file")
	case n > 1:
		toFile, fromFile := files[0], files[1]
		var toRelPath, fromRelPath string

		absPath, toRelPath = calcPaths(toFile, root, wd)
		_, fromRelPath = calcPaths(fromFile, root, wd)

		relPath = fmt.Sprintf("%s -> %s", fromRelPath, toRelPath)
	default:
		absPath, relPath = calcPaths(files[0], root, wd)
	}

	return
}

// given path of a file relative to git root, git root, and working directory,
// calculate the absolute path of the file on the system, and attempt to figure
// out its relative path to $CWD (if can't, fallback to absolute for both).
func calcPaths(rootPath []byte, root, wd string) (absPath, relPath string) {
	file := rootPath
	absPath = filepath.Join(root, string(file))
	relPath, err := filepath.Rel(wd, absPath)
	if err != nil {
		relPath = absPath
	}
	return
}

// basically a StatusItem minus the file information, for now just being
// used to get results from the change code processing...
// This could probably be encapsulated in StatusItem itself, but wary of adding
// more nesting...
// TODO: should either figure out a way to get rid of this or formalize it more.
type change struct {
	msg   string
	col   ColorGroup
	group StatusGroup
}

/*
Extracts a git status "short code" into the proper UI "change" items we will
display in our status output.

Below documentation from git status:

   Ignored files are not listed, unless --ignored option is in effect, in
   which case XY are !!.

   X          Y     Meaning
   -------------------------------------------------
             [MD]   not updated
   M        [ MD]   updated in index
   A        [ MD]   added to index
   D         [ M]   deleted from index
   R        [ MD]   renamed in index
   C        [ MD]   copied in index
   [MARC]           index and work tree matches
   [ MARC]     M    work tree changed since index
   [ MARC]     D    deleted in work tree
   -------------------------------------------------
   D           D    unmerged, both deleted
   A           U    unmerged, added by us
   U           D    unmerged, deleted by them
   U           A    unmerged, added by them
   D           U    unmerged, deleted by us
   A           A    unmerged, both added
   U           U    unmerged, both modified
   -------------------------------------------------
   ?           ?    untracked
   !           !    ignored
   -------------------------------------------------
*/
func extractChangeCodes(chunk []byte) []*change {
	x := rune(chunk[0])
	y := rune(chunk[1])

	var changes []*change
	if p := decodePrimaryChangeCode(x, y); p != nil {
		changes = append(changes, p)
	}
	if s := decodeSecondaryChangeCode(x, y); s != nil {
		changes = append(changes, s)
	}
	return changes
}

func decodePrimaryChangeCode(x, y rune) *change {
	xy := string(x) + string(y)

	// unmerged cases are simple, only a single change UI is possible
	//
	// TODO: should we handle !! as well? need to determine if possible for user
	// to enable it via their gitconfig where it would affect us.  we coud also
	// allow it to be activated via CLI switch on our end too if desired.
	switch xy {
	case "DD":
		return &change{"   both deleted", del, Unmerged}
	case "AU":
		return &change{"    added by us", neu, Unmerged}
	case "UD":
		return &change{"deleted by them", del, Unmerged}
	case "UA":
		return &change{"  added by them", neu, Unmerged}
	case "DU":
		return &change{"  deleted by us", del, Unmerged}
	case "AA":
		return &change{"     both added", neu, Unmerged}
	case "UU":
		return &change{"  both modified", mod, Unmerged}
	case "??":
		return &change{" untracked", unt, Untracked}
	}

	// staged changes are all single X cases
	// right now we dont need to check the Y, because we consider a modifying Y to
	// these to be a compound case that adds a secondary change in the UI, so that
	// is currently handled in decodeSecondaryChangeCode()
	switch x {
	case 'M':
		return &change{"  modified", mod, Staged}
	case 'A':
		return &change{"  new file", neu, Staged}
	case 'D':
		return &change{"   deleted", del, Staged}
	case 'R':
		return &change{"   renamed", ren, Staged}
	case 'C':
		return &change{"    copied", cpy, Staged}
	case 'T':
		return &change{"typechange", typ, Staged}
	}

	return nil
}

func decodeSecondaryChangeCode(x, y rune) *change {
	switch {
	case y == 'M': //.M
		return &change{"  modified", mod, Unstaged}
	// Don't show deleted 'y' during a merge conflict.
	case y == 'D' && x != 'D' && x != 'U': //[!D!U]D
		return &change{"   deleted", del, Unstaged}
	case y == 'T': //.T
		return &change{"typechange", typ, Unstaged}
	}

	return nil
}
<commit_msg>fix git init issue<commit_after>package status

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Process takes the raw output of `git status --porcelain -b -z` and turns it
// into a structured data type.
func Process(gitStatusOutput []byte, root string) *StatusList {
	// initialize a statuslist to hold the results
	results := NewStatusList()

	// put the output into a bufferreader+scanner so we can consume it iteratively
	scanner := bufio.NewScanner(bytes.NewReader(gitStatusOutput))

	// the scanner needs a custom split function for splitting on NUL
	scanNul := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i, b := range data {
			if b == '\x00' {
				return i + 1, data[:i], nil
			}
		}
		return 0, nil, nil
	}
	scanner.Split(scanNul)

	// branch output is first line
	if !scanner.Scan() {
		log.Println("Failed to read buffer when expecting branch status")
		log.Fatal(scanner.Err())
	}
	branchBytes := scanner.Bytes()
	results.branch = ExtractBranch(branchBytes)

	// give ProcessChanges the scanner and let it handle the rest
	// (it does complicated stuff so it needs the entire scanner)
	for _, r := range ProcessChanges(scanner, root) {
		results.groups[r.group].items = append(results.groups[r.group].items, r)
	}

	return results
}

// ExtractBranch handles parsing the branch status from `status --porcelain -b`.
//
// Examples of stuff we will want to parse:
//
// 		## Initial commit on master
// 		## master
// 		## master...origin/master
// 		## master...origin/master [ahead 1]
//
func ExtractBranch(bs []byte) *BranchInfo {
	b := BranchInfo{}

	b.name = decodeBranchName(bs)
	b.ahead, b.behind = decodeBranchPosition(bs)

	return &b
}

func decodeBranchName(bs []byte) (branch string) {
	branchRegex := regexp.MustCompile(`^## (?:Initial commit on )?(?:No commits yet on )?(\S+?)(?:\.{3}|$)`)
	headRegex := regexp.MustCompile(`^## (HEAD \(no branch\))`)

	branchMatch := branchRegex.FindSubmatch(bs)
	if branchMatch != nil {
		branch = string(branchMatch[1])
	}

	headMatch := headRegex.FindSubmatch(bs)
	if headMatch != nil {
		branch = string(headMatch[1])
	}

	if headMatch == nil && branchMatch == nil {
		log.Fatalf("Failed to parse branch name for output: [%s]", bs)
	}

	return
}

func decodeBranchPosition(bs []byte) (ahead, behind int) {
	reA := regexp.MustCompile(`\[ahead ?(\d+).*\]`)
	reB := regexp.MustCompile(`\[.*behind ?(\d+)\]`)

	mA := reA.FindSubmatch(bs)
	if mA != nil {
		ahead, _ = strconv.Atoi(string(mA[1]))
	}

	mB := reB.FindSubmatch(bs)
	if mB != nil {
		behind, _ = strconv.Atoi(string(mB[1]))
	}

	return
}

/*
ProcessChanges takes `git status -z` output and returns all status items.

(Note: in our case, we actually use `git status -bz` and remove branch header
when we process it earlier, but the results are binary identical.)

This is a complicated process because the format is weird. Each line is a
variable length number of columns (2-3), but the separator for 1-2 is a space
(but the content of columns can contain spaces too!), and the separator for 2-3
is a NUL character (ASCII 0), *if* there is a third column. But here's where it
gets wacky: NUL is also the entry terminator (rather than a LF like in normal
porcelain mode)

Thankfully(?), column 1 which contains the status codes is a fixed length of two
bytes, and in theory the status codes contain enough secrets for us to determine
whether we should expect 2 or 3 columns (current hypothesis is we only get the
third column which is PATH2 when there is a "rename" operation). Sooo... we can
just read those two bytes and use that to determine how many NULs to scan to
until we have consumed a full entry.

We put up with this because it means no shell escaping, which should mean better
cross-platform support. Better hope some Windows people end up using it someday!
*/
func ProcessChanges(s *bufio.Scanner, root string) (results []*StatusItem) {

	// Before we process any changes, get the Current Working Directory.
	// We're going to need use to calculate absolute and relative filepaths for
	// every change, so we get it once now and pass it along.
	// If for some reason this fails (?), fallback to the git worktree root.
	wd, err := os.Getwd()
	if err != nil {
		wd = root
	}

	for s.Scan() {
		chunk := s.Bytes()
		// ...if chunk represents a rename or copy op, need to append another chunk
		// to get the full change item, with NUL manually reinserted because scanner
		// will extract past it.
		if (chunk[0] == 'R' || chunk[0] == 'C') && s.Scan() {
			chunk = append(chunk, '\x00')
			chunk = append(chunk, s.Bytes()...)
		}
		results = append(results, processChange(chunk, wd, root)...)
	}

	return
}

// process change for a single item from a `git status -z`.
//
// Takes raw bytes representing status change from `git status --porcelain -z`,
// assumes that it has already been properly split away from the rest of the
// changes.
//
// See ProcessChanges (plural) for more details on that process.
//
// Note some change items can have multiple statuses, so this returns a slice.
func processChange(chunk []byte, wd, root string) (results []*StatusItem) {

	absolutePath, relativePath := extractFile(chunk, root, wd)

	for _, c := range extractChangeCodes(chunk) {
		result := &StatusItem{
			msg:         c.msg,
			col:         c.col,
			group:       c.group,
			fileAbsPath: absolutePath,
			fileRelPath: relativePath,
		}
		results = append(results, result)
	}

	if len(results) < 1 {
		log.Fatalf(`
Failed to decode git status change code for chunk: [%s]
Please file a bug including this error message as well as the output of:

git status --porcelain

You can file the bug at: https://github.com/mroth/scmpuff/issues/
		`, chunk)
	}
	return results
}

/*
extractFile extracts the filename from a status change, and determines the
absolute and display paths.

 - root: the absolute path to the git working tree
 - wd: current working directory path
*/
func extractFile(chunk []byte, root, wd string) (absPath, relPath string) {
	// file identifier starts at pos4 and continues to EOL
	filePortion := chunk[3:]
	files := bytes.SplitN(filePortion, []byte{'\x00'}, 2)

	n := len(files)
	switch {
	case n < 1:
		log.Fatalf("tried to process a change chunk with no file")
	case n > 1:
		toFile, fromFile := files[0], files[1]
		var toRelPath, fromRelPath string

		absPath, toRelPath = calcPaths(toFile, root, wd)
		_, fromRelPath = calcPaths(fromFile, root, wd)

		relPath = fmt.Sprintf("%s -> %s", fromRelPath, toRelPath)
	default:
		absPath, relPath = calcPaths(files[0], root, wd)
	}

	return
}

// given path of a file relative to git root, git root, and working directory,
// calculate the absolute path of the file on the system, and attempt to figure
// out its relative path to $CWD (if can't, fallback to absolute for both).
func calcPaths(rootPath []byte, root, wd string) (absPath, relPath string) {
	file := rootPath
	absPath = filepath.Join(root, string(file))
	relPath, err := filepath.Rel(wd, absPath)
	if err != nil {
		relPath = absPath
	}
	return
}

// basically a StatusItem minus the file information, for now just being
// used to get results from the change code processing...
// This could probably be encapsulated in StatusItem itself, but wary of adding
// more nesting...
// TODO: should either figure out a way to get rid of this or formalize it more.
type change struct {
	msg   string
	col   ColorGroup
	group StatusGroup
}

/*
Extracts a git status "short code" into the proper UI "change" items we will
display in our status output.

Below documentation from git status:

   Ignored files are not listed, unless --ignored option is in effect, in
   which case XY are !!.

   X          Y     Meaning
   -------------------------------------------------
             [MD]   not updated
   M        [ MD]   updated in index
   A        [ MD]   added to index
   D         [ M]   deleted from index
   R        [ MD]   renamed in index
   C        [ MD]   copied in index
   [MARC]           index and work tree matches
   [ MARC]     M    work tree changed since index
   [ MARC]     D    deleted in work tree
   -------------------------------------------------
   D           D    unmerged, both deleted
   A           U    unmerged, added by us
   U           D    unmerged, deleted by them
   U           A    unmerged, added by them
   D           U    unmerged, deleted by us
   A           A    unmerged, both added
   U           U    unmerged, both modified
   -------------------------------------------------
   ?           ?    untracked
   !           !    ignored
   -------------------------------------------------
*/
func extractChangeCodes(chunk []byte) []*change {
	x := rune(chunk[0])
	y := rune(chunk[1])

	var changes []*change
	if p := decodePrimaryChangeCode(x, y); p != nil {
		changes = append(changes, p)
	}
	if s := decodeSecondaryChangeCode(x, y); s != nil {
		changes = append(changes, s)
	}
	return changes
}

func decodePrimaryChangeCode(x, y rune) *change {
	xy := string(x) + string(y)

	// unmerged cases are simple, only a single change UI is possible
	//
	// TODO: should we handle !! as well? need to determine if possible for user
	// to enable it via their gitconfig where it would affect us.  we coud also
	// allow it to be activated via CLI switch on our end too if desired.
	switch xy {
	case "DD":
		return &change{"   both deleted", del, Unmerged}
	case "AU":
		return &change{"    added by us", neu, Unmerged}
	case "UD":
		return &change{"deleted by them", del, Unmerged}
	case "UA":
		return &change{"  added by them", neu, Unmerged}
	case "DU":
		return &change{"  deleted by us", del, Unmerged}
	case "AA":
		return &change{"     both added", neu, Unmerged}
	case "UU":
		return &change{"  both modified", mod, Unmerged}
	case "??":
		return &change{" untracked", unt, Untracked}
	}

	// staged changes are all single X cases
	// right now we dont need to check the Y, because we consider a modifying Y to
	// these to be a compound case that adds a secondary change in the UI, so that
	// is currently handled in decodeSecondaryChangeCode()
	switch x {
	case 'M':
		return &change{"  modified", mod, Staged}
	case 'A':
		return &change{"  new file", neu, Staged}
	case 'D':
		return &change{"   deleted", del, Staged}
	case 'R':
		return &change{"   renamed", ren, Staged}
	case 'C':
		return &change{"    copied", cpy, Staged}
	case 'T':
		return &change{"typechange", typ, Staged}
	}

	return nil
}

func decodeSecondaryChangeCode(x, y rune) *change {
	switch {
	case y == 'M': //.M
		return &change{"  modified", mod, Unstaged}
	// Don't show deleted 'y' during a merge conflict.
	case y == 'D' && x != 'D' && x != 'U': //[!D!U]D
		return &change{"   deleted", del, Unstaged}
	case y == 'T': //.T
		return &change{"typechange", typ, Unstaged}
	}

	return nil
}
<|endoftext|>


<commit_before>package signal_test

import (
	"context"
	"errors"
	"testing"

	. "v2ray.com/core/common/signal"
	. "v2ray.com/ext/assert"
)

func TestErrorOrFinish2_Error(t *testing.T) {
	assert := With(t)

	c1 := make(chan error, 1)
	c2 := make(chan error, 2)
	c := make(chan error, 1)

	go func() {
		c <- ErrorOrFinish2(context.Background(), c1, c2)
	}()

	c1 <- errors.New("test")
	err := <-c
	assert(err.Error(), Equals, "test")
}

func TestErrorOrFinish2_Error2(t *testing.T) {
	assert := With(t)

	c1 := make(chan error, 1)
	c2 := make(chan error, 2)
	c := make(chan error, 1)

	go func() {
		c <- ErrorOrFinish2(context.Background(), c1, c2)
	}()

	c2 <- errors.New("test")
	err := <-c
	assert(err.Error(), Equals, "test")
}

func TestErrorOrFinish2_NoneError(t *testing.T) {
	assert := With(t)

	c1 := make(chan error, 1)
	c2 := make(chan error, 2)
	c := make(chan error, 1)

	go func() {
		c <- ErrorOrFinish2(context.Background(), c1, c2)
	}()

	close(c1)
	select {
	case <-c:
		t.Fail()
	default:
	}

	close(c2)
	err := <-c
	assert(err, IsNil)
}

func TestErrorOrFinish2_NoneError2(t *testing.T) {
	assert := With(t)

	c1 := make(chan error, 1)
	c2 := make(chan error, 2)
	c := make(chan error, 1)

	go func() {
		c <- ErrorOrFinish2(context.Background(), c1, c2)
	}()

	close(c2)
	select {
	case <-c:
		t.Fail()
	default:
	}

	close(c1)
	err := <-c
	assert(err, IsNil)
}
<commit_msg>fix test<commit_after>package signal_test

import (
	"context"
	"errors"
	"testing"
	"time"

	. "v2ray.com/core/common/signal"
	. "v2ray.com/ext/assert"
)

func TestExecuteParallel(t *testing.T) {
	assert := With(t)

	err := ExecuteParallel(context.Background(), func() error {
		time.Sleep(time.Millisecond * 200)
		return errors.New("test")
	}, func() error {
		time.Sleep(time.Millisecond * 500)
		return errors.New("test2")
	})

	assert(err.Error(), Equals, "test")
}

func TestExecuteParallelContextCancel(t *testing.T) {
	assert := With(t)

	ctx, cancel := context.WithCancel(context.Background())
	err := ExecuteParallel(ctx, func() error {
		time.Sleep(time.Millisecond * 2000)
		return errors.New("test")
	}, func() error {
		time.Sleep(time.Millisecond * 5000)
		return errors.New("test2")
	}, func() error {
		cancel()
		return nil
	})

	assert(err.Error(), HasSubstring, "canceled")
}
<|endoftext|>


<commit_before>// Copyright 2016 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

package expvastic_test

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/arsham/expvastic"
	"github.com/arsham/expvastic/lib"
	reader_testing "github.com/arsham/expvastic/reader/testing"
	recorder_testing "github.com/arsham/expvastic/recorder/testing"
)

func ExampleEngine_sendingJobs() {
	log := lib.DiscardLogger()
	ctx, cancel := context.WithCancel(context.Background())
	recorded := make(chan string)

	redTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		desire := `{"the key": "is the value!"}`
		io.WriteString(w, desire)
	}))
	defer redTs.Close()

	recTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		recorded <- "Job was recorded"
	}))
	defer recTs.Close()

	red, err := reader_testing.NewSimpleReader(log, redTs.URL, "reader_example", "typeName", time.Millisecond, time.Millisecond, 5) //for testing
	if err != nil {
		panic(err)
	}
	rec, err := recorder_testing.NewSimpleRecorder(ctx, log, "reader_example", recTs.URL, "intexName", time.Millisecond, 5)
	if err != nil {
		panic(err)
	}

	e, err := expvastic.NewWithReadRecorder(ctx, log, rec, red)
	done := make(chan struct{})
	go func() {
		e.Start()
		done <- struct{}{}
	}()
	fmt.Println("Engine creation success:", err == nil)

	fmt.Println(<-recorded)

	cancel()
	<-done
	fmt.Println("Client closed gracefully")

	// Output:
	// Engine creation success: true
	// Job was recorded
	// Client closed gracefully
}
<commit_msg>Increases the chance of reader/recorders<commit_after>// Copyright 2016 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

package expvastic_test

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/arsham/expvastic"
	"github.com/arsham/expvastic/lib"
	reader_testing "github.com/arsham/expvastic/reader/testing"
	recorder_testing "github.com/arsham/expvastic/recorder/testing"
)

func ExampleEngine_sendingJobs() {
	log := lib.DiscardLogger()
	ctx, cancel := context.WithCancel(context.Background())
	recorded := make(chan string)

	redTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		desire := `{"the key": "is the value!"}`
		io.WriteString(w, desire)
	}))
	defer redTs.Close()

	recTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		recorded <- "Job was recorded"
	}))
	defer recTs.Close()

	red, err := reader_testing.NewSimpleReader(log, redTs.URL, "reader_example", "typeName", time.Second, time.Second, 5) //for testing
	if err != nil {
		panic(err)
	}
	rec, err := recorder_testing.NewSimpleRecorder(ctx, log, "reader_example", recTs.URL, "intexName", time.Second, 5)
	if err != nil {
		panic(err)
	}

	e, err := expvastic.NewWithReadRecorder(ctx, log, rec, red)
	done := make(chan struct{})
	go func() {
		e.Start()
		done <- struct{}{}
	}()
	fmt.Println("Engine creation success:", err == nil)

	fmt.Println(<-recorded)

	cancel()
	<-done
	fmt.Println("Client closed gracefully")

	// Output:
	// Engine creation success: true
	// Job was recorded
	// Client closed gracefully
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0
// Details: https://raw.githubusercontent.com/square/quotaservice/master/LICENSE

package admin

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	"github.com/square/quotaservice/config"
	"github.com/square/quotaservice/test/helpers"
)

func TestNamespacesPostWithVersion(t *testing.T) {
	ts := establishTestServer(3)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "3", t)

	if jsonResponse["error"] != "" {
		t.Errorf("POST request with correct version header should succeed", jsonResponse)
	}
}

func TestNamespacesPostNoVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, false, http.MethodPost, "", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesPostIncorrectVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "3", t)

	if jsonResponse["error"] != http.StatusText(http.StatusConflict) {
		t.Errorf("Expected 409 Conflict, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesPostBadVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "ABC", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesGetWithVersion(t *testing.T) {
	ts := establishTestServer(3)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "3", t)

	if jsonResponse["error"] != "" {
		t.Errorf("GET request with correct version header should succeed", jsonResponse)
	}
}

func TestNamespacesGetNoVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, false, http.MethodGet, "", t)

	if jsonResponse["error"] != "" {
		t.Errorf("GET request without version header should succeed", jsonResponse)
	}
}

func TestNamespacesGetIncorrectVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "3", t)

	if jsonResponse["error"] != http.StatusText(http.StatusConflict) {
		t.Errorf("Expected 409 Conflict, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesGetBadVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "ABC", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestUnmarshalBucketConfig(t *testing.T) {
	c := config.NewDefaultBucketConfig("Blah 123")
	c.FillRate = 12345
	c.MaxDebtMillis = 54321
	c.MaxIdleMillis = 67890
	c.MaxTokensPerRequest = 9876
	c.Size = 50000

	b, e := json.Marshal(c)
	if e != nil {
		t.Fatal("Unable to JSONify proto", e)
	}

	reRead, err := getBucketConfig(bytes.NewReader(b))
	if err != nil {
		t.Fatal("Unable to unmarshal JSON", err)
	}
	if !reflect.DeepEqual(c, reRead) {
		t.Fatalf("Two representations aren't equal: %+v != %+v", c, reRead)
	}
}

func TestUnmarshalNamespaceConfig(t *testing.T) {
	n := config.NewDefaultNamespaceConfig("Blah Namespace 123")
	n.MaxDynamicBuckets = 8000
	config.SetDynamicBucketTemplate(n, config.NewDefaultBucketConfig(""))

	c1 := config.NewDefaultBucketConfig("Blah 123")
	c1.FillRate = 12345
	c1.MaxDebtMillis = 54321
	c1.MaxIdleMillis = 67890
	c1.MaxTokensPerRequest = 9876
	c1.Size = 50000

	c2 := config.NewDefaultBucketConfig("Blah 456")
	c2.FillRate = 123450
	c2.MaxDebtMillis = 543210
	c2.MaxIdleMillis = 678900
	c2.MaxTokensPerRequest = 98760
	c2.Size = 5000

	c3 := config.NewDefaultBucketConfig("Blah 789")
	c3.FillRate = 1234500
	c3.MaxDebtMillis = 5432100
	c3.MaxIdleMillis = 6789000
	c3.MaxTokensPerRequest = 987600
	c3.Size = 500

	helpers.CheckError(t, config.AddBucket(n, c1))
	helpers.CheckError(t, config.AddBucket(n, c2))
	helpers.CheckError(t, config.AddBucket(n, c3))

	b, e := json.Marshal(n)
	if e != nil {
		t.Fatal("Unable to JSONify proto", e)
	}

	reRead, err := getNamespaceConfig(bytes.NewReader(b))
	if err != nil {
		t.Fatal("Unable to unmarshal JSON", err)
	}

	if !reflect.DeepEqual(n, reRead) {
		t.Fatalf("Two representations aren't equal: %+v != %+v", n, reRead)
	}
}

func establishTestServer(version int32) *httptest.Server {
	a := NewMockAdministrable()
	a.Configs().Version = version

	apiHandler := apiVersionHandler(a, http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			writeJSONOk(w)
		}),
	)

	return httptest.NewServer(apiHandler)
}

func executeRequestForVersioningTest(ts *httptest.Server, versioned bool, method string, version string, t *testing.T) map[string]string {
	client := &http.Client{}
	request, err := http.NewRequest(method, ts.URL, strings.NewReader(""))

	if versioned {
		request.Header.Set("Version", version)
	}

	res, err := client.Do(request)

	if err != nil {
		t.Fatal(err)
	}

	jsonResponse := make(map[string]string)
	err = unmarshalJSON(res.Body, &jsonResponse)

	if err != nil {
		t.Fatal(err)
	}

	return jsonResponse
}
<commit_msg>fix go vet errors<commit_after>// Licensed under the Apache License, Version 2.0
// Details: https://raw.githubusercontent.com/square/quotaservice/master/LICENSE

package admin

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	"github.com/square/quotaservice/config"
	"github.com/square/quotaservice/test/helpers"
)

func TestNamespacesPostWithVersion(t *testing.T) {
	ts := establishTestServer(3)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "3", t)

	if jsonResponse["error"] != "" {
		t.Errorf("POST request with correct version header should succeed: %+v", jsonResponse)
	}
}

func TestNamespacesPostNoVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, false, http.MethodPost, "", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesPostIncorrectVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "3", t)

	if jsonResponse["error"] != http.StatusText(http.StatusConflict) {
		t.Errorf("Expected 409 Conflict, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesPostBadVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodPost, "ABC", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesGetWithVersion(t *testing.T) {
	ts := establishTestServer(3)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "3", t)

	if jsonResponse["error"] != "" {
		t.Errorf("GET request with correct version header should succeed: %+v", jsonResponse)
	}
}

func TestNamespacesGetNoVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, false, http.MethodGet, "", t)

	if jsonResponse["error"] != "" {
		t.Errorf("GET request without version header should succeed: %+v", jsonResponse)
	}
}

func TestNamespacesGetIncorrectVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "3", t)

	if jsonResponse["error"] != http.StatusText(http.StatusConflict) {
		t.Errorf("Expected 409 Conflict, but received \"%+v\"", jsonResponse)
	}
}

func TestNamespacesGetBadVersion(t *testing.T) {
	ts := establishTestServer(10)
	defer ts.Close()
	jsonResponse := executeRequestForVersioningTest(ts, true, http.MethodGet, "ABC", t)

	if jsonResponse["error"] != http.StatusText(http.StatusBadRequest) {
		t.Errorf("Expected 400 Bad Request, but received \"%+v\"", jsonResponse)
	}
}

func TestUnmarshalBucketConfig(t *testing.T) {
	c := config.NewDefaultBucketConfig("Blah 123")
	c.FillRate = 12345
	c.MaxDebtMillis = 54321
	c.MaxIdleMillis = 67890
	c.MaxTokensPerRequest = 9876
	c.Size = 50000

	b, e := json.Marshal(c)
	if e != nil {
		t.Fatal("Unable to JSONify proto", e)
	}

	reRead, err := getBucketConfig(bytes.NewReader(b))
	if err != nil {
		t.Fatal("Unable to unmarshal JSON", err)
	}
	if !reflect.DeepEqual(c, reRead) {
		t.Fatalf("Two representations aren't equal: %+v != %+v", c, reRead)
	}
}

func TestUnmarshalNamespaceConfig(t *testing.T) {
	n := config.NewDefaultNamespaceConfig("Blah Namespace 123")
	n.MaxDynamicBuckets = 8000
	config.SetDynamicBucketTemplate(n, config.NewDefaultBucketConfig(""))

	c1 := config.NewDefaultBucketConfig("Blah 123")
	c1.FillRate = 12345
	c1.MaxDebtMillis = 54321
	c1.MaxIdleMillis = 67890
	c1.MaxTokensPerRequest = 9876
	c1.Size = 50000

	c2 := config.NewDefaultBucketConfig("Blah 456")
	c2.FillRate = 123450
	c2.MaxDebtMillis = 543210
	c2.MaxIdleMillis = 678900
	c2.MaxTokensPerRequest = 98760
	c2.Size = 5000

	c3 := config.NewDefaultBucketConfig("Blah 789")
	c3.FillRate = 1234500
	c3.MaxDebtMillis = 5432100
	c3.MaxIdleMillis = 6789000
	c3.MaxTokensPerRequest = 987600
	c3.Size = 500

	helpers.CheckError(t, config.AddBucket(n, c1))
	helpers.CheckError(t, config.AddBucket(n, c2))
	helpers.CheckError(t, config.AddBucket(n, c3))

	b, e := json.Marshal(n)
	if e != nil {
		t.Fatal("Unable to JSONify proto", e)
	}

	reRead, err := getNamespaceConfig(bytes.NewReader(b))
	if err != nil {
		t.Fatal("Unable to unmarshal JSON", err)
	}

	if !reflect.DeepEqual(n, reRead) {
		t.Fatalf("Two representations aren't equal: %+v != %+v", n, reRead)
	}
}

func establishTestServer(version int32) *httptest.Server {
	a := NewMockAdministrable()
	a.Configs().Version = version

	apiHandler := apiVersionHandler(a, http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			writeJSONOk(w)
		}),
	)

	return httptest.NewServer(apiHandler)
}

func executeRequestForVersioningTest(ts *httptest.Server, versioned bool, method string, version string, t *testing.T) map[string]string {
	client := &http.Client{}
	request, err := http.NewRequest(method, ts.URL, strings.NewReader(""))

	if versioned {
		request.Header.Set("Version", version)
	}

	res, err := client.Do(request)

	if err != nil {
		t.Fatal(err)
	}

	jsonResponse := make(map[string]string)
	err = unmarshalJSON(res.Body, &jsonResponse)

	if err != nil {
		t.Fatal(err)
	}

	return jsonResponse
}
<|endoftext|>


<commit_before>package admin

import (
	"fmt"
	"net/http"
	"path"
	"strings"

	"github.com/qor/qor/responder"
	"github.com/qor/qor/roles"
)

type controller struct {
	*Admin
}

const HTTPUnprocessableEntity = 422

func (context *Context) checkResourcePermission(permission roles.PermissionMode) bool {
	if context.Resource == nil || context.Resource.HasPermission(permission, context.Context) {
		return true
	}
	context.Writer.Write([]byte("Permission denied"))
	return false
}

func (ac *controller) Dashboard(context *Context) {
	context.Execute("dashboard", nil)
}

func (ac *controller) Index(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		// Singleton Resource
		if context.Resource.Config.Singleton {
			var result = context.Resource.NewStruct()
			if err := context.Resource.CallFindMany(result, context.Context); err == nil {
				context.Execute("show", result)
			} else {
				context.Execute("new", result)
			}
			return
		}

		result, err := context.FindMany()
		context.AddError(err)

		if context.HasError() {
			http.NotFound(context.Writer, context.Request)
		} else {
			responder.With("html", func() {
				context.Execute("index", result)
			}).With("json", func() {
				context.JSON("index", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) SearchCenter(context *Context) {
	type searchResult struct {
		Context  *Context
		Resource *Resource
		Results  interface{}
	}
	var searchResults []searchResult
	for _, res := range context.Admin.searchResources {
		resourceName := context.Request.URL.Query().Get("resource_name")
		if resourceName == "" || res.ToParam() == resourceName {
			ctx := context.clone().setResource(res)
			if results, err := ctx.FindMany(); err == nil {
				searchResults = append(searchResults, searchResult{
					Context:  ctx,
					Resource: res,
					Results:  results,
				})
			}
		}
	}
	context.Execute("search_center", searchResults)
}

func (ac *controller) New(context *Context) {
	if context.checkResourcePermission(roles.Create) {
		context.Execute("new", context.Resource.NewStruct())
	}
}

func (ac *controller) Create(context *Context) {
	if context.checkResourcePermission(roles.Create) {
		res := context.Resource
		result := res.NewStruct()
		if context.AddError(res.Decode(context.Context, result)); !context.HasError() {
			context.AddError(res.CallSave(result, context.Context))
		}

		if context.HasError() {
			responder.With("html", func() {
				context.Writer.WriteHeader(HTTPUnprocessableEntity)
				context.Execute("new", result)
			}).With("json", func() {
				context.Writer.WriteHeader(HTTPUnprocessableEntity)
				context.JSON("index", map[string]interface{}{"errors": context.GetErrors()})
			}).Respond(context.Request)
		} else {
			responder.With("html", func() {
				context.Flash(string(context.dt("resource_successfully_created", "{{.Name}} was successfully created", res)), "success")
				if res.Config.Singleton {
					http.Redirect(context.Writer, context.Request, path.Join(context.Request.URL.Path), http.StatusFound)
				} else {
					http.Redirect(context.Writer, context.Request, context.editResourcePath(result, res), http.StatusFound)
				}
			}).With("json", func() {
				context.JSON("show", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) Show(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		result, err := context.FindOne()
		context.AddError(err)

		responder.With("html", func() {
			context.Execute("show", result)
		}).With("json", func() {
			context.JSON("show", result)
		}).Respond(context.Request)
	}
}

func (ac *controller) Edit(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		result, err := context.FindOne()
		context.AddError(err)

		responder.With("html", func() {
			context.Execute("edit", result)
		}).With("json", func() {
			context.JSON("edit", result)
		}).Respond(context.Request)
	}
}

func (ac *controller) Update(context *Context) {
	if context.checkResourcePermission(roles.Update) {
		res := context.Resource
		result, err := context.FindOne()
		context.AddError(err)
		if !context.HasError() {
			if context.AddError(res.Decode(context.Context, result)); !context.HasError() {
				context.AddError(res.CallSave(result, context.Context))
			}
		}

		if context.HasError() {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			responder.With("html", func() {
				context.Execute("edit", result)
			}).With("json", func() {
				context.JSON("edit", map[string]interface{}{"errors": context.GetErrors()})
			}).Respond(context.Request)
		} else {
			responder.With("html", func() {
				context.FlashNow(string(context.dt("resource_successfully_updated", "{{.Name}} was successfully updated", res)), "success")
				if res.Config.Singleton {
					http.Redirect(context.Writer, context.Request, context.UrlFor(res), http.StatusFound)
				} else {
					context.Execute("show", result)
				}
			}).With("json", func() {
				context.JSON("show", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) Delete(context *Context) {
	if context.checkResourcePermission(roles.Delete) {
		res := context.Resource
		status := http.StatusOK
		if context.AddError(res.CallDelete(res.NewStruct(), context.Context)); context.HasError() {
			status = http.StatusNotFound
		}

		responder.With("html", func() {
			http.Redirect(context.Writer, context.Request, path.Join(ac.GetRouter().Prefix, res.ToParam()), status)
		}).With("json", func() {
			context.Writer.WriteHeader(status)
		}).Respond(context.Request)
	}
}

func (ac *controller) Action(context *Context) {
	var err error
	name := strings.Split(context.Request.URL.Path, "/")[4]

	for _, action := range context.Resource.actions {
		if action.Name == name {
			ids := context.Request.Form.Get("ids")
			scope := context.GetDB().Where(fmt.Sprintf("%v IN (?)", context.Resource.PrimaryField().DBName), ids)
			err = action.Handle(scope, context.Context)
		}
	}

	responder.With("html", func() {
		if err == nil {
			http.Redirect(context.Writer, context.Request, context.Request.Referer(), http.StatusFound)
		} else {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			context.Writer.Write([]byte(err.Error()))
		}
	}).With("json", func() {
		if err == nil {
			context.Writer.Write([]byte("OK"))
		} else {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			context.Writer.Write([]byte(err.Error()))
		}
	}).Respond(context.Request)
}

func (ac *controller) Asset(context *Context) {
	file := strings.TrimPrefix(context.Request.URL.Path, ac.GetRouter().Prefix)
	if filename, err := context.findFile(file); err == nil {
		http.ServeFile(context.Writer, context.Request, filename)
	} else {
		http.NotFound(context.Writer, context.Request)
	}
}
<commit_msg>Split responder into another repo<commit_after>package admin

import (
	"fmt"
	"net/http"
	"path"
	"strings"

	"github.com/qor/qor/roles"
	"github.com/qor/responder"
)

type controller struct {
	*Admin
}

const HTTPUnprocessableEntity = 422

func (context *Context) checkResourcePermission(permission roles.PermissionMode) bool {
	if context.Resource == nil || context.Resource.HasPermission(permission, context.Context) {
		return true
	}
	context.Writer.Write([]byte("Permission denied"))
	return false
}

func (ac *controller) Dashboard(context *Context) {
	context.Execute("dashboard", nil)
}

func (ac *controller) Index(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		// Singleton Resource
		if context.Resource.Config.Singleton {
			var result = context.Resource.NewStruct()
			if err := context.Resource.CallFindMany(result, context.Context); err == nil {
				context.Execute("show", result)
			} else {
				context.Execute("new", result)
			}
			return
		}

		result, err := context.FindMany()
		context.AddError(err)

		if context.HasError() {
			http.NotFound(context.Writer, context.Request)
		} else {
			responder.With("html", func() {
				context.Execute("index", result)
			}).With("json", func() {
				context.JSON("index", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) SearchCenter(context *Context) {
	type searchResult struct {
		Context  *Context
		Resource *Resource
		Results  interface{}
	}
	var searchResults []searchResult
	for _, res := range context.Admin.searchResources {
		resourceName := context.Request.URL.Query().Get("resource_name")
		if resourceName == "" || res.ToParam() == resourceName {
			ctx := context.clone().setResource(res)
			if results, err := ctx.FindMany(); err == nil {
				searchResults = append(searchResults, searchResult{
					Context:  ctx,
					Resource: res,
					Results:  results,
				})
			}
		}
	}
	context.Execute("search_center", searchResults)
}

func (ac *controller) New(context *Context) {
	if context.checkResourcePermission(roles.Create) {
		context.Execute("new", context.Resource.NewStruct())
	}
}

func (ac *controller) Create(context *Context) {
	if context.checkResourcePermission(roles.Create) {
		res := context.Resource
		result := res.NewStruct()
		if context.AddError(res.Decode(context.Context, result)); !context.HasError() {
			context.AddError(res.CallSave(result, context.Context))
		}

		if context.HasError() {
			responder.With("html", func() {
				context.Writer.WriteHeader(HTTPUnprocessableEntity)
				context.Execute("new", result)
			}).With("json", func() {
				context.Writer.WriteHeader(HTTPUnprocessableEntity)
				context.JSON("index", map[string]interface{}{"errors": context.GetErrors()})
			}).Respond(context.Request)
		} else {
			responder.With("html", func() {
				context.Flash(string(context.dt("resource_successfully_created", "{{.Name}} was successfully created", res)), "success")
				if res.Config.Singleton {
					http.Redirect(context.Writer, context.Request, path.Join(context.Request.URL.Path), http.StatusFound)
				} else {
					http.Redirect(context.Writer, context.Request, context.editResourcePath(result, res), http.StatusFound)
				}
			}).With("json", func() {
				context.JSON("show", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) Show(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		result, err := context.FindOne()
		context.AddError(err)

		responder.With("html", func() {
			context.Execute("show", result)
		}).With("json", func() {
			context.JSON("show", result)
		}).Respond(context.Request)
	}
}

func (ac *controller) Edit(context *Context) {
	if context.checkResourcePermission(roles.Read) {
		result, err := context.FindOne()
		context.AddError(err)

		responder.With("html", func() {
			context.Execute("edit", result)
		}).With("json", func() {
			context.JSON("edit", result)
		}).Respond(context.Request)
	}
}

func (ac *controller) Update(context *Context) {
	if context.checkResourcePermission(roles.Update) {
		res := context.Resource
		result, err := context.FindOne()
		context.AddError(err)
		if !context.HasError() {
			if context.AddError(res.Decode(context.Context, result)); !context.HasError() {
				context.AddError(res.CallSave(result, context.Context))
			}
		}

		if context.HasError() {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			responder.With("html", func() {
				context.Execute("edit", result)
			}).With("json", func() {
				context.JSON("edit", map[string]interface{}{"errors": context.GetErrors()})
			}).Respond(context.Request)
		} else {
			responder.With("html", func() {
				context.FlashNow(string(context.dt("resource_successfully_updated", "{{.Name}} was successfully updated", res)), "success")
				if res.Config.Singleton {
					http.Redirect(context.Writer, context.Request, context.UrlFor(res), http.StatusFound)
				} else {
					context.Execute("show", result)
				}
			}).With("json", func() {
				context.JSON("show", result)
			}).Respond(context.Request)
		}
	}
}

func (ac *controller) Delete(context *Context) {
	if context.checkResourcePermission(roles.Delete) {
		res := context.Resource
		status := http.StatusOK
		if context.AddError(res.CallDelete(res.NewStruct(), context.Context)); context.HasError() {
			status = http.StatusNotFound
		}

		responder.With("html", func() {
			http.Redirect(context.Writer, context.Request, path.Join(ac.GetRouter().Prefix, res.ToParam()), status)
		}).With("json", func() {
			context.Writer.WriteHeader(status)
		}).Respond(context.Request)
	}
}

func (ac *controller) Action(context *Context) {
	var err error
	name := strings.Split(context.Request.URL.Path, "/")[4]

	for _, action := range context.Resource.actions {
		if action.Name == name {
			ids := context.Request.Form.Get("ids")
			scope := context.GetDB().Where(fmt.Sprintf("%v IN (?)", context.Resource.PrimaryField().DBName), ids)
			err = action.Handle(scope, context.Context)
		}
	}

	responder.With("html", func() {
		if err == nil {
			http.Redirect(context.Writer, context.Request, context.Request.Referer(), http.StatusFound)
		} else {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			context.Writer.Write([]byte(err.Error()))
		}
	}).With("json", func() {
		if err == nil {
			context.Writer.Write([]byte("OK"))
		} else {
			context.Writer.WriteHeader(HTTPUnprocessableEntity)
			context.Writer.Write([]byte(err.Error()))
		}
	}).Respond(context.Request)
}

func (ac *controller) Asset(context *Context) {
	file := strings.TrimPrefix(context.Request.URL.Path, ac.GetRouter().Prefix)
	if filename, err := context.findFile(file); err == nil {
		http.ServeFile(context.Writer, context.Request, filename)
	} else {
		http.NotFound(context.Writer, context.Request)
	}
}
<|endoftext|>