<commit_before>package command

import (
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/helper/copy"
	"github.com/mitchellh/cli"
)

func setupTest(fixturepath string, args ...string) (*cli.MockUi, int) {
	ui := new(cli.MockUi)
	c := &ValidateCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args = append(args, testFixturePath(fixturepath))

	code := c.Run(args)
	return ui, code
}

func TestValidateCommand(t *testing.T) {
	if ui, code := setupTest("validate-valid"); code != 0 {
		t.Fatalf("unexpected non-successful exit code %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateCommandWithTfvarsFile(t *testing.T) {
	// Create a temporary working directory that is empty because this test
	// requires scanning the current working directory by validate command.
	td := tempDir(t)
	copy.CopyDir(testFixturePath("validate-valid/with-tfvars-file"), td)
	defer os.RemoveAll(td)
	defer testChdir(t, td)()

	ui := new(cli.MockUi)
	c := &ValidateCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommand(t *testing.T) {
	if ui, code := setupTest("validate-invalid"); code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommandMissingQuote(t *testing.T) {
	// FIXME: Re-enable once we've updated core for new data structures
	t.Skip("test temporarily disabled until deep validate supports new config structures")

	ui, code := setupTest("validate-invalid/missing_quote")

	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "IDENT test") {
		t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommandMissingVariable(t *testing.T) {
	// FIXME: Re-enable once we've updated core for new data structures
	t.Skip("test temporarily disabled until deep validate supports new config structures")

	ui, code := setupTest("validate-invalid/missing_var")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'; define it with a 'variable' block") {
		t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
	}
}

func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_providers")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := "Error: Duplicate provider configuration"
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_modules")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := "Error: Duplicate module call"
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_resources")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := `Error: Duplicate resource "aws_instance" configuration`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestOutputWithoutValueShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/outputs")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := `The attribute "value" is required, but no definition was found.`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `An attribute named "values" is not expected here. Did you mean "value"?`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/incorrectmodulename")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}

	wantError := `Error: Invalid module instance name`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `Error: Variables not allowed`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/interpolation")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}

	wantError := `Error: Variables not allowed`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `A static variable reference is required.`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestMissingDefinedVar(t *testing.T) {
	ui, code := setupTest("validate-invalid/missing_defined_var")
	// This is allowed because validate tests only that variables are referenced
	// correctly, not that they all have defined values.
	if code != 0 {
		t.Fatalf("Should have passed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
}
<commit_msg>command: Fix "terraform validate" tests<commit_after>package command

import (
	"os"
	"strings"
	"testing"

	"github.com/mitchellh/cli"
	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/terraform/configs/configschema"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/helper/copy"
)

func setupTest(fixturepath string, args ...string) (*cli.MockUi, int) {
	ui := new(cli.MockUi)
	p := testProvider()
	p.GetSchemaReturn = &terraform.ProviderSchema{
		ResourceTypes: map[string]*configschema.Block{
			"test_instance": {
				Attributes: map[string]*configschema.Attribute{
					"ami": {Type: cty.String, Optional: true},
				},
				BlockTypes: map[string]*configschema.NestedBlock{
					"network_interface": {
						Nesting: configschema.NestingList,
						Block: configschema.Block{
							Attributes: map[string]*configschema.Attribute{
								"device_index": {Type: cty.String, Optional: true},
								"description":  {Type: cty.String, Optional: true},
							},
						},
					},
				},
			},
		},
	}
	c := &ValidateCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			Ui:               ui,
		},
	}

	args = append(args, testFixturePath(fixturepath))

	code := c.Run(args)
	return ui, code
}

func TestValidateCommand(t *testing.T) {
	if ui, code := setupTest("validate-valid"); code != 0 {
		t.Fatalf("unexpected non-successful exit code %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateCommandWithTfvarsFile(t *testing.T) {
	// Create a temporary working directory that is empty because this test
	// requires scanning the current working directory by validate command.
	td := tempDir(t)
	copy.CopyDir(testFixturePath("validate-valid/with-tfvars-file"), td)
	defer os.RemoveAll(td)
	defer testChdir(t, td)()

	ui := new(cli.MockUi)
	c := &ValidateCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommand(t *testing.T) {
	if ui, code := setupTest("validate-invalid"); code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommandMissingQuote(t *testing.T) {
	// FIXME: Re-enable once we've updated core for new data structures
	t.Skip("test temporarily disabled until deep validate supports new config structures")

	ui, code := setupTest("validate-invalid/missing_quote")

	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "IDENT test") {
		t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
	}
}

func TestValidateFailingCommandMissingVariable(t *testing.T) {
	// FIXME: Re-enable once we've updated core for new data structures
	t.Skip("test temporarily disabled until deep validate supports new config structures")

	ui, code := setupTest("validate-invalid/missing_var")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'; define it with a 'variable' block") {
		t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
	}
}

func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_providers")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := "Error: Duplicate provider configuration"
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_modules")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := "Error: Duplicate module call"
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/multiple_resources")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := `Error: Duplicate resource "aws_instance" configuration`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestOutputWithoutValueShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/outputs")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
	wantError := `The attribute "value" is required, but no definition was found.`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `An attribute named "values" is not expected here. Did you mean "value"?`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/incorrectmodulename")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}

	wantError := `Error: Invalid module instance name`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `Error: Variables not allowed`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
	ui, code := setupTest("validate-invalid/interpolation")
	if code != 1 {
		t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
	}

	wantError := `Error: Variables not allowed`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
	wantError = `A static variable reference is required.`
	if !strings.Contains(ui.ErrorWriter.String(), wantError) {
		t.Fatalf("Missing error string %q\n\n'%s'", wantError, ui.ErrorWriter.String())
	}
}

func TestMissingDefinedVar(t *testing.T) {
	ui, code := setupTest("validate-invalid/missing_defined_var")
	// This is allowed because validate tests only that variables are referenced
	// correctly, not that they all have defined values.
	if code != 0 {
		t.Fatalf("Should have passed: %d\n\n%s", code, ui.ErrorWriter.String())
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 etcd3

import (
	"context"
	"strconv"
	"sync"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
	"k8s.io/klog/v2"
)

const (
	compactRevKey = "compact_rev_key"
)

var (
	endpointsMapMu sync.Mutex
	endpointsMap   map[string]struct{}
)

func init() {
	endpointsMap = make(map[string]struct{})
}

// StartCompactor starts a compactor in the background to compact old version of keys that's not needed.
// By default, we save the most recent 10 minutes data and compact versions > 10minutes ago.
// It should be enough for slow watchers and to tolerate burst.
// TODO: We might keep a longer history (12h) in the future once storage API can take advantage of past version of keys.
func StartCompactor(ctx context.Context, client *clientv3.Client, compactInterval time.Duration) {
	endpointsMapMu.Lock()
	defer endpointsMapMu.Unlock()

	// In one process, we can have only one compactor for one cluster.
	// Currently we rely on endpoints to differentiate clusters.
	for _, ep := range client.Endpoints() {
		if _, ok := endpointsMap[ep]; ok {
			klog.V(4).Infof("compactor already exists for endpoints %v", client.Endpoints())
			return
		}
	}
	for _, ep := range client.Endpoints() {
		endpointsMap[ep] = struct{}{}
	}

	if compactInterval != 0 {
		go compactor(ctx, client, compactInterval)
	}
}

// compactor periodically compacts historical versions of keys in etcd.
// It will compact keys with versions older than given interval.
// In other words, after compaction, it will only contain keys set during last interval.
// Any API call for the older versions of keys will return error.
// Interval is the time interval between each compaction. The first compaction happens after "interval".
func compactor(ctx context.Context, client *clientv3.Client, interval time.Duration) {
	// Technical definitions:
	// We have a special key in etcd defined as *compactRevKey*.
	// compactRevKey's value will be set to the string of last compacted revision.
	// compactRevKey's version will be used as logical time for comparison. THe version is referred as compact time.
	// Initially, because the key doesn't exist, the compact time (version) is 0.
	//
	// Algorithm:
	// - Compare to see if (local compact_time) = (remote compact_time).
	// - If yes, increment both local and remote compact_time, and do a compaction.
	// - If not, set local to remote compact_time.
	//
	// Technical details/insights:
	//
	// The protocol here is lease based. If one compactor CAS successfully, the others would know it when they fail in
	// CAS later and would try again in 10 minutes. If an APIServer crashed, another one would "take over" the lease.
	//
	// For example, in the following diagram, we have a compactor C1 doing compaction in t1, t2. Another compactor C2
	// at t1' (t1 < t1' < t2) would CAS fail, set its known oldRev to rev at t1', and try again in t2' (t2' > t2).
	// If C1 crashed and wouldn't compact at t2, C2 would CAS successfully at t2'.
	//
	//                 oldRev(t2)     curRev(t2)
	//                                  +
	//   oldRev        curRev           |
	//     +             +              |
	//     |             |              |
	//     |             |    t1'       |     t2'
	// +---v-------------v----^---------v------^---->
	//     t0           t1             t2
	//
	// We have the guarantees:
	// - in normal cases, the interval is 10 minutes.
	// - in failover, the interval is >10m and <20m
	//
	// FAQ:
	// - What if time is not accurate? We don't care as long as someone did the compaction. Atomicity is ensured using
	//   etcd API.
	// - What happened under heavy load scenarios? Initially, each apiserver will do only one compaction
	//   every 10 minutes. This is very unlikely affecting or affected w.r.t. server load.

	var compactTime int64
	var rev int64
	var err error
	for {
		select {
		case <-time.After(interval):
		case <-ctx.Done():
			return
		}

		compactTime, rev, err = compact(ctx, client, compactTime, rev)
		if err != nil {
			klog.Errorf("etcd: endpoint (%v) compact failed: %v", client.Endpoints(), err)
			continue
		}
	}
}

// compact compacts etcd store and returns current rev.
// It will return the current compact time and global revision if no error occurred.
// Note that CAS fail will not incur any error.
func compact(ctx context.Context, client *clientv3.Client, t, rev int64) (int64, int64, error) {
	resp, err := client.KV.Txn(ctx).If(
		clientv3.Compare(clientv3.Version(compactRevKey), "=", t),
	).Then(
		clientv3.OpPut(compactRevKey, strconv.FormatInt(rev, 10)), // Expect side effect: increment Version
	).Else(
		clientv3.OpGet(compactRevKey),
	).Commit()
	if err != nil {
		return t, rev, err
	}

	curRev := resp.Header.Revision

	if !resp.Succeeded {
		curTime := resp.Responses[0].GetResponseRange().Kvs[0].Version
		return curTime, curRev, nil
	}
	curTime := t + 1

	if rev == 0 {
		// We don't compact on bootstrap.
		return curTime, curRev, nil
	}
	if _, err = client.Compact(ctx, rev); err != nil {
		return curTime, curRev, err
	}
	klog.V(4).Infof("etcd: compacted rev (%d), endpoints (%v)", rev, client.Endpoints())
	return curTime, curRev, nil
}
<commit_msg>fix etcd compaction doc<commit_after>/*
Copyright 2016 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 etcd3

import (
	"context"
	"strconv"
	"sync"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
	"k8s.io/klog/v2"
)

const (
	compactRevKey = "compact_rev_key"
)

var (
	endpointsMapMu sync.Mutex
	endpointsMap   map[string]struct{}
)

func init() {
	endpointsMap = make(map[string]struct{})
}

// StartCompactor starts a compactor in the background to compact old version of keys that's not needed.
// By default, we save the most recent 5 minutes data and compact versions > 5minutes ago.
// It should be enough for slow watchers and to tolerate burst.
// TODO: We might keep a longer history (12h) in the future once storage API can take advantage of past version of keys.
func StartCompactor(ctx context.Context, client *clientv3.Client, compactInterval time.Duration) {
	endpointsMapMu.Lock()
	defer endpointsMapMu.Unlock()

	// In one process, we can have only one compactor for one cluster.
	// Currently we rely on endpoints to differentiate clusters.
	for _, ep := range client.Endpoints() {
		if _, ok := endpointsMap[ep]; ok {
			klog.V(4).Infof("compactor already exists for endpoints %v", client.Endpoints())
			return
		}
	}
	for _, ep := range client.Endpoints() {
		endpointsMap[ep] = struct{}{}
	}

	if compactInterval != 0 {
		go compactor(ctx, client, compactInterval)
	}
}

// compactor periodically compacts historical versions of keys in etcd.
// It will compact keys with versions older than given interval.
// In other words, after compaction, it will only contain keys set during last interval.
// Any API call for the older versions of keys will return error.
// Interval is the time interval between each compaction. The first compaction happens after "interval".
func compactor(ctx context.Context, client *clientv3.Client, interval time.Duration) {
	// Technical definitions:
	// We have a special key in etcd defined as *compactRevKey*.
	// compactRevKey's value will be set to the string of last compacted revision.
	// compactRevKey's version will be used as logical time for comparison. THe version is referred as compact time.
	// Initially, because the key doesn't exist, the compact time (version) is 0.
	//
	// Algorithm:
	// - Compare to see if (local compact_time) = (remote compact_time).
	// - If yes, increment both local and remote compact_time, and do a compaction.
	// - If not, set local to remote compact_time.
	//
	// Technical details/insights:
	//
	// The protocol here is lease based. If one compactor CAS successfully, the others would know it when they fail in
	// CAS later and would try again in 5 minutes. If an APIServer crashed, another one would "take over" the lease.
	//
	// For example, in the following diagram, we have a compactor C1 doing compaction in t1, t2. Another compactor C2
	// at t1' (t1 < t1' < t2) would CAS fail, set its known oldRev to rev at t1', and try again in t2' (t2' > t2).
	// If C1 crashed and wouldn't compact at t2, C2 would CAS successfully at t2'.
	//
	//                 oldRev(t2)     curRev(t2)
	//                                  +
	//   oldRev        curRev           |
	//     +             +              |
	//     |             |              |
	//     |             |    t1'       |     t2'
	// +---v-------------v----^---------v------^---->
	//     t0           t1             t2
	//
	// We have the guarantees:
	// - in normal cases, the interval is 5 minutes.
	// - in failover, the interval is >5m and <10m
	//
	// FAQ:
	// - What if time is not accurate? We don't care as long as someone did the compaction. Atomicity is ensured using
	//   etcd API.
	// - What happened under heavy load scenarios? Initially, each apiserver will do only one compaction
	//   every 5 minutes. This is very unlikely affecting or affected w.r.t. server load.

	var compactTime int64
	var rev int64
	var err error
	for {
		select {
		case <-time.After(interval):
		case <-ctx.Done():
			return
		}

		compactTime, rev, err = compact(ctx, client, compactTime, rev)
		if err != nil {
			klog.Errorf("etcd: endpoint (%v) compact failed: %v", client.Endpoints(), err)
			continue
		}
	}
}

// compact compacts etcd store and returns current rev.
// It will return the current compact time and global revision if no error occurred.
// Note that CAS fail will not incur any error.
func compact(ctx context.Context, client *clientv3.Client, t, rev int64) (int64, int64, error) {
	resp, err := client.KV.Txn(ctx).If(
		clientv3.Compare(clientv3.Version(compactRevKey), "=", t),
	).Then(
		clientv3.OpPut(compactRevKey, strconv.FormatInt(rev, 10)), // Expect side effect: increment Version
	).Else(
		clientv3.OpGet(compactRevKey),
	).Commit()
	if err != nil {
		return t, rev, err
	}

	curRev := resp.Header.Revision

	if !resp.Succeeded {
		curTime := resp.Responses[0].GetResponseRange().Kvs[0].Version
		return curTime, curRev, nil
	}
	curTime := t + 1

	if rev == 0 {
		// We don't compact on bootstrap.
		return curTime, curRev, nil
	}
	if _, err = client.Compact(ctx, rev); err != nil {
		return curTime, curRev, err
	}
	klog.V(4).Infof("etcd: compacted rev (%d), endpoints (%v)", rev, client.Endpoints())
	return curTime, curRev, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"cmd/internal/browser"
	"flag"
	"fmt"
	"html/template"
	"internal/trace"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
)

const usageMessage = "" +
	`Usage of 'go tool trace':
Given a trace file produced by 'go test':
	go test -trace=trace.out pkg

Open a web browser displaying trace:
	go tool trace [flags] [pkg.test] trace.out

Generate a pprof-like profile from the trace:
    go tool trace -pprof=TYPE [pkg.test] trace.out

[pkg.test] argument is required for traces produced by Go 1.6 and below.
Go 1.7 does not require the binary argument.

Supported profile types are:
    - net: network blocking profile
    - sync: synchronization blocking profile
    - syscall: syscall blocking profile
    - sched: scheduler latency profile

Flags:
	-http=addr: HTTP service address (e.g., ':6060')
	-pprof=type: print a pprof-like profile instead

Note that while the various profiles available when launching
'go tool trace' work on every browser, the trace viewer itself
(the 'view trace' page) comes from the Chrome/Chromium project
and is only actively tested on that browser.
`

var (
	httpFlag  = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
	pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")

	// The binary file name, left here for serveSVGProfile.
	programBinary string
	traceFile     string
)

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, usageMessage)
		os.Exit(2)
	}
	flag.Parse()

	// Go 1.7 traces embed symbol info and does not require the binary.
	// But we optionally accept binary as first arg for Go 1.5 traces.
	switch flag.NArg() {
	case 1:
		traceFile = flag.Arg(0)
	case 2:
		programBinary = flag.Arg(0)
		traceFile = flag.Arg(1)
	default:
		flag.Usage()
	}

	var pprofFunc func(io.Writer) error
	switch *pprofFlag {
	case "net":
		pprofFunc = pprofIO
	case "sync":
		pprofFunc = pprofBlock
	case "syscall":
		pprofFunc = pprofSyscall
	case "sched":
		pprofFunc = pprofSched
	}
	if pprofFunc != nil {
		if err := pprofFunc(os.Stdout); err != nil {
			dief("failed to generate pprof: %v\n", err)
		}
		os.Exit(0)
	}
	if *pprofFlag != "" {
		dief("unknown pprof type %s\n", *pprofFlag)
	}

	ln, err := net.Listen("tcp", *httpFlag)
	if err != nil {
		dief("failed to create server socket: %v\n", err)
	}

	log.Printf("Parsing trace...")
	events, err := parseEvents()
	if err != nil {
		dief("%v\n", err)
	}

	log.Printf("Serializing trace...")
	params := &traceParams{
		events:  events,
		endTime: int64(1<<63 - 1),
	}
	data, err := generateTrace(params)
	if err != nil {
		dief("%v\n", err)
	}

	log.Printf("Splitting trace...")
	ranges = splitTrace(data)

	log.Printf("Opening browser")
	if !browser.Open("http://" + ln.Addr().String()) {
		fmt.Fprintf(os.Stderr, "Trace viewer is listening on http://%s\n", ln.Addr().String())
	}

	// Start http server.
	http.HandleFunc("/", httpMain)
	err = http.Serve(ln, nil)
	dief("failed to start http server: %v\n", err)
}

var ranges []Range

var loader struct {
	once   sync.Once
	events []*trace.Event
	err    error
}

func parseEvents() ([]*trace.Event, error) {
	loader.once.Do(func() {
		tracef, err := os.Open(traceFile)
		if err != nil {
			loader.err = fmt.Errorf("failed to open trace file: %v", err)
			return
		}
		defer tracef.Close()

		// Parse and symbolize.
		events, err := trace.Parse(bufio.NewReader(tracef), programBinary)
		if err != nil {
			loader.err = fmt.Errorf("failed to parse trace: %v", err)
			return
		}
		loader.events = events
	})
	return loader.events, loader.err
}

// httpMain serves the starting page.
func httpMain(w http.ResponseWriter, r *http.Request) {
	if err := templMain.Execute(w, ranges); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

var templMain = template.Must(template.New("").Parse(`

{{if $}}
	{{range $e := $}}
		View trace ({{$e.Name}})

	{{end}}

{{else}}
	View trace

{{end}}
Goroutine analysis

Network blocking profile

Synchronization blocking profile

Syscall blocking profile

Scheduler latency profile

`))

func dief(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg, args...)
	os.Exit(1)
}
<commit_msg>cmd/trace: add -d that prints parsed traces<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"cmd/internal/browser"
	"flag"
	"fmt"
	"html/template"
	"internal/trace"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
)

const usageMessage = "" +
	`Usage of 'go tool trace':
Given a trace file produced by 'go test':
	go test -trace=trace.out pkg

Open a web browser displaying trace:
	go tool trace [flags] [pkg.test] trace.out

Generate a pprof-like profile from the trace:
    go tool trace -pprof=TYPE [pkg.test] trace.out

[pkg.test] argument is required for traces produced by Go 1.6 and below.
Go 1.7 does not require the binary argument.

Supported profile types are:
    - net: network blocking profile
    - sync: synchronization blocking profile
    - syscall: syscall blocking profile
    - sched: scheduler latency profile

Flags:
	-http=addr: HTTP service address (e.g., ':6060')
	-pprof=type: print a pprof-like profile instead
	-d: print debug info such as parsed events

Note that while the various profiles available when launching
'go tool trace' work on every browser, the trace viewer itself
(the 'view trace' page) comes from the Chrome/Chromium project
and is only actively tested on that browser.
`

var (
	httpFlag  = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
	pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")
	debugFlag = flag.Bool("d", false, "print debug information such as parsed events list")

	// The binary file name, left here for serveSVGProfile.
	programBinary string
	traceFile     string
)

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, usageMessage)
		os.Exit(2)
	}
	flag.Parse()

	// Go 1.7 traces embed symbol info and does not require the binary.
	// But we optionally accept binary as first arg for Go 1.5 traces.
	switch flag.NArg() {
	case 1:
		traceFile = flag.Arg(0)
	case 2:
		programBinary = flag.Arg(0)
		traceFile = flag.Arg(1)
	default:
		flag.Usage()
	}

	var pprofFunc func(io.Writer) error
	switch *pprofFlag {
	case "net":
		pprofFunc = pprofIO
	case "sync":
		pprofFunc = pprofBlock
	case "syscall":
		pprofFunc = pprofSyscall
	case "sched":
		pprofFunc = pprofSched
	}
	if pprofFunc != nil {
		if err := pprofFunc(os.Stdout); err != nil {
			dief("failed to generate pprof: %v\n", err)
		}
		os.Exit(0)
	}
	if *pprofFlag != "" {
		dief("unknown pprof type %s\n", *pprofFlag)
	}

	ln, err := net.Listen("tcp", *httpFlag)
	if err != nil {
		dief("failed to create server socket: %v\n", err)
	}

	log.Print("Parsing trace...")
	events, err := parseEvents()
	if err != nil {
		dief("%v\n", err)
	}

	if *debugFlag {
		trace.Print(events)
		os.Exit(0)
	}

	log.Print("Serializing trace...")
	params := &traceParams{
		events:  events,
		endTime: int64(1<<63 - 1),
	}
	data, err := generateTrace(params)
	if err != nil {
		dief("%v\n", err)
	}

	log.Print("Splitting trace...")
	ranges = splitTrace(data)

	addr := "http://" + ln.Addr().String()
	log.Printf("Opening browser. Trace viewer is listening on %s", addr)
	browser.Open(addr)

	// Start http server.
	http.HandleFunc("/", httpMain)
	err = http.Serve(ln, nil)
	dief("failed to start http server: %v\n", err)
}

var ranges []Range

var loader struct {
	once   sync.Once
	events []*trace.Event
	err    error
}

func parseEvents() ([]*trace.Event, error) {
	loader.once.Do(func() {
		tracef, err := os.Open(traceFile)
		if err != nil {
			loader.err = fmt.Errorf("failed to open trace file: %v", err)
			return
		}
		defer tracef.Close()

		// Parse and symbolize.
		events, err := trace.Parse(bufio.NewReader(tracef), programBinary)
		if err != nil {
			loader.err = fmt.Errorf("failed to parse trace: %v", err)
			return
		}
		loader.events = events
	})
	return loader.events, loader.err
}

// httpMain serves the starting page.
func httpMain(w http.ResponseWriter, r *http.Request) {
	if err := templMain.Execute(w, ranges); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

var templMain = template.Must(template.New("").Parse(`

{{if $}}
	{{range $e := $}}
		View trace ({{$e.Name}})

	{{end}}

{{else}}
	View trace

{{end}}
Goroutine analysis

Network blocking profile

Synchronization blocking profile

Syscall blocking profile

Scheduler latency profile

`))

func dief(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg, args...)
	os.Exit(1)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package auth

import (
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"testing"

	"golang.org/x/net/context"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/luci/luci-go/common/auth/internal"
	"github.com/luci/luci-go/common/logging"
)

var (
	ctx = context.Background()
	log = logging.Null()
)

func ExampleDefaultAuthenticatedClient() {
	client, err := AuthenticatedClient(SilentLogin, NewAuthenticator(Options{}))
	if err == ErrLoginRequired {
		log.Errorf("Run 'auth login' to login")
		return
	}
	if err != nil {
		log.Errorf("Failed to login: %s", err)
		return
	}
	_, _ = client.Get("https://some-server.appspot.com")
}

func mockSecretsDir() string {
	tempDir, err := ioutil.TempDir("", "auth_test")
	So(err, ShouldBeNil)

	prev := secretsDir
	secretsDir = func() string { return tempDir }
	Reset(func() {
		secretsDir = prev
		_ = os.RemoveAll(tempDir)
	})
	So(SecretsDir(), ShouldEqual, tempDir)

	return tempDir
}

func mockTokenProvider(factory func() internal.TokenProvider) {
	prev := makeTokenProvider
	makeTokenProvider = func(*Options) (internal.TokenProvider, error) {
		return factory(), nil
	}
	Reset(func() {
		makeTokenProvider = prev
	})
}

func TestAuthenticator(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		tempDir := mockSecretsDir()

		Convey("Check NewAuthenticator defaults", func() {
			clientID, clientSecret := DefaultClient()
			ctx := context.Background()
			a := NewAuthenticator(Options{Context: ctx}).(*authenticatorImpl)
			So(a.opts, ShouldResemble, &Options{
				Method:                 AutoSelectMethod,
				Scopes:                 []string{OAuthScopeEmail},
				ClientID:               clientID,
				ClientSecret:           clientSecret,
				ServiceAccountJSONPath: filepath.Join(tempDir, "service_account.json"),
				GCEAccountName:         "default",
				Context:                ctx,
				Logger:                 logging.Get(ctx),
			})
		})
	})
}

func TestAuthenticatedClient(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		var tokenProvider internal.TokenProvider

		mockSecretsDir()
		mockTokenProvider(func() internal.TokenProvider { return tokenProvider })

		Convey("Test login required", func() {
			tokenProvider = &fakeTokenProvider{interactive: true}
			c, err := AuthenticatedClient(InteractiveLogin, NewAuthenticator(Options{}))
			So(err, ShouldBeNil)
			So(c, ShouldNotEqual, http.DefaultClient)
		})

		Convey("Test login not required", func() {
			tokenProvider = &fakeTokenProvider{interactive: true}
			c, err := AuthenticatedClient(OptionalLogin, NewAuthenticator(Options{}))
			So(err, ShouldBeNil)
			So(c, ShouldEqual, http.DefaultClient)
		})
	})
}

func TestRefreshToken(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		var tokenProvider *fakeTokenProvider

		mockSecretsDir()
		mockTokenProvider(func() internal.TokenProvider { return tokenProvider })

		Convey("Test non interactive auth", func() {
			tokenProvider = &fakeTokenProvider{
				interactive: false,
				tokenToMint: &fakeToken{},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldBeNil)
			// No token yet. The token is minted on first refresh.
			So(auth.currentToken(), ShouldBeNil)
			tok, err := auth.refreshToken(nil)
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToMint)
		})

		Convey("Test interactive auth (cache expired)", func() {
			tokenProvider = &fakeTokenProvider{
				interactive:      true,
				tokenToMint:      &fakeToken{name: "minted"},
				tokenToRefresh:   &fakeToken{name: "refreshed"},
				tokenToUnmarshal: &fakeToken{name: "cached", expired: true},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldEqual, ErrLoginRequired)
			err = auth.Login()
			So(err, ShouldBeNil)
			_, err = auth.Transport()
			So(err, ShouldBeNil)
			// Minted initial token.
			So(auth.currentToken(), ShouldEqual, tokenProvider.tokenToMint)
			// Should return refreshed token.
			tok, err := auth.refreshToken(auth.currentToken())
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToRefresh)
		})

		Convey("Test interactive auth (cache non expired)", func() {
			tokenProvider = &fakeTokenProvider{
				interactive:      true,
				tokenToMint:      &fakeToken{name: "minted"},
				tokenToRefresh:   &fakeToken{name: "refreshed"},
				tokenToUnmarshal: &fakeToken{name: "cached", expired: false},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldEqual, ErrLoginRequired)
			err = auth.Login()
			So(err, ShouldBeNil)
			_, err = auth.Transport()
			So(err, ShouldBeNil)
			// Minted initial token.
			So(auth.currentToken(), ShouldEqual, tokenProvider.tokenToMint)
			// Should return token from cache (since it's not expired yet).
			tok, err := auth.refreshToken(auth.currentToken())
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToUnmarshal)
		})
	})
}

////////////////////////////////////////////////////////////////////////////////

type fakeTokenProvider struct {
	interactive      bool
	tokenToMint      internal.Token
	tokenToRefresh   internal.Token
	tokenToUnmarshal internal.Token
}

func (p *fakeTokenProvider) RequiresInteraction() bool {
	return p.interactive
}

func (p *fakeTokenProvider) MintToken() (internal.Token, error) {
	if p.tokenToMint != nil {
		return p.tokenToMint, nil
	}
	return &fakeToken{}, nil
}

func (p *fakeTokenProvider) RefreshToken(internal.Token) (internal.Token, error) {
	if p.tokenToRefresh != nil {
		return p.tokenToRefresh, nil
	}
	return &fakeToken{}, nil
}

func (p *fakeTokenProvider) MarshalToken(internal.Token) ([]byte, error) {
	return []byte("fake token"), nil
}

func (p *fakeTokenProvider) UnmarshalToken([]byte) (internal.Token, error) {
	if p.tokenToUnmarshal != nil {
		return p.tokenToUnmarshal, nil
	}
	return &fakeToken{}, nil
}

type fakeToken struct {
	name    string
	expired bool
}

func (t *fakeToken) Equals(another internal.Token) bool {
	casted, ok := another.(*fakeToken)
	return ok && casted == t
}

func (t *fakeToken) RequestHeaders() map[string]string { return make(map[string]string) }
func (t *fakeToken) Expired() bool                     { return t.expired }
<commit_msg>Do not ignore error in RemoveAll when cleaning up after the test.<commit_after>// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package auth

import (
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"testing"

	"golang.org/x/net/context"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/luci/luci-go/common/auth/internal"
	"github.com/luci/luci-go/common/logging"
)

var (
	ctx = context.Background()
	log = logging.Null()
)

func ExampleDefaultAuthenticatedClient() {
	client, err := AuthenticatedClient(SilentLogin, NewAuthenticator(Options{}))
	if err == ErrLoginRequired {
		log.Errorf("Run 'auth login' to login")
		return
	}
	if err != nil {
		log.Errorf("Failed to login: %s", err)
		return
	}
	_, _ = client.Get("https://some-server.appspot.com")
}

func mockSecretsDir() string {
	tempDir, err := ioutil.TempDir("", "auth_test")
	So(err, ShouldBeNil)

	prev := secretsDir
	secretsDir = func() string { return tempDir }
	Reset(func() {
		secretsDir = prev
		So(os.RemoveAll(tempDir), ShouldBeNil)
	})
	So(SecretsDir(), ShouldEqual, tempDir)

	return tempDir
}

func mockTokenProvider(factory func() internal.TokenProvider) {
	prev := makeTokenProvider
	makeTokenProvider = func(*Options) (internal.TokenProvider, error) {
		return factory(), nil
	}
	Reset(func() {
		makeTokenProvider = prev
	})
}

func TestAuthenticator(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		tempDir := mockSecretsDir()

		Convey("Check NewAuthenticator defaults", func() {
			clientID, clientSecret := DefaultClient()
			ctx := context.Background()
			a := NewAuthenticator(Options{Context: ctx}).(*authenticatorImpl)
			So(a.opts, ShouldResemble, &Options{
				Method:                 AutoSelectMethod,
				Scopes:                 []string{OAuthScopeEmail},
				ClientID:               clientID,
				ClientSecret:           clientSecret,
				ServiceAccountJSONPath: filepath.Join(tempDir, "service_account.json"),
				GCEAccountName:         "default",
				Context:                ctx,
				Logger:                 logging.Get(ctx),
			})
		})
	})
}

func TestAuthenticatedClient(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		var tokenProvider internal.TokenProvider

		mockSecretsDir()
		mockTokenProvider(func() internal.TokenProvider { return tokenProvider })

		Convey("Test login required", func() {
			tokenProvider = &fakeTokenProvider{interactive: true}
			c, err := AuthenticatedClient(InteractiveLogin, NewAuthenticator(Options{}))
			So(err, ShouldBeNil)
			So(c, ShouldNotEqual, http.DefaultClient)
		})

		Convey("Test login not required", func() {
			tokenProvider = &fakeTokenProvider{interactive: true}
			c, err := AuthenticatedClient(OptionalLogin, NewAuthenticator(Options{}))
			So(err, ShouldBeNil)
			So(c, ShouldEqual, http.DefaultClient)
		})
	})
}

func TestRefreshToken(t *testing.T) {
	Convey("Given mocked secrets dir", t, func() {
		var tokenProvider *fakeTokenProvider

		mockSecretsDir()
		mockTokenProvider(func() internal.TokenProvider { return tokenProvider })

		Convey("Test non interactive auth", func() {
			tokenProvider = &fakeTokenProvider{
				interactive: false,
				tokenToMint: &fakeToken{},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldBeNil)
			// No token yet. The token is minted on first refresh.
			So(auth.currentToken(), ShouldBeNil)
			tok, err := auth.refreshToken(nil)
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToMint)
		})

		Convey("Test interactive auth (cache expired)", func() {
			tokenProvider = &fakeTokenProvider{
				interactive:      true,
				tokenToMint:      &fakeToken{name: "minted"},
				tokenToRefresh:   &fakeToken{name: "refreshed"},
				tokenToUnmarshal: &fakeToken{name: "cached", expired: true},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldEqual, ErrLoginRequired)
			err = auth.Login()
			So(err, ShouldBeNil)
			_, err = auth.Transport()
			So(err, ShouldBeNil)
			// Minted initial token.
			So(auth.currentToken(), ShouldEqual, tokenProvider.tokenToMint)
			// Should return refreshed token.
			tok, err := auth.refreshToken(auth.currentToken())
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToRefresh)
		})

		Convey("Test interactive auth (cache non expired)", func() {
			tokenProvider = &fakeTokenProvider{
				interactive:      true,
				tokenToMint:      &fakeToken{name: "minted"},
				tokenToRefresh:   &fakeToken{name: "refreshed"},
				tokenToUnmarshal: &fakeToken{name: "cached", expired: false},
			}
			auth, ok := NewAuthenticator(Options{}).(*authenticatorImpl)
			So(ok, ShouldBeTrue)
			_, err := auth.Transport()
			So(err, ShouldEqual, ErrLoginRequired)
			err = auth.Login()
			So(err, ShouldBeNil)
			_, err = auth.Transport()
			So(err, ShouldBeNil)
			// Minted initial token.
			So(auth.currentToken(), ShouldEqual, tokenProvider.tokenToMint)
			// Should return token from cache (since it's not expired yet).
			tok, err := auth.refreshToken(auth.currentToken())
			So(err, ShouldBeNil)
			So(tok, ShouldEqual, tokenProvider.tokenToUnmarshal)
		})
	})
}

////////////////////////////////////////////////////////////////////////////////

type fakeTokenProvider struct {
	interactive      bool
	tokenToMint      internal.Token
	tokenToRefresh   internal.Token
	tokenToUnmarshal internal.Token
}

func (p *fakeTokenProvider) RequiresInteraction() bool {
	return p.interactive
}

func (p *fakeTokenProvider) MintToken() (internal.Token, error) {
	if p.tokenToMint != nil {
		return p.tokenToMint, nil
	}
	return &fakeToken{}, nil
}

func (p *fakeTokenProvider) RefreshToken(internal.Token) (internal.Token, error) {
	if p.tokenToRefresh != nil {
		return p.tokenToRefresh, nil
	}
	return &fakeToken{}, nil
}

func (p *fakeTokenProvider) MarshalToken(internal.Token) ([]byte, error) {
	return []byte("fake token"), nil
}

func (p *fakeTokenProvider) UnmarshalToken([]byte) (internal.Token, error) {
	if p.tokenToUnmarshal != nil {
		return p.tokenToUnmarshal, nil
	}
	return &fakeToken{}, nil
}

type fakeToken struct {
	name    string
	expired bool
}

func (t *fakeToken) Equals(another internal.Token) bool {
	casted, ok := another.(*fakeToken)
	return ok && casted == t
}

func (t *fakeToken) RequestHeaders() map[string]string { return make(map[string]string) }
func (t *fakeToken) Expired() bool                     { return t.expired }
<|endoftext|>


<commit_before>package h2spec

import (
	"github.com/bradfitz/http2"
	"io"
	"time"
)

func TestErrorHandling(ctx *Context) {
	PrintHeader("5.4. Error Handling", 0)
	TestConnectionErrorHandling(ctx)
	PrintFooter()
}

func TestConnectionErrorHandling(ctx *Context) {
	PrintHeader("5.4.1. Connection Error Handling", 1)

	func(ctx *Context) {
		desc := "Receives a GOAWAY frame"
		msg := "After sending the GOAWAY frame, the endpoint MUST close the TCP connection."
		gfResult := false
		closeResult := false

		http2Conn := CreateHttp2Conn(ctx, true)
		defer http2Conn.conn.Close()

		http2Conn.fr.WriteData(1, true, []byte("test"))
		timeCh := time.After(3 * time.Second)

	loop:
		for {
			select {
			case f := <-http2Conn.dataCh:
				gf, ok := f.(*http2.GoAwayFrame)
				if ok {
					if gf.ErrCode == http2.ErrCodeProtocol {
						gfResult = false
					}
				}
			case err := <-http2Conn.errCh:
				if err == io.EOF {
					closeResult = true
				}
				break loop
			case <-timeCh:
				break loop
			}
		}

		PrintResult(gfResult && closeResult, desc, msg, 1)
	}(ctx)
}
<commit_msg>Fix debug code<commit_after>package h2spec

import (
	"github.com/bradfitz/http2"
	"io"
	"time"
)

func TestErrorHandling(ctx *Context) {
	PrintHeader("5.4. Error Handling", 0)
	TestConnectionErrorHandling(ctx)
	PrintFooter()
}

func TestConnectionErrorHandling(ctx *Context) {
	PrintHeader("5.4.1. Connection Error Handling", 1)

	func(ctx *Context) {
		desc := "Receives a GOAWAY frame"
		msg := "After sending the GOAWAY frame, the endpoint MUST close the TCP connection."
		gfResult := false
		closeResult := false

		http2Conn := CreateHttp2Conn(ctx, true)
		defer http2Conn.conn.Close()

		http2Conn.fr.WriteData(1, true, []byte("test"))
		timeCh := time.After(3 * time.Second)

	loop:
		for {
			select {
			case f := <-http2Conn.dataCh:
				gf, ok := f.(*http2.GoAwayFrame)
				if ok {
					if gf.ErrCode == http2.ErrCodeProtocol {
						gfResult = true
					}
				}
			case err := <-http2Conn.errCh:
				if err == io.EOF {
					closeResult = true
				}
				break loop
			case <-timeCh:
				break loop
			}
		}

		PrintResult(gfResult && closeResult, desc, msg, 1)
	}(ctx)
}
<|endoftext|>


<commit_before>/*
Copyright 2014 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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1"  // major version, always numeric
	gitMinor string = "6+" // minor version, numeric possibly followed by "+"

	// semantic version, derived by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.6.0-beta.4+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"

	buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
<commit_msg>update-all.sh.<commit_after>/*
Copyright 2014 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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1"  // major version, always numeric
	gitMinor string = "6+" // minor version, numeric possibly followed by "+"

	// semantic version, derived by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.6.0-rc.1+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"

	buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
<|endoftext|>


<commit_before>package sleekhtml

import (
	"bytes"
	"io"
	"strings"

	"code.google.com/p/go.net/html"
)

var emptyByte = []byte("")
var nbsp = []byte(" ")

// Sanitize - sanitizes & grooms HTML from unnecessary space & tags
func Sanitize(r io.Reader, tags *Tags) ([]byte, error) {
	if tags == nil {
		tags = NewTags()
	}
	return process(html.NewTokenizer(r), tags)
}

func process(tokenizer *html.Tokenizer, tags *Tags) ([]byte, error) {
	var buffer bytes.Buffer
	var ignoredTag = false

	for {
		tt := tokenizer.Next() // TokenType
		if tt == html.ErrorToken {
			return buffer.Bytes(), nil
		}

		switch tt {
		case html.DoctypeToken:
			buffer.Write(trimSpace(tokenizer.Raw()))

		case html.CommentToken: // Ignore HTML comments except IE
			if tags.AllowIEComments && bytes.HasPrefix(tokenizer.Raw(), []byte("<!--[if")) {
				buffer.Write(trimSpace(tokenizer.Raw()))
			}
			break

		case html.StartTagToken, html.SelfClosingTagToken:
			token := tokenizer.Token()
			if tags.IsIgnoredHTMLTag(token.DataAtom) {
				ignoredTag = true
				break
			}

			if len(token.Attr) > 0 {
				tokenAttr := token.Attr
				token.Attr = nil
				for _, attr := range tokenAttr {
					if tags.IsAllowedAttribute(attr.Key) {
						token.Attr = append(token.Attr, attr)
					}
				}
			}
			buffer.WriteString(strings.TrimSpace(token.String()))
		case html.TextToken:
			if ignoredTag {
				break
			}
			buffer.Write(trimText(tokenizer.Raw()))

		case html.EndTagToken:
			if ignoredTag {
				ignoredTag = false
				break
			}
			buffer.Write(trimSpace(tokenizer.Raw()))
		default:
			buffer.Write(trimSpace(tokenizer.Raw()))
		}
	}
}

func trimSpace(b []byte) []byte {
	return bytes.TrimSpace(b)
}

func trimText(b []byte) []byte {
	return trimSpace(bytes.Replace(b, nbsp, emptyByte, -1))
}
<commit_msg>Expose Process function into public, add Filter Token func<commit_after>package sleekhtml

import (
	"bytes"
	"io"
	"strings"

	"golang.org/x/net/html"
)

// FilterTokenFunc - callback function process token
type FilterTokenFunc func(t *html.Token)

var emptyByte = []byte("")
var nbsp = []byte(" ")

// Sanitize - sanitizes & grooms HTML from unnecessary space & tags
func Sanitize(r io.Reader, tags *Tags) ([]byte, error) {
	if tags == nil {
		tags = NewTags()
	}
	return Process(html.NewTokenizer(r), tags, nil)
}

// Process -
func Process(parser *html.Tokenizer, tags *Tags, tokenFilter FilterTokenFunc) ([]byte, error) {
	var buffer bytes.Buffer
	var ignoredTag = false

	for {
		tt := parser.Next() // TokenType
		if tt == html.ErrorToken {
			return buffer.Bytes(), nil
		}

		switch tt {
		case html.DoctypeToken:
			buffer.Write(trimSpace(parser.Raw()))

		case html.CommentToken: // Ignore HTML comments except IE
			if tags.AllowIEComments && bytes.HasPrefix(parser.Raw(), []byte("<!--[if")) {
				buffer.Write(trimSpace(parser.Raw()))
			}
			break

		case html.StartTagToken, html.SelfClosingTagToken:
			token := parser.Token()
			if tags.IsIgnoredHTMLTag(token.DataAtom) {
				ignoredTag = true
				break
			}

			if tokenFilter != nil {
				tokenFilter(&token)
			}

			if len(token.Attr) > 0 {
				tokenAttr := token.Attr
				token.Attr = nil
				for _, attr := range tokenAttr {
					if tags.IsAllowedAttribute(attr.Key) {
						token.Attr = append(token.Attr, attr)
					}
				}
			}
			buffer.WriteString(strings.TrimSpace(token.String()))
		case html.TextToken:
			if ignoredTag {
				break
			}
			buffer.Write(trimText(parser.Raw()))

		case html.EndTagToken:
			if ignoredTag {
				ignoredTag = false
				break
			}
			buffer.Write(trimSpace(parser.Raw()))
		default:
			buffer.Write(trimSpace(parser.Raw()))
		}
	}
}

func trimSpace(b []byte) []byte {
	return bytes.TrimSpace(b)
}

func trimText(b []byte) []byte {
	return trimSpace(bytes.Replace(b, nbsp, emptyByte, -1))
}
<|endoftext|>


<commit_before>package main

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

	"github.com/litl/galaxy/log"
)

var (
	httpRouter *HostRouter
)

type RequestLogger struct{}

// This works along with the ServiceRegistry, and the individual Services to
// route http requests based on the Host header. The Resgistry hold the mapping
// of VHost names to individual services, and each service has it's own
// ReeverseProxy to fulfill the request.
// HostRouter contains the ReverseProxy http Listener, and has an http.Handler
// to service the requets.
type HostRouter struct {
	sync.Mutex
	// the http frontend
	server *http.Server

	// track our listener so we can kill the server
	listener net.Listener
}

func NewHostRouter() *HostRouter {
	return &HostRouter{}
}

func (r *HostRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	reqId := genId()
	req.Header.Set("X-Request-Id", reqId)
	defer func(start time.Time) {
		log.Printf("id=%s total_duration=%s", reqId, time.Since(start))
	}(time.Now())

	var err error
	host := req.Host
	if strings.Contains(host, ":") {
		host, _, err = net.SplitHostPort(req.Host)
		if err != nil {
			log.Warnf("%s", err)
		}
	}

	svc := Registry.GetVHostService(host)

	if svc != nil && svc.httpProxy != nil {
		// The vhost has a service registered, give it to the proxy
		svc.ServeHTTP(w, req)
		return
	}

	r.adminHandler(w, req)
}

func (r *HostRouter) adminHandler(w http.ResponseWriter, req *http.Request) {
	r.Lock()
	defer r.Unlock()

	if Registry.VHostsLen() == 0 {
		http.Error(w, "no backends available", http.StatusServiceUnavailable)
		return
	}

	// TODO: better status lines
	stats := Registry.Stats()
	for _, svc := range stats {
		if len(svc.VirtualHosts) == 0 {
			continue
		}
		fmt.Fprintf(w, "%v\n", svc.VirtualHosts)
		for _, b := range svc.Backends {
			js, _ := json.Marshal(b)
			fmt.Fprintf(w, "\t%s\n", string(js))
		}
	}

	fmt.Fprintf(w, "\n")
	return
}

// TODO: collect more stats?

// Start the HTTP Router frontend.
// Takes a channel to notify when the listener is started
// to safely synchronize tests.
func (r *HostRouter) Start(ready chan bool) {
	//FIXME: poor locking strategy
	r.Lock()

	log.Printf("HTTP server listening at %s", listenAddr)

	// Proxy acts as http handler:
	// These timeouts for for overall request duration. They don't effect
	// keepalive, but will close an overly slow request.
	r.server = &http.Server{
		Addr:           listenAddr,
		Handler:        r,
		ReadTimeout:    10 * time.Minute,
		WriteTimeout:   10 * time.Minute,
		MaxHeaderBytes: 1 << 20,
	}

	var err error

	// These timeouts are for each individual Read/Write operation
	// These will close keepalive connections too.
	// TODO: configure timeout somewhere
	r.listener, err = newTimeoutListener(listenAddr, 120*time.Second)
	if err != nil {
		log.Errorf("%s", err)
		r.Unlock()
		return
	}

	r.Unlock()
	if ready != nil {
		close(ready)
	}

	// This will log a closed connection error every time we Stop
	// but that's mostly a testing issue.
	log.Errorf("%s", r.server.Serve(r.listener))
}

func (r *HostRouter) Stop() {
	r.listener.Close()
}

func startHTTPServer() {
	//FIXME: this global wg?
	defer wg.Done()
	httpRouter = NewHostRouter()
	httpRouter.Start(nil)
}

func sslRedirect(pr *ProxyRequest) bool {
	if sslOnly && pr.Request.Header.Get("X-Forwarded-Proto") != "https" {
		//TODO: verify RequestURI
		redirLoc := "https://" + pr.Request.Host + pr.Request.RequestURI
		http.Redirect(pr.ResponseWriter, pr.Request, redirLoc, http.StatusMovedPermanently)
		return false
	}

	return true
}

type ErrorPage struct {
	// The Mutex protects access to the body slice, and headers
	// Everything else should be static once the ErrorPage is created.
	sync.Mutex

	Location    string
	StatusCodes []int

	// body contains the cached error page
	body []byte
	// important headers
	header http.Header
}

func (e *ErrorPage) Body() []byte {
	e.Lock()
	defer e.Unlock()
	return e.body
}

func (e *ErrorPage) SetBody(b []byte) {
	e.Lock()
	defer e.Unlock()
	e.body = b
}

func (e *ErrorPage) Header() http.Header {
	e.Lock()
	defer e.Unlock()
	return e.header
}

func (e *ErrorPage) SetHeader(h http.Header) {
	e.Lock()
	defer e.Unlock()
	e.header = h
}

// List of headers we want to cache for ErrorPages
var ErrorHeaders = []string{
	"Content-Type",
	"Content-Encoding",
	"Cache-Control",
	"Last-Modified",
	"Retry-After",
	"Set-Cookie",
}

// ErrorResponse provides a ReverProxy callback to process a response and
// insert custom error pages for a virtual host.
type ErrorResponse struct {
	sync.Mutex

	// map them by status for responses
	pages map[int]*ErrorPage

	// keep this handy to refresh the pages
	client *http.Client
}

func NewErrorResponse(pages map[string][]int) *ErrorResponse {
	errors := &ErrorResponse{
		pages: make(map[int]*ErrorPage),
	}

	// aggressively timeout connections
	errors.client = &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 2 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 2 * time.Second,
		},
		Timeout: 5 * time.Second,
	}

	if pages != nil {
		errors.Update(pages)
	}
	return errors
}

// Get the ErrorPage, returning nil if the page was incomplete.
// We permanently cache error pages and headers once we've seen them.
func (e *ErrorResponse) Get(code int) *ErrorPage {
	e.Lock()
	page, ok := e.pages[code]
	e.Unlock()

	if !ok {
		// this is a code we don't handle
		return nil
	}

	body := page.Body()
	if body != nil {
		return page
	}

	// we haven't successfully fetched this error
	e.fetch(page)
	return page
}

func (e *ErrorResponse) fetch(page *ErrorPage) {
	log.Debugf("Fetching error page from %s", page.Location)
	resp, err := e.client.Get(page.Location)
	if err != nil {
		log.Warnf("Could not fetch %s: %s", page.Location, err.Error())
		return
	}
	defer resp.Body.Close()

	// If the StatusCode matches any of our registered codes, it's OK
	for _, code := range page.StatusCodes {
		if resp.StatusCode == code {
			resp.StatusCode = http.StatusOK
			break
		}
	}

	if resp.StatusCode != http.StatusOK {
		log.Warnf("Server returned %d when fetching %s", resp.StatusCode, page.Location)
		return
	}

	header := make(map[string][]string)
	for _, key := range ErrorHeaders {
		if hdr, ok := resp.Header[key]; ok {
			header[key] = hdr
		}
	}
	// set the headers along with the body below

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Warnf("Error reading response from %s: %s", page.Location, err.Error())
		return
	}

	if len(body) > 0 {
		page.SetHeader(header)
		page.SetBody(body)
		return
	}
	log.Warnf("Empty response from %s", page.Location)
}

// This replaces all existing ErrorPages
func (e *ErrorResponse) Update(pages map[string][]int) {
	e.Lock()
	defer e.Unlock()

	e.pages = make(map[int]*ErrorPage)

	for loc, codes := range pages {
		page := &ErrorPage{
			StatusCodes: codes,
			Location:    loc,
		}

		for _, code := range codes {
			e.pages[code] = page
		}
		go e.fetch(page)
	}
}

func (e *ErrorResponse) CheckResponse(pr *ProxyRequest) bool {

	errPage := e.Get(pr.Response.StatusCode)
	if errPage != nil {
		// load the cached headers
		header := pr.ResponseWriter.Header()
		for key, val := range errPage.Header() {
			header[key] = val
		}

		pr.ResponseWriter.WriteHeader(pr.Response.StatusCode)
		pr.ResponseWriter.Write(errPage.Body())
		return false
	}

	return true
}

func logProxyRequest(pr *ProxyRequest) bool {
	// TODO: we may to be able to switch this off
	if pr == nil || pr.Request == nil {
		return true
	}

	var id, method, clientIP, url, backend, agent string
	var status int

	duration := pr.FinishTime.Sub(pr.StartTime)

	id = pr.Request.Header.Get("X-Request-Id")
	method = pr.Request.Method
	url = pr.Request.Host + pr.Request.RequestURI
	agent = pr.Request.UserAgent()
	status = pr.Response.StatusCode

	clientIP = pr.Request.Header.Get("X-Forwarded-For")
	if clientIP == "" {
		clientIP = pr.Request.RemoteAddr
	}

	if pr.Response != nil && pr.Response.Request != nil && pr.Response.Request.URL != nil {
		backend = pr.Response.Request.URL.Host
	}

	err := fmt.Sprintf("%v", pr.ProxyError)

	fmtStr := "id=%s method=%s clientIp=%s url=%s backend=%s status=%d duration=%s agent=%s, err=%s"

	log.Printf(fmtStr, id, method, clientIP, url, backend, status, duration, agent, err)
	return true
}
<commit_msg>shuttle: Remove duplicate duration logging<commit_after>package main

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

	"github.com/litl/galaxy/log"
)

var (
	httpRouter *HostRouter
)

type RequestLogger struct{}

// This works along with the ServiceRegistry, and the individual Services to
// route http requests based on the Host header. The Resgistry hold the mapping
// of VHost names to individual services, and each service has it's own
// ReeverseProxy to fulfill the request.
// HostRouter contains the ReverseProxy http Listener, and has an http.Handler
// to service the requets.
type HostRouter struct {
	sync.Mutex
	// the http frontend
	server *http.Server

	// track our listener so we can kill the server
	listener net.Listener
}

func NewHostRouter() *HostRouter {
	return &HostRouter{}
}

func (r *HostRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	reqId := genId()
	req.Header.Set("X-Request-Id", reqId)

	var err error
	host := req.Host
	if strings.Contains(host, ":") {
		host, _, err = net.SplitHostPort(req.Host)
		if err != nil {
			log.Warnf("%s", err)
		}
	}

	svc := Registry.GetVHostService(host)

	if svc != nil && svc.httpProxy != nil {
		// The vhost has a service registered, give it to the proxy
		svc.ServeHTTP(w, req)
		return
	}

	r.adminHandler(w, req)
}

func (r *HostRouter) adminHandler(w http.ResponseWriter, req *http.Request) {
	r.Lock()
	defer r.Unlock()

	if Registry.VHostsLen() == 0 {
		http.Error(w, "no backends available", http.StatusServiceUnavailable)
		return
	}

	// TODO: better status lines
	stats := Registry.Stats()
	for _, svc := range stats {
		if len(svc.VirtualHosts) == 0 {
			continue
		}
		fmt.Fprintf(w, "%v\n", svc.VirtualHosts)
		for _, b := range svc.Backends {
			js, _ := json.Marshal(b)
			fmt.Fprintf(w, "\t%s\n", string(js))
		}
	}

	fmt.Fprintf(w, "\n")
	return
}

// TODO: collect more stats?

// Start the HTTP Router frontend.
// Takes a channel to notify when the listener is started
// to safely synchronize tests.
func (r *HostRouter) Start(ready chan bool) {
	//FIXME: poor locking strategy
	r.Lock()

	log.Printf("HTTP server listening at %s", listenAddr)

	// Proxy acts as http handler:
	// These timeouts for for overall request duration. They don't effect
	// keepalive, but will close an overly slow request.
	r.server = &http.Server{
		Addr:           listenAddr,
		Handler:        r,
		ReadTimeout:    10 * time.Minute,
		WriteTimeout:   10 * time.Minute,
		MaxHeaderBytes: 1 << 20,
	}

	var err error

	// These timeouts are for each individual Read/Write operation
	// These will close keepalive connections too.
	// TODO: configure timeout somewhere
	r.listener, err = newTimeoutListener(listenAddr, 120*time.Second)
	if err != nil {
		log.Errorf("%s", err)
		r.Unlock()
		return
	}

	r.Unlock()
	if ready != nil {
		close(ready)
	}

	// This will log a closed connection error every time we Stop
	// but that's mostly a testing issue.
	log.Errorf("%s", r.server.Serve(r.listener))
}

func (r *HostRouter) Stop() {
	r.listener.Close()
}

func startHTTPServer() {
	//FIXME: this global wg?
	defer wg.Done()
	httpRouter = NewHostRouter()
	httpRouter.Start(nil)
}

func sslRedirect(pr *ProxyRequest) bool {
	if sslOnly && pr.Request.Header.Get("X-Forwarded-Proto") != "https" {
		//TODO: verify RequestURI
		redirLoc := "https://" + pr.Request.Host + pr.Request.RequestURI
		http.Redirect(pr.ResponseWriter, pr.Request, redirLoc, http.StatusMovedPermanently)
		return false
	}

	return true
}

type ErrorPage struct {
	// The Mutex protects access to the body slice, and headers
	// Everything else should be static once the ErrorPage is created.
	sync.Mutex

	Location    string
	StatusCodes []int

	// body contains the cached error page
	body []byte
	// important headers
	header http.Header
}

func (e *ErrorPage) Body() []byte {
	e.Lock()
	defer e.Unlock()
	return e.body
}

func (e *ErrorPage) SetBody(b []byte) {
	e.Lock()
	defer e.Unlock()
	e.body = b
}

func (e *ErrorPage) Header() http.Header {
	e.Lock()
	defer e.Unlock()
	return e.header
}

func (e *ErrorPage) SetHeader(h http.Header) {
	e.Lock()
	defer e.Unlock()
	e.header = h
}

// List of headers we want to cache for ErrorPages
var ErrorHeaders = []string{
	"Content-Type",
	"Content-Encoding",
	"Cache-Control",
	"Last-Modified",
	"Retry-After",
	"Set-Cookie",
}

// ErrorResponse provides a ReverProxy callback to process a response and
// insert custom error pages for a virtual host.
type ErrorResponse struct {
	sync.Mutex

	// map them by status for responses
	pages map[int]*ErrorPage

	// keep this handy to refresh the pages
	client *http.Client
}

func NewErrorResponse(pages map[string][]int) *ErrorResponse {
	errors := &ErrorResponse{
		pages: make(map[int]*ErrorPage),
	}

	// aggressively timeout connections
	errors.client = &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 2 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 2 * time.Second,
		},
		Timeout: 5 * time.Second,
	}

	if pages != nil {
		errors.Update(pages)
	}
	return errors
}

// Get the ErrorPage, returning nil if the page was incomplete.
// We permanently cache error pages and headers once we've seen them.
func (e *ErrorResponse) Get(code int) *ErrorPage {
	e.Lock()
	page, ok := e.pages[code]
	e.Unlock()

	if !ok {
		// this is a code we don't handle
		return nil
	}

	body := page.Body()
	if body != nil {
		return page
	}

	// we haven't successfully fetched this error
	e.fetch(page)
	return page
}

func (e *ErrorResponse) fetch(page *ErrorPage) {
	log.Debugf("Fetching error page from %s", page.Location)
	resp, err := e.client.Get(page.Location)
	if err != nil {
		log.Warnf("Could not fetch %s: %s", page.Location, err.Error())
		return
	}
	defer resp.Body.Close()

	// If the StatusCode matches any of our registered codes, it's OK
	for _, code := range page.StatusCodes {
		if resp.StatusCode == code {
			resp.StatusCode = http.StatusOK
			break
		}
	}

	if resp.StatusCode != http.StatusOK {
		log.Warnf("Server returned %d when fetching %s", resp.StatusCode, page.Location)
		return
	}

	header := make(map[string][]string)
	for _, key := range ErrorHeaders {
		if hdr, ok := resp.Header[key]; ok {
			header[key] = hdr
		}
	}
	// set the headers along with the body below

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Warnf("Error reading response from %s: %s", page.Location, err.Error())
		return
	}

	if len(body) > 0 {
		page.SetHeader(header)
		page.SetBody(body)
		return
	}
	log.Warnf("Empty response from %s", page.Location)
}

// This replaces all existing ErrorPages
func (e *ErrorResponse) Update(pages map[string][]int) {
	e.Lock()
	defer e.Unlock()

	e.pages = make(map[int]*ErrorPage)

	for loc, codes := range pages {
		page := &ErrorPage{
			StatusCodes: codes,
			Location:    loc,
		}

		for _, code := range codes {
			e.pages[code] = page
		}
		go e.fetch(page)
	}
}

func (e *ErrorResponse) CheckResponse(pr *ProxyRequest) bool {

	errPage := e.Get(pr.Response.StatusCode)
	if errPage != nil {
		// load the cached headers
		header := pr.ResponseWriter.Header()
		for key, val := range errPage.Header() {
			header[key] = val
		}

		pr.ResponseWriter.WriteHeader(pr.Response.StatusCode)
		pr.ResponseWriter.Write(errPage.Body())
		return false
	}

	return true
}

func logProxyRequest(pr *ProxyRequest) bool {
	// TODO: we may to be able to switch this off
	if pr == nil || pr.Request == nil {
		return true
	}

	var id, method, clientIP, url, backend, agent string
	var status int

	duration := pr.FinishTime.Sub(pr.StartTime)

	id = pr.Request.Header.Get("X-Request-Id")
	method = pr.Request.Method
	url = pr.Request.Host + pr.Request.RequestURI
	agent = pr.Request.UserAgent()
	status = pr.Response.StatusCode

	clientIP = pr.Request.Header.Get("X-Forwarded-For")
	if clientIP == "" {
		clientIP = pr.Request.RemoteAddr
	}

	if pr.Response != nil && pr.Response.Request != nil && pr.Response.Request.URL != nil {
		backend = pr.Response.Request.URL.Host
	}

	err := fmt.Sprintf("%v", pr.ProxyError)

	fmtStr := "id=%s method=%s clientIp=%s url=%s backend=%s status=%d duration=%s agent=%s, err=%s"

	log.Printf(fmtStr, id, method, clientIP, url, backend, status, duration, agent, err)
	return true
}
<|endoftext|>


<commit_before>package cmd_test

import (
	"bufio"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var buildPath string

var _ = SynchronizedBeforeSuite(func() []byte {
	path, buildErr := Build("code.cloudfoundry.org/cli")
	Expect(buildErr).NotTo(HaveOccurred())
	return []byte(path)
}, func(data []byte) {
	buildPath = string(data)
})

// gexec.Build leaves a compiled binary behind in /tmp.
var _ = SynchronizedAfterSuite(func() {}, func() {
	CleanupBuildArtifacts()
})

var _ = Describe("main", func() {
	var (
		old_PLUGINS_HOME string
	)

	BeforeEach(func() {
		old_PLUGINS_HOME = os.Getenv("CF_PLUGIN_HOME")

		dir, err := os.Getwd()
		Expect(err).NotTo(HaveOccurred())

		fullDir := filepath.Join(dir, "..", "..", "fixtures", "config", "main-plugin-test-config")
		err = os.Setenv("CF_PLUGIN_HOME", fullDir)
		Expect(err).NotTo(HaveOccurred())
	})

	AfterEach(func() {
		err := os.Setenv("CF_PLUGIN_HOME", old_PLUGINS_HOME)
		Expect(err).NotTo(HaveOccurred())
	})

	Describe("Help menu with -h/--help", func() {
		It("prints the help output with our custom template when run with 'cf -h'", func() {
			output := Cf("-h")
			Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
			Eventually(output.Out.Contents).Should(ContainSubstring("CF_TRACE=true"))
		})

		It("prints the help output with our custom template when run with 'cf --help'", func() {
			output := Cf("--help")
			Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
			Eventually(output.Out.Contents).Should(ContainSubstring("CF_TRACE=true"))
		})

		It("accepts -h and --h flags for all commands", func() {
			result := Cf("push", "-h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))

			result = Cf("push", "--no-route", "-h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))

			result = Cf("target", "--h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))
		})

		It("accepts -h before the command name", func() {
			result := Cf("-h", "push", "--no-route")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Consistently(result.Out).ShouldNot(Say("Start an app"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))
		})
	})

	Describe("Shows version with -v or --version", func() {
		It("prints the cf version if '-v' flag is provided", func() {
			output := Cf("-v")
			Eventually(output.Out.Contents).Should(ContainSubstring("cf version"))
			Eventually(output).Should(Exit(0))
		})

		It("prints the cf version if '--version' flag is provided", func() {
			output := Cf("--version")
			Eventually(output.Out.Contents).Should(ContainSubstring("cf version"))
			Eventually(output).Should(Exit(0))
		})
	})

	Describe("Enables verbose output with -v", func() {
		BeforeEach(func() {
			client := http.Client{Timeout: 3 * time.Second}
			_, err := client.Get("http://api.bosh-lite.com/v2/info")
			if err != nil {
				Skip("unable to communicate with bosh-lite, skipping")
			}
			setApiOutput := Cf("api", "http://api.bosh-lite.com", "--skip-ssl-validation")
			Eventually(setApiOutput.Out.Contents).Should(ContainSubstring("OK"))
		})

		AfterEach(func() {
			Eventually(Cf("api", "nonsense").Out.Contents).Should(ContainSubstring("no such host"))
		})

		// Normally cf curl only shows the output of the response
		// When using trace, it also shows the request/response information
		It("enables verbose output when -v is provided before a command", func() {
			output := Cf("-v", "curl", "/v2/info")
			Consistently(output.Out.Contents).ShouldNot(ContainSubstring("Invalid flag: -v"))
			Eventually(output.Out.Contents).Should(ContainSubstring("GET /v2/info HTTP/1.1"))
		})

		It("enables verbose output when -v is provided after a command", func() {
			output := Cf("curl", "/v2/info", "-v")
			Consistently(output.Out.Contents).ShouldNot(ContainSubstring("Invalid flag: -v"))
			Eventually(output.Out.Contents).Should(ContainSubstring("GET /v2/info HTTP/1.1"))
		})
	})

	Describe("Commands with new command structure", func() {
		It("prints usage help for all commands by providing `help` flag", func() {
			output := Cf("api", "-h")
			Eventually(output.Out.Contents).Should(ContainSubstring("USAGE"))
			Eventually(output.Out.Contents).Should(ContainSubstring("OPTIONS"))
		})

		It("accepts -h and --h flags for commands", func() {
			result := Cf("api", "-h")
			Consistently(result.Out).ShouldNot(Say("Invalid flag: -h"))
			Eventually(result.Out.Contents).Should(ContainSubstring("api - Set or view target api url"))

			result = Cf("api", "--h")
			Consistently(result.Out).ShouldNot(Say("Invalid flag: --h"))
			Eventually(result.Out.Contents).Should(ContainSubstring("api - Set or view target api url"))
		})

		It("runs requirement of the command", func() {
			dir, err := os.Getwd()
			Expect(err).ToNot(HaveOccurred())
			fullDir := filepath.Join(dir, "..", "..", "fixtures") //set home to a config w/o targeted api
			result := CfWith_CF_HOME(fullDir, "app", "app-should-never-exist-blah-blah")

			Eventually(result.Out).Should(Say("No API endpoint set."))
		})
	})

	Describe("exit codes", func() {
		It("exits non-zero when an unknown command is invoked", func() {
			result := Cf("some-command-that-should-never-actually-be-a-real-thing-i-can-use")

			Eventually(result, 3*time.Second).Should(Say("not a registered command"))
			Eventually(result).Should(Exit(1))
		})

		It("exits non-zero when known command is invoked with invalid option", func() {
			result := Cf("push", "--crazy")
			Eventually(result).Should(Exit(1))
		})
	})

	It("can print help menu by executing only the command `cf`", func() {
		output := Cf()
		Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
	})

	It("show user suggested commands for typos", func() {
		output := Cf("hlp")
		Eventually(output.Out, 3*time.Second).Should(Say("'hlp' is not a registered command. See 'cf help'"))
		Eventually(output.Out, 3*time.Second).Should(Say("Did you mean?"))
	})

	It("does not display requirement errors twice", func() {
		output := Cf("space")
		Eventually(output).Should(Exit(1))
		Expect(output.Out).To(Say("Incorrect Usage."))
		Expect(output.Out).NotTo(Say("Incorrect usage:"))
	})

	Describe("Plugins", func() {
		It("Can call a plugin command from the Plugins configuration if it does not exist as a cf command", func() {
			output := Cf("test_1_cmd1")
			Eventually(output.Out).Should(Say("You called cmd1 in test_1"))
		})

		It("Can call a plugin command via alias if it does not exist as a cf command", func() {
			output := Cf("test_1_cmd1_alias")
			Eventually(output.Out).Should(Say("You called cmd1 in test_1"))
		})

		It("Can call another plugin command when more than one plugin is installed", func() {
			output := Cf("test_2_cmd1")
			Eventually(output.Out).Should(Say("You called cmd1 in test_2"))
		})

		It("hide suggetsions for commands that aren't close to anything", func() {
			output := Cf("this-does-not-match-any-command")
			Eventually(output.Out, 3*time.Second).Should(Say("'this-does-not-match-any-command' is not a registered command. See 'cf help'"))
			Consistently(output.Out, 3*time.Second).ShouldNot(Say("Did you mean?\n help"))
		})

		It("informs user for any invalid commands", func() {
			output := Cf("foo-bar")
			Eventually(output.Out, 3*time.Second).Should(Say("'foo-bar' is not a registered command"))
		})

		It("Calls help if the plugin shares the same name", func() {
			output := Cf("help")
			Consistently(output.Out, 1).ShouldNot(Say("You called help in test_with_help"))
		})

		It("shows help with a '-h' or '--help' flag in plugin command", func() {
			output := Cf("test_1_cmd1", "-h")
			Consistently(output.Out).ShouldNot(Say("You called cmd1 in test_1"))
			Eventually(output.Out.Contents).Should(ContainSubstring("USAGE:"))
			Eventually(output.Out.Contents).Should(ContainSubstring("OPTIONS:"))
		})

		It("Calls the core push command if the plugin shares the same name", func() {
			output := Cf("push")
			Consistently(output.Out, 1).ShouldNot(Say("You called push in test_with_push"))
		})

		It("Calls the core short name if a plugin shares the same name", func() {
			output := Cf("p")
			Consistently(output.Out, 1).ShouldNot(Say("You called p within the plugin"))
		})

		It("Passes all arguments to a plugin", func() {
			output := Cf("my-say", "foo")
			Eventually(output.Out).Should(Say("foo"))
		})

		It("Passes all arguments and flags to a plugin", func() {
			output := Cf("my-say", "foo", "--loud")
			Eventually(output.Out).Should(Say("FOO"))
		})

		It("Calls a plugin that calls core commands", func() {
			output := Cf("awesomeness")
			Eventually(output.Out).Should(Say("my-say")) //look for another plugin
		})

		It("Sends stdoutput to the plugin to echo", func() {
			output := Cf("core-command", "plugins")
			Eventually(output.Out.Contents).Should(MatchRegexp("Command output from the plugin(.*\\W)*awesomeness(.*\\W)*FIN"))
		})

		It("Can call a core commmand from a plugin without terminal output", func() {
			output := Cf("core-command-quiet", "plugins")
			Eventually(output.Out.Contents).Should(MatchRegexp("^\n---------- Command output from the plugin"))
		})

		It("Can call a plugin that requires stdin (interactive)", func() {
			session := CfWithIo("input", "silly\n")
			Eventually(session.Out).Should(Say("silly"))
		})

		It("exits 1 when a plugin panics", func() {
			session := Cf("panic")
			Eventually(session).Should(Exit(1))
		})

		It("exits 1 when a plugin exits 1", func() {
			session := Cf("exit1")
			Eventually(session).Should(Exit(1))
		})

		It("show user suggested plugin commands for typos", func() {
			output := Cf("test_1_cmd")
			Eventually(output.Out, 3*time.Second).Should(Say("'test_1_cmd' is not a registered command. See 'cf help'"))
			Eventually(output.Out, 3*time.Second).Should(Say("Did you mean?"))
		})
	})
})

func Cf(args ...string) *Session {
	session, err := Start(exec.Command(buildPath, args...), GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}

func CfWithIo(command string, args string) *Session {
	cmd := exec.Command(buildPath, command)

	stdin, err := cmd.StdinPipe()
	Expect(err).ToNot(HaveOccurred())

	buffer := bufio.NewWriter(stdin)
	buffer.WriteString(args)
	buffer.Flush()

	session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}

func CfWith_CF_HOME(cfHome string, args ...string) *Session {
	cmd := exec.Command(buildPath, args...)
	cmd.Env = append(cmd.Env, "CF_HOME="+cfHome)
	session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}
<commit_msg>Update test to reflect error returned from go-flags<commit_after>package cmd_test

import (
	"bufio"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var buildPath string

var _ = SynchronizedBeforeSuite(func() []byte {
	path, buildErr := Build("code.cloudfoundry.org/cli")
	Expect(buildErr).NotTo(HaveOccurred())
	return []byte(path)
}, func(data []byte) {
	buildPath = string(data)
})

// gexec.Build leaves a compiled binary behind in /tmp.
var _ = SynchronizedAfterSuite(func() {}, func() {
	CleanupBuildArtifacts()
})

var _ = Describe("main", func() {
	var (
		old_PLUGINS_HOME string
	)

	BeforeEach(func() {
		old_PLUGINS_HOME = os.Getenv("CF_PLUGIN_HOME")

		dir, err := os.Getwd()
		Expect(err).NotTo(HaveOccurred())

		fullDir := filepath.Join(dir, "..", "..", "fixtures", "config", "main-plugin-test-config")
		err = os.Setenv("CF_PLUGIN_HOME", fullDir)
		Expect(err).NotTo(HaveOccurred())
	})

	AfterEach(func() {
		err := os.Setenv("CF_PLUGIN_HOME", old_PLUGINS_HOME)
		Expect(err).NotTo(HaveOccurred())
	})

	Describe("Help menu with -h/--help", func() {
		It("prints the help output with our custom template when run with 'cf -h'", func() {
			output := Cf("-h")
			Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
			Eventually(output.Out.Contents).Should(ContainSubstring("CF_TRACE=true"))
		})

		It("prints the help output with our custom template when run with 'cf --help'", func() {
			output := Cf("--help")
			Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
			Eventually(output.Out.Contents).Should(ContainSubstring("CF_TRACE=true"))
		})

		It("accepts -h and --h flags for all commands", func() {
			result := Cf("push", "-h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))

			result = Cf("push", "--no-route", "-h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))

			result = Cf("target", "--h")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))
		})

		It("accepts -h before the command name", func() {
			result := Cf("-h", "push", "--no-route")
			Consistently(result.Out).ShouldNot(Say("Incorrect Usage"))
			Consistently(result.Out).ShouldNot(Say("Start an app"))
			Eventually(result.Out.Contents).Should(ContainSubstring("USAGE"))
		})
	})

	Describe("Shows version with -v or --version", func() {
		It("prints the cf version if '-v' flag is provided", func() {
			output := Cf("-v")
			Eventually(output.Out.Contents).Should(ContainSubstring("cf version"))
			Eventually(output).Should(Exit(0))
		})

		It("prints the cf version if '--version' flag is provided", func() {
			output := Cf("--version")
			Eventually(output.Out.Contents).Should(ContainSubstring("cf version"))
			Eventually(output).Should(Exit(0))
		})
	})

	Describe("Enables verbose output with -v", func() {
		BeforeEach(func() {
			client := http.Client{Timeout: 3 * time.Second}
			_, err := client.Get("http://api.bosh-lite.com/v2/info")
			if err != nil {
				Skip("unable to communicate with bosh-lite, skipping")
			}
			setApiOutput := Cf("api", "http://api.bosh-lite.com", "--skip-ssl-validation")
			Eventually(setApiOutput.Out.Contents).Should(ContainSubstring("OK"))
		})

		AfterEach(func() {
			Eventually(Cf("api", "nonsense").Out.Contents).Should(ContainSubstring("no such host"))
		})

		// Normally cf curl only shows the output of the response
		// When using trace, it also shows the request/response information
		It("enables verbose output when -v is provided before a command", func() {
			output := Cf("-v", "curl", "/v2/info")
			Consistently(output.Out.Contents).ShouldNot(ContainSubstring("Invalid flag: -v"))
			Eventually(output.Out.Contents).Should(ContainSubstring("GET /v2/info HTTP/1.1"))
		})

		It("enables verbose output when -v is provided after a command", func() {
			output := Cf("curl", "/v2/info", "-v")
			Consistently(output.Out.Contents).ShouldNot(ContainSubstring("Invalid flag: -v"))
			Eventually(output.Out.Contents).Should(ContainSubstring("GET /v2/info HTTP/1.1"))
		})
	})

	Describe("Commands with new command structure", func() {
		It("prints usage help for all commands by providing `help` flag", func() {
			output := Cf("api", "-h")
			Eventually(output.Out.Contents).Should(ContainSubstring("USAGE"))
			Eventually(output.Out.Contents).Should(ContainSubstring("OPTIONS"))
		})

		It("accepts -h and --h flags for commands", func() {
			result := Cf("api", "-h")
			Consistently(result.Out).ShouldNot(Say("Invalid flag: -h"))
			Eventually(result.Out.Contents).Should(ContainSubstring("api - Set or view target api url"))

			result = Cf("api", "--h")
			Consistently(result.Out).ShouldNot(Say("Invalid flag: --h"))
			Eventually(result.Out.Contents).Should(ContainSubstring("api - Set or view target api url"))
		})

		It("runs requirement of the command", func() {
			dir, err := os.Getwd()
			Expect(err).ToNot(HaveOccurred())
			fullDir := filepath.Join(dir, "..", "..", "fixtures") //set home to a config w/o targeted api
			result := CfWith_CF_HOME(fullDir, "app", "app-should-never-exist-blah-blah")

			Eventually(result.Out).Should(Say("No API endpoint set."))
		})
	})

	Describe("exit codes", func() {
		It("exits non-zero when an unknown command is invoked", func() {
			result := Cf("some-command-that-should-never-actually-be-a-real-thing-i-can-use")

			Eventually(result, 3*time.Second).Should(Say("not a registered command"))
			Eventually(result).Should(Exit(1))
		})

		It("exits non-zero when known command is invoked with invalid option", func() {
			result := Cf("push", "--crazy")
			Eventually(result).Should(Exit(1))
		})
	})

	It("can print help menu by executing only the command `cf`", func() {
		output := Cf()
		Eventually(output.Out.Contents).Should(ContainSubstring("A command line tool to interact with Cloud Foundry"))
	})

	It("show user suggested commands for typos", func() {
		output := Cf("hlp")
		Eventually(output.Out, 3*time.Second).Should(Say("'hlp' is not a registered command. See 'cf help'"))
		Eventually(output.Out, 3*time.Second).Should(Say("Did you mean?"))
	})

	It("does not display requirement errors twice", func() {
		output := Cf("space")
		Eventually(output).Should(Exit(1))
		Expect(output.Out).To(Say("the required argument `SPACE` was not provided"))
		Expect(output.Out).NotTo(Say("the required argument `SPACE` was not provided"))
	})

	Describe("Plugins", func() {
		It("Can call a plugin command from the Plugins configuration if it does not exist as a cf command", func() {
			output := Cf("test_1_cmd1")
			Eventually(output.Out).Should(Say("You called cmd1 in test_1"))
		})

		It("Can call a plugin command via alias if it does not exist as a cf command", func() {
			output := Cf("test_1_cmd1_alias")
			Eventually(output.Out).Should(Say("You called cmd1 in test_1"))
		})

		It("Can call another plugin command when more than one plugin is installed", func() {
			output := Cf("test_2_cmd1")
			Eventually(output.Out).Should(Say("You called cmd1 in test_2"))
		})

		It("hide suggetsions for commands that aren't close to anything", func() {
			output := Cf("this-does-not-match-any-command")
			Eventually(output.Out, 3*time.Second).Should(Say("'this-does-not-match-any-command' is not a registered command. See 'cf help'"))
			Consistently(output.Out, 3*time.Second).ShouldNot(Say("Did you mean?\n help"))
		})

		It("informs user for any invalid commands", func() {
			output := Cf("foo-bar")
			Eventually(output.Out, 3*time.Second).Should(Say("'foo-bar' is not a registered command"))
		})

		It("Calls help if the plugin shares the same name", func() {
			output := Cf("help")
			Consistently(output.Out, 1).ShouldNot(Say("You called help in test_with_help"))
		})

		It("shows help with a '-h' or '--help' flag in plugin command", func() {
			output := Cf("test_1_cmd1", "-h")
			Consistently(output.Out).ShouldNot(Say("You called cmd1 in test_1"))
			Eventually(output.Out.Contents).Should(ContainSubstring("USAGE:"))
			Eventually(output.Out.Contents).Should(ContainSubstring("OPTIONS:"))
		})

		It("Calls the core push command if the plugin shares the same name", func() {
			output := Cf("push")
			Consistently(output.Out, 1).ShouldNot(Say("You called push in test_with_push"))
		})

		It("Calls the core short name if a plugin shares the same name", func() {
			output := Cf("p")
			Consistently(output.Out, 1).ShouldNot(Say("You called p within the plugin"))
		})

		It("Passes all arguments to a plugin", func() {
			output := Cf("my-say", "foo")
			Eventually(output.Out).Should(Say("foo"))
		})

		It("Passes all arguments and flags to a plugin", func() {
			output := Cf("my-say", "foo", "--loud")
			Eventually(output.Out).Should(Say("FOO"))
		})

		It("Calls a plugin that calls core commands", func() {
			output := Cf("awesomeness")
			Eventually(output.Out).Should(Say("my-say")) //look for another plugin
		})

		It("Sends stdoutput to the plugin to echo", func() {
			output := Cf("core-command", "plugins")
			Eventually(output.Out.Contents).Should(MatchRegexp("Command output from the plugin(.*\\W)*awesomeness(.*\\W)*FIN"))
		})

		It("Can call a core commmand from a plugin without terminal output", func() {
			output := Cf("core-command-quiet", "plugins")
			Eventually(output.Out.Contents).Should(MatchRegexp("^\n---------- Command output from the plugin"))
		})

		It("Can call a plugin that requires stdin (interactive)", func() {
			session := CfWithIo("input", "silly\n")
			Eventually(session.Out).Should(Say("silly"))
		})

		It("exits 1 when a plugin panics", func() {
			session := Cf("panic")
			Eventually(session).Should(Exit(1))
		})

		It("exits 1 when a plugin exits 1", func() {
			session := Cf("exit1")
			Eventually(session).Should(Exit(1))
		})

		It("show user suggested plugin commands for typos", func() {
			output := Cf("test_1_cmd")
			Eventually(output.Out, 3*time.Second).Should(Say("'test_1_cmd' is not a registered command. See 'cf help'"))
			Eventually(output.Out, 3*time.Second).Should(Say("Did you mean?"))
		})
	})
})

func Cf(args ...string) *Session {
	session, err := Start(exec.Command(buildPath, args...), GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}

func CfWithIo(command string, args string) *Session {
	cmd := exec.Command(buildPath, command)

	stdin, err := cmd.StdinPipe()
	Expect(err).ToNot(HaveOccurred())

	buffer := bufio.NewWriter(stdin)
	buffer.WriteString(args)
	buffer.Flush()

	session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}

func CfWith_CF_HOME(cfHome string, args ...string) *Session {
	cmd := exec.Command(buildPath, args...)
	cmd.Env = append(cmd.Env, "CF_HOME="+cfHome)
	session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	return session
}
<|endoftext|>