<commit_before>// Copyright 2016-2018 The NATS 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 test

import (
	"regexp"
	"testing"
)

const DefaultPass = "foo"

var permErrRe = regexp.MustCompile(`\A\-ERR\s+'Permissions Violation([^\r\n]+)\r\n`)

func TestUserAuthorizationProto(t *testing.T) {
	srv, opts := RunServerWithConfig("./configs/authorization.conf")
	defer srv.Shutdown()

	// Alice can do anything, check a few for OK result.
	c := createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "alice", DefaultPass)
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, okRe)

	// Check that we now reserve _SYS.> though for internal, so no clients.
	sendProto(t, c, "PUB _SYS.HB 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// Check that _ is ok
	sendProto(t, c, "PUB _ 2\r\nok\r\n")
	expectResult(t, c, okRe)

	c.Close()

	// Bob is a requestor only, e.g. req.foo, req.bar for publish, subscribe only to INBOXes.
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "bob", DefaultPass)
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// These should work ok.
	sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB req.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB req.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)
	c.Close()

	// Joe is a default user
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "joe", DefaultPass)
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo.bar.* 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar.baz 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// These should work ok.
	sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB PUBLIC.abcd 1\r\n")
	expectResult(t, c, okRe)

	sendProto(t, c, "PUB SANDBOX.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB SANDBOX.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)

	// Since only PWC, this should fail (too many tokens).
	sendProto(t, c, "PUB SANDBOX.foo.bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	c.Close()

	// This is the new style permissions with allow and deny clauses.
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "ns", DefaultPass)
	expectResult(t, c, okRe)

	// These should work
	sendProto(t, c, "PUB SANDBOX.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB baz.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB baz.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar.baz 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB SYS.1 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// Subscriptions

	// These should work ok.
	sendProto(t, c, "SUB foo.bar 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB foo.foo 1\r\n")
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "SUB foo.baz 1\r\n")
	expectResult(t, c, permErrRe)
}
<commit_msg>Test cache too<commit_after>// Copyright 2016-2018 The NATS 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 test

import (
	"regexp"
	"testing"
)

const DefaultPass = "foo"

var permErrRe = regexp.MustCompile(`\A\-ERR\s+'Permissions Violation([^\r\n]+)\r\n`)

func TestUserAuthorizationProto(t *testing.T) {
	srv, opts := RunServerWithConfig("./configs/authorization.conf")
	defer srv.Shutdown()

	// Alice can do anything, check a few for OK result.
	c := createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "alice", DefaultPass)
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, okRe)

	// Check that we now reserve _SYS.> though for internal, so no clients.
	sendProto(t, c, "PUB _SYS.HB 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// Check that _ is ok
	sendProto(t, c, "PUB _ 2\r\nok\r\n")
	expectResult(t, c, okRe)

	c.Close()

	// Bob is a requestor only, e.g. req.foo, req.bar for publish, subscribe only to INBOXes.
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "bob", DefaultPass)
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// These should work ok.
	sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB req.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB req.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)
	c.Close()

	// Joe is a default user
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "joe", DefaultPass)
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo.bar.* 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar.baz 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// These should work ok.
	sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB PUBLIC.abcd 1\r\n")
	expectResult(t, c, okRe)

	sendProto(t, c, "PUB SANDBOX.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB SANDBOX.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)

	// Since only PWC, this should fail (too many tokens).
	sendProto(t, c, "PUB SANDBOX.foo.bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	c.Close()

	// This is the new style permissions with allow and deny clauses.
	c = createClientConn(t, opts.Host, opts.Port)
	defer c.Close()
	expectAuthRequired(t, c)
	doAuthConnect(t, c, "", "ns", DefaultPass)
	expectResult(t, c, okRe)

	// These should work
	sendProto(t, c, "PUB SANDBOX.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB baz.bar 2\r\nok\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "PUB baz.foo 2\r\nok\r\n")
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "PUB foo 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB foo.bar.baz 2\r\nok\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "PUB SYS.1 2\r\nok\r\n")
	expectResult(t, c, permErrRe)

	// Subscriptions

	// These should work ok.
	sendProto(t, c, "SUB foo.bar 1\r\n")
	expectResult(t, c, okRe)
	sendProto(t, c, "SUB foo.foo 1\r\n")
	expectResult(t, c, okRe)

	// These should error.
	sendProto(t, c, "SUB foo 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "SUB foo.baz 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "SUB foo.baz 1\r\n")
	expectResult(t, c, permErrRe)
	sendProto(t, c, "SUB foo.baz 1\r\n")
	expectResult(t, c, permErrRe)
}
<|endoftext|>


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

package pypi

import (
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"

	packages_model "code.gitea.io/gitea/models/packages"
	"code.gitea.io/gitea/modules/context"
	packages_module "code.gitea.io/gitea/modules/packages"
	pypi_module "code.gitea.io/gitea/modules/packages/pypi"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/validation"
	"code.gitea.io/gitea/routers/api/packages/helper"
	packages_service "code.gitea.io/gitea/services/packages"
)

// https://www.python.org/dev/peps/pep-0503/#normalized-names
var normalizer = strings.NewReplacer(".", "-", "_", "-")
var nameMatcher = regexp.MustCompile(`\A[a-z0-9\.\-_]+\z`)

// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)

func apiError(ctx *context.Context, status int, obj interface{}) {
	helper.LogAndProcessError(ctx, status, obj, func(message string) {
		ctx.PlainText(status, message)
	})
}

// PackageMetadata returns the metadata for a single package
func PackageMetadata(ctx *context.Context) {
	packageName := normalizer.Replace(ctx.Params("id"))

	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypePyPI, packageName)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	if len(pvs) == 0 {
		apiError(ctx, http.StatusNotFound, err)
		return
	}

	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	ctx.Data["RegistryURL"] = setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/pypi"
	ctx.Data["PackageDescriptor"] = pds[0]
	ctx.Data["PackageDescriptors"] = pds
	ctx.HTML(http.StatusOK, "api/packages/pypi/simple")
}

// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
	packageName := normalizer.Replace(ctx.Params("id"))
	packageVersion := ctx.Params("version")
	filename := ctx.Params("filename")

	s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
		ctx,
		&packages_service.PackageInfo{
			Owner:       ctx.Package.Owner,
			PackageType: packages_model.TypePyPI,
			Name:        packageName,
			Version:     packageVersion,
		},
		&packages_service.PackageFileInfo{
			Filename: filename,
		},
	)
	if err != nil {
		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
			apiError(ctx, http.StatusNotFound, err)
			return
		}
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	defer s.Close()

	ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
}

// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
func UploadPackageFile(ctx *context.Context) {
	file, fileHeader, err := ctx.Req.FormFile("content")
	if err != nil {
		apiError(ctx, http.StatusBadRequest, err)
		return
	}
	defer file.Close()

	buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	defer buf.Close()

	_, _, hashSHA256, _ := buf.Sums()

	if !strings.EqualFold(ctx.Req.FormValue("sha256_digest"), fmt.Sprintf("%x", hashSHA256)) {
		apiError(ctx, http.StatusBadRequest, "hash mismatch")
		return
	}

	if _, err := buf.Seek(0, io.SeekStart); err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	packageName := normalizer.Replace(ctx.Req.FormValue("name"))
	packageVersion := ctx.Req.FormValue("version")
	if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
		apiError(ctx, http.StatusBadRequest, "invalid name or version")
		return
	}

	projectURL := ctx.Req.FormValue("home_page")
	if !validation.IsValidURL(projectURL) {
		projectURL = ""
	}

	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
		&packages_service.PackageCreationInfo{
			PackageInfo: packages_service.PackageInfo{
				Owner:       ctx.Package.Owner,
				PackageType: packages_model.TypePyPI,
				Name:        packageName,
				Version:     packageVersion,
			},
			SemverCompatible: true,
			Creator:          ctx.Doer,
			Metadata: &pypi_module.Metadata{
				Author:          ctx.Req.FormValue("author"),
				Description:     ctx.Req.FormValue("description"),
				LongDescription: ctx.Req.FormValue("long_description"),
				Summary:         ctx.Req.FormValue("summary"),
				ProjectURL:      projectURL,
				License:         ctx.Req.FormValue("license"),
				RequiresPython:  ctx.Req.FormValue("requires_python"),
			},
		},
		&packages_service.PackageFileCreationInfo{
			PackageFileInfo: packages_service.PackageFileInfo{
				Filename: fileHeader.Filename,
			},
			Data:   buf,
			IsLead: true,
		},
	)
	if err != nil {
		if err == packages_model.ErrDuplicatePackageFile {
			apiError(ctx, http.StatusBadRequest, err)
			return
		}
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	ctx.Status(http.StatusCreated)
}
<commit_msg>Allow uppercase ASCII alphabet in PyPI package names (#21095)<commit_after>// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package pypi

import (
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"

	packages_model "code.gitea.io/gitea/models/packages"
	"code.gitea.io/gitea/modules/context"
	packages_module "code.gitea.io/gitea/modules/packages"
	pypi_module "code.gitea.io/gitea/modules/packages/pypi"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/validation"
	"code.gitea.io/gitea/routers/api/packages/helper"
	packages_service "code.gitea.io/gitea/services/packages"
)

// https://www.python.org/dev/peps/pep-0503/#normalized-names
var normalizer = strings.NewReplacer(".", "-", "_", "-")
var nameMatcher = regexp.MustCompile(`\A[a-zA-Z0-9\.\-_]+\z`)

// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)

func apiError(ctx *context.Context, status int, obj interface{}) {
	helper.LogAndProcessError(ctx, status, obj, func(message string) {
		ctx.PlainText(status, message)
	})
}

// PackageMetadata returns the metadata for a single package
func PackageMetadata(ctx *context.Context) {
	packageName := normalizer.Replace(ctx.Params("id"))

	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypePyPI, packageName)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	if len(pvs) == 0 {
		apiError(ctx, http.StatusNotFound, err)
		return
	}

	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	ctx.Data["RegistryURL"] = setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/pypi"
	ctx.Data["PackageDescriptor"] = pds[0]
	ctx.Data["PackageDescriptors"] = pds
	ctx.HTML(http.StatusOK, "api/packages/pypi/simple")
}

// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
	packageName := normalizer.Replace(ctx.Params("id"))
	packageVersion := ctx.Params("version")
	filename := ctx.Params("filename")

	s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
		ctx,
		&packages_service.PackageInfo{
			Owner:       ctx.Package.Owner,
			PackageType: packages_model.TypePyPI,
			Name:        packageName,
			Version:     packageVersion,
		},
		&packages_service.PackageFileInfo{
			Filename: filename,
		},
	)
	if err != nil {
		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
			apiError(ctx, http.StatusNotFound, err)
			return
		}
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	defer s.Close()

	ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
}

// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
func UploadPackageFile(ctx *context.Context) {
	file, fileHeader, err := ctx.Req.FormFile("content")
	if err != nil {
		apiError(ctx, http.StatusBadRequest, err)
		return
	}
	defer file.Close()

	buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024)
	if err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}
	defer buf.Close()

	_, _, hashSHA256, _ := buf.Sums()

	if !strings.EqualFold(ctx.Req.FormValue("sha256_digest"), fmt.Sprintf("%x", hashSHA256)) {
		apiError(ctx, http.StatusBadRequest, "hash mismatch")
		return
	}

	if _, err := buf.Seek(0, io.SeekStart); err != nil {
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	packageName := normalizer.Replace(ctx.Req.FormValue("name"))
	packageVersion := ctx.Req.FormValue("version")
	if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
		apiError(ctx, http.StatusBadRequest, "invalid name or version")
		return
	}

	projectURL := ctx.Req.FormValue("home_page")
	if !validation.IsValidURL(projectURL) {
		projectURL = ""
	}

	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
		&packages_service.PackageCreationInfo{
			PackageInfo: packages_service.PackageInfo{
				Owner:       ctx.Package.Owner,
				PackageType: packages_model.TypePyPI,
				Name:        packageName,
				Version:     packageVersion,
			},
			SemverCompatible: true,
			Creator:          ctx.Doer,
			Metadata: &pypi_module.Metadata{
				Author:          ctx.Req.FormValue("author"),
				Description:     ctx.Req.FormValue("description"),
				LongDescription: ctx.Req.FormValue("long_description"),
				Summary:         ctx.Req.FormValue("summary"),
				ProjectURL:      projectURL,
				License:         ctx.Req.FormValue("license"),
				RequiresPython:  ctx.Req.FormValue("requires_python"),
			},
		},
		&packages_service.PackageFileCreationInfo{
			PackageFileInfo: packages_service.PackageFileInfo{
				Filename: fileHeader.Filename,
			},
			Data:   buf,
			IsLead: true,
		},
	)
	if err != nil {
		if err == packages_model.ErrDuplicatePackageFile {
			apiError(ctx, http.StatusBadRequest, err)
			return
		}
		apiError(ctx, http.StatusInternalServerError, err)
		return
	}

	ctx.Status(http.StatusCreated)
}
<|endoftext|>


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

package main

import (
	// "encoding/gob"
	"fmt"
	"os"
	"path"
	. "./equtils"

	"./historystorage"
	"github.com/mitchellh/cli"
)

const (
	storageMaterialsPath string = "materials"
)

func recursiveHardLink(srcPath, dstPath string) error {
	// TODO: write error to stderr with some logging library
	f, err := os.Open(srcPath)
	if err != nil {
		fmt.Printf("failed to open source path: %s (%s)\n",
			srcPath, err)
		return err
	}

	names, err := f.Readdirnames(0)
	if err != nil {
		fmt.Printf("failed to readdirnames: %s\n", err)
		return err
	}

	for _, name := range names {
		p := path.Join(srcPath, name)

		fi, err := os.Lstat(p)
		if err != nil {
			fmt.Printf("failed to stat (%s): %s", p, err)
			return err
		}

		if fi.Mode().IsDir() {
			dstDir := path.Join(dstPath, name)
			err = os.Mkdir(dstDir, 0777)
			if err != nil {
				fmt.Printf("failed to make directory %s: %s\n",
					dstDir, err)
				return err
			}
			err = recursiveHardLink(p, dstDir)
			if err != nil {
				return err
			}
		} else {
			realPath := p
			if fi.Mode() & os.ModeSymlink != 0 {
				realPath, err = os.Readlink(p)
				if err != nil {
					fmt.Printf("could not read link %s", p)
					return err
				}
			}
			err = os.Link(realPath, path.Join(dstPath, name))
			if err != nil {
				fmt.Printf("failed to link (src: %s(%s), dst: %s): %s\n",
					p, realPath, path.Join(dstPath, name), err)
				return err
			}
		}
	}
	return nil
}

func _init(args []string) {
	if len(args) != 3 {
		fmt.Printf("specify <config file path> <materials dir path> <storage dir path>\n")
		os.Exit(1)
	}

	confPath := args[0]
	materials := args[1]
	storagePath := args[2]

	cfi, err := os.Stat(confPath)
	if err != nil {
		fmt.Printf("failed to stat path: %s (%s)\n", confPath, err)
		os.Exit(1)
	}

	if !cfi.Mode().IsRegular() {
		fmt.Printf("config file (%s) must be a regular file\n", confPath)
		os.Exit(1)
	}

	sfi, err := os.Stat(storagePath)
	if err != nil {
		fmt.Printf("failed to stat path: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	if !sfi.Mode().IsDir() {
		fmt.Printf("storagePath directory (%s) must be a directory\n", storagePath)
		os.Exit(1)
	}

	dir, err := os.Open(storagePath)
	if err != nil {
		fmt.Printf("failed to open storagePath directory: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	fi, err := dir.Readdir(0)
	if err != nil {
		fmt.Printf("failed to read storagePath directory: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	if len(fi) != 0 {
		fmt.Printf("directory for earthquake storagePath (%s) must be empty\n", storagePath)
		os.Exit(1)
	}

	config, err := ParseConfigFile(confPath)
	if err != nil {
		fmt.Printf("parsing config file (%s) failed: %s\n", confPath, err)
		os.Exit(1)
	}

	// conf may be JSON/YAML/TOML, but always converted to JSON
	err = config.DumpToJsonFile(path.Join(storagePath, historystorage.StorageConfigPath))
	if err != nil {
		fmt.Printf("placing config file (%s) failed (%s)\n", confPath, err)
		os.Exit(1)
	}

	materialDir := path.Join(storagePath, storageMaterialsPath)
	err = os.Mkdir(materialDir, 0777)
	if err != nil {
		fmt.Printf("creating a directory for materials (%s) failed (%s)\n",
			materialDir, err)
		os.Exit(1)
		// TODO: cleaning conf file
	}

	err = recursiveHardLink(materials, materialDir)
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	}

	storage := historystorage.New(config.GetString("storageType"), storagePath)
	storage.CreateStorage()

	fmt.Printf("ok\n")
}

type initCmd struct {
}

func (cmd initCmd) Help() string {
	return "init help (todo)"
}

func (cmd initCmd) Run(args []string) int {
	_init(args)
	return 0
}

func (cmd initCmd) Synopsis() string {
	return "init subcommand"
}

func initCommandFactory() (cli.Command, error) {
	return initCmd{}, nil
}
<commit_msg>init: support --force option which forcibly (re-)creates storage dir<commit_after>// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	// "encoding/gob"
	"fmt"
	"os"
	"path"
	. "./equtils"

	"./historystorage"
	"github.com/mitchellh/cli"
	"flag"
)

const (
	storageMaterialsPath string = "materials"
)
type initFlags struct {
	Force bool
}

var (
	initFlagset = flag.NewFlagSet("init", flag.ExitOnError)
	_initFlags  = initFlags{}
)

func init() {
	initFlagset.BoolVar(&_initFlags.Force, "force", false, "forcibly (re-)create storage dir")
}

func recursiveHardLink(srcPath, dstPath string) error {
	// TODO: write error to stderr with some logging library
	f, err := os.Open(srcPath)
	if err != nil {
		fmt.Printf("failed to open source path: %s (%s)\n",
			srcPath, err)
		return err
	}

	names, err := f.Readdirnames(0)
	if err != nil {
		fmt.Printf("failed to readdirnames: %s\n", err)
		return err
	}

	for _, name := range names {
		p := path.Join(srcPath, name)

		fi, err := os.Lstat(p)
		if err != nil {
			fmt.Printf("failed to stat (%s): %s", p, err)
			return err
		}

		if fi.Mode().IsDir() {
			dstDir := path.Join(dstPath, name)
			err = os.Mkdir(dstDir, 0777)
			if err != nil {
				fmt.Printf("failed to make directory %s: %s\n",
					dstDir, err)
				return err
			}
			err = recursiveHardLink(p, dstDir)
			if err != nil {
				return err
			}
		} else {
			realPath := p
			if fi.Mode() & os.ModeSymlink != 0 {
				realPath, err = os.Readlink(p)
				if err != nil {
					fmt.Printf("could not read link %s", p)
					return err
				}
			}
			err = os.Link(realPath, path.Join(dstPath, name))
			if err != nil {
				fmt.Printf("failed to link (src: %s(%s), dst: %s): %s\n",
					p, realPath, path.Join(dstPath, name), err)
				return err
			}
		}
	}
	return nil
}

func _init(args []string) {
	if err := initFlagset.Parse(args); err != nil {
		fmt.Printf("%s", err.Error())
		os.Exit(1)
	}

	if initFlagset.NArg() != 3 {
		fmt.Printf("specify <config file path> <materials dir path> <storage dir path>\n")
		os.Exit(1)
	}

	confPath := initFlagset.Arg(0)
	materials := initFlagset.Arg(1)
	storagePath := initFlagset.Arg(2)

	cfi, err := os.Stat(confPath)
	if err != nil {
		fmt.Printf("failed to stat path: %s (%s)\n", confPath, err)
		os.Exit(1)
	}

	if !cfi.Mode().IsRegular() {
		fmt.Printf("config file (%s) must be a regular file\n", confPath)
		os.Exit(1)
	}
	if _initFlags.Force {
		// avoid catastrophe caused by a typo
		wellKnownDirs := map[string] bool {
			".": true, "..": true, "/":true, "/home":true, "/tmp":true, "/dummyWellKnownDir":true }
		if wellKnownDirs[storagePath] {
			fmt.Printf("storage dir(%s) typo?\n", storagePath)
			os.Exit(1)
		}
		if err = os.RemoveAll(storagePath); err != nil {
			// NOTE: if storagePath does not exist, RemoveAll returns nil
			fmt.Printf("could not remove %s (%s)\n", storagePath, err)
			os.Exit(1)
		}
		if err = os.Mkdir(storagePath, 0777); err != nil {
			fmt.Printf("could not create %s (%s)\n", storagePath, err)
			os.Exit(1)
		}
	}

	sfi, err := os.Stat(storagePath)
	if err != nil {
		fmt.Printf("failed to stat path: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	if !sfi.Mode().IsDir() {
		fmt.Printf("storagePath directory (%s) must be a directory\n", storagePath)
		os.Exit(1)
	}

	dir, err := os.Open(storagePath)
	if err != nil {
		fmt.Printf("failed to open storagePath directory: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	fi, err := dir.Readdir(0)
	if err != nil {
		fmt.Printf("failed to read storagePath directory: %s (%s)\n", storagePath, err)
		os.Exit(1)
	}

	if len(fi) != 0 {
		fmt.Printf("directory for earthquake storagePath (%s) must be empty\n", storagePath)
		os.Exit(1)
	}

	config, err := ParseConfigFile(confPath)
	if err != nil {
		fmt.Printf("parsing config file (%s) failed: %s\n", confPath, err)
		os.Exit(1)
	}

	// conf may be JSON/YAML/TOML, but always converted to JSON
	err = config.DumpToJsonFile(path.Join(storagePath, historystorage.StorageConfigPath))
	if err != nil {
		fmt.Printf("placing config file (%s) failed (%s)\n", confPath, err)
		os.Exit(1)
	}

	materialDir := path.Join(storagePath, storageMaterialsPath)
	err = os.Mkdir(materialDir, 0777)
	if err != nil {
		fmt.Printf("creating a directory for materials (%s) failed (%s)\n",
			materialDir, err)
		os.Exit(1)
		// TODO: cleaning conf file
	}

	err = recursiveHardLink(materials, materialDir)
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	}

	storage := historystorage.New(config.GetString("storageType"), storagePath)
	storage.CreateStorage()

	fmt.Printf("ok\n")
}

type initCmd struct {
}

func (cmd initCmd) Help() string {
	return "init help (todo)"
}

func (cmd initCmd) Run(args []string) int {
	_init(args)
	return 0
}

func (cmd initCmd) Synopsis() string {
	return "init subcommand"
}

func initCommandFactory() (cli.Command, error) {
	return initCmd{}, nil
}
<|endoftext|>


<commit_before>package metrics

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

// TODO Instrumentation? To get statistics?

// More detailed error
type HawkularClientError struct {
	msg  string
	Code int
}

func (self *HawkularClientError) Error() string {
	return fmt.Sprintf("Hawkular returned status code %d, error message: %s", self.Code, self.msg)
}

// Client creation and instance config

const (
	base_url string        = "hawkular/metrics"
	timeout  time.Duration = time.Duration(30 * time.Second)
)

type Parameters struct {
	Tenant    string // Technically optional, but requires setting Tenant() option everytime
	Url       string
	TLSConfig *tls.Config
	Token     string
}

type Client struct {
	Tenant string
	url    *url.URL
	client *http.Client
	Token  string
}

type HawkularClient interface {
	Send(*http.Request) (*http.Response, error)
}

// Modifiers

type Modifier func(*http.Request) error

// Override function to replace the Tenant (defaults to Client default)
func Tenant(tenant string) Modifier {
	return func(r *http.Request) error {
		r.Header.Set("Hawkular-Tenant", tenant)
		return nil
	}
}

// Add payload to the request
func Data(data interface{}) Modifier {
	return func(r *http.Request) error {
		jsonb, err := json.Marshal(data)
		if err != nil {
			return err
		}

		b := bytes.NewBuffer(jsonb)
		rc := ioutil.NopCloser(b)
		r.Body = rc

		// fmt.Printf("Sending: %s\n", string(jsonb))

		if b != nil {
			r.ContentLength = int64(b.Len())
		}
		return nil
	}
}

func (self *Client) Url(method string, e ...Endpoint) Modifier {
	// TODO Create composite URLs? Add().Add().. etc? Easier to modify on the fly..
	return func(r *http.Request) error {
		u := self.createUrl(e...)
		r.URL = u
		r.Method = method
		return nil
	}
}

// Filters for querying

type Filter func(r *http.Request)

func Filters(f ...Filter) Modifier {
	return func(r *http.Request) error {
		for _, filter := range f {
			filter(r)
		}
		return nil // Or should filter return err?
	}
}

// Add query parameters
func Param(k string, v string) Filter {
	return func(r *http.Request) {
		q := r.URL.Query()
		q.Set(k, v)
		r.URL.RawQuery = q.Encode()
	}
}

func TypeFilter(t MetricType) Filter {
	return Param("type", t.shortForm())
}

func TagsFilter(t map[string]string) Filter {
	j := tagsEncoder(t)
	return Param("tags", j)
}

// Requires HWKMETRICS-233
func IdFilter(regexp string) Filter {
	return Param("id", regexp)
}

func StartTimeFilter(duration time.Duration) Filter {
	return Param("start", strconv.Itoa(int(duration)))
}

func EndTimeFilter(duration time.Duration) Filter {
	return Param("end", strconv.Itoa(int(duration)))
}

func BucketsFilter(buckets int) Filter {
	return Param("buckets", strconv.Itoa(buckets))
}

// The SEND method..

func (self *Client) createRequest() *http.Request {
	req := &http.Request{
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       self.url.Host,
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Hawkular-Tenant", self.Tenant)

	if len(self.Token) > 0 {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", self.Token))
	}

	return req
}

func (self *Client) Send(o ...Modifier) (*http.Response, error) {
	// Initialize
	r := self.createRequest()

	// Run all the modifiers
	for _, f := range o {
		err := f(r)
		if err != nil {
			return nil, err
		}
	}

	return self.client.Do(r)
}

// Commands

func prepend(slice []Modifier, a ...Modifier) []Modifier {
	p := make([]Modifier, 0, len(slice)+len(a))
	p = append(p, a...)
	p = append(p, slice...)
	return p
}

// Create new Definition
func (self *Client) Create(md MetricDefinition, o ...Modifier) (bool, error) {
	// Keep the order, add custom prepend
	o = prepend(o, self.Url("POST", TypeEndpoint(md.Type)), Data(md))

	r, err := self.Send(o...)
	if err != nil {
		return false, err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		err = self.parseErrorResponse(r)
		if err, ok := err.(*HawkularClientError); ok {
			if err.Code != http.StatusConflict {
				return false, err
			} else {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Fetch definitions
func (self *Client) Definitions(o ...Modifier) ([]*MetricDefinition, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(Generic)))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		md := []*MetricDefinition{}
		if b != nil {
			if err = json.Unmarshal(b, &md); err != nil {
				return nil, err
			}
		}
		return md, err
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Return a single definition
func (self *Client) Definition(t MetricType, id string, o ...Modifier) (*MetricDefinition, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id)))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		md := &MetricDefinition{}
		if b != nil {
			if err = json.Unmarshal(b, md); err != nil {
				return nil, err
			}
		}
		return md, err
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Update tags
func (self *Client) UpdateTags(t MetricType, id string, tags map[string]string, o ...Modifier) error {
	o = prepend(o, self.Url("PUT", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint()), Data(tags))

	r, err := self.Send(o...)
	if err != nil {
		return err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		return self.parseErrorResponse(r)
	}

	return nil
}

// Delete given tags from the definition
func (self *Client) DeleteTags(t MetricType, id string, tags map[string]string, o ...Modifier) error {
	o = prepend(o, self.Url("DELETE", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint(), TagsEndpoint(tags)))

	r, err := self.Send(o...)
	if err != nil {
		return err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		return self.parseErrorResponse(r)
	}

	return nil
}

// Fetch metric definition tags
func (self *Client) Tags(t MetricType, id string, o ...Modifier) (map[string]string, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint()))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		tags := make(map[string]string)
		if b != nil {
			if err = json.Unmarshal(b, &tags); err != nil {
				return nil, err
			}
		}
		return tags, nil
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Write datapoints to the server
func (self *Client) Write(metrics []MetricHeader, o ...Modifier) error {
	if len(metrics) > 0 {
		mHs := make(map[MetricType][]MetricHeader)
		for _, m := range metrics {
			if _, found := mHs[m.Type]; !found {
				mHs[m.Type] = make([]MetricHeader, 0, 1)
			}
			mHs[m.Type] = append(mHs[m.Type], m)
		}

		wg := &sync.WaitGroup{}
		errorsChan := make(chan error, len(mHs))

		for k, v := range mHs {
			wg.Add(1)
			go func(k MetricType, v []MetricHeader) {
				defer wg.Done()

				// Should be sorted and splitted by type & tenant..
				on := o
				on = prepend(on, self.Url("POST", TypeEndpoint(k), DataEndpoint()), Data(v))

				r, err := self.Send(on...)
				if err != nil {
					errorsChan <- err
					return
				}

				defer r.Body.Close()

				if r.StatusCode > 399 {
					errorsChan <- self.parseErrorResponse(r)
				}
			}(k, v)
		}
		wg.Wait()
		select {
		case err, ok := <-errorsChan:
			if ok {
				return err
			}
			// If channel is closed, we're done
		default:
			// Nothing to do
		}

	}
	return nil
}

// Read data from the server
func (self *Client) ReadMetric(t MetricType, id string, o ...Modifier) ([]*Datapoint, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id), DataEndpoint()))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}

		// Check for GaugeBucketpoint and so on for the rest.. uh
		dp := []*Datapoint{}
		if b != nil {
			if err = json.Unmarshal(b, &dp); err != nil {
				return nil, err
			}
		}
		return dp, nil
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Initialization

func NewHawkularClient(p Parameters) (*Client, error) {
	uri, err := url.Parse(p.Url)
	if err != nil {
		return nil, err
	}

	if uri.Path == "" {
		uri.Path = base_url
	}

	u := &url.URL{
		Host:   uri.Host,
		Path:   uri.Path,
		Scheme: uri.Scheme,
		Opaque: fmt.Sprintf("//%s/%s", uri.Host, uri.Path),
	}

	c := &http.Client{
		Timeout: timeout,
	}
	if p.TLSConfig != nil {
		transport := &http.Transport{TLSClientConfig: p.TLSConfig}
		c.Transport = transport
	}

	return &Client{
		url:    u,
		Tenant: p.Tenant,
		Token:  p.Token,
		client: c,
	}, nil
}

// HTTP Helper functions

func cleanId(id string) string {
	return url.QueryEscape(id)
}

func (self *Client) parseErrorResponse(resp *http.Response) error {
	// Parse error messages here correctly..
	reply, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return &HawkularClientError{Code: resp.StatusCode,
			msg: fmt.Sprintf("Reply could not be read: %s", err.Error()),
		}
	}

	details := &HawkularError{}

	err = json.Unmarshal(reply, details)
	if err != nil {
		return &HawkularClientError{Code: resp.StatusCode,
			msg: fmt.Sprintf("Reply could not be parsed: %s", err.Error()),
		}
	}

	return &HawkularClientError{Code: resp.StatusCode,
		msg: details.ErrorMsg,
	}
}

// URL functions (...)

type Endpoint func(u *url.URL)

func (self *Client) createUrl(e ...Endpoint) *url.URL {
	mu := *self.url
	for _, f := range e {
		f(&mu)
	}
	return &mu
}

func TypeEndpoint(t MetricType) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, t.String())
	}
}

func SingleMetricEndpoint(id string) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, url.QueryEscape(id))
	}
}

func TagEndpoint() Endpoint {
	return func(u *url.URL) {
		addToUrl(u, "tags")
	}
}

func TagsEndpoint(tags map[string]string) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, tagsEncoder(tags))
	}
}

func DataEndpoint() Endpoint {
	return func(u *url.URL) {
		addToUrl(u, "data")
	}
}

func addToUrl(u *url.URL, s string) *url.URL {
	u.Opaque = fmt.Sprintf("%s/%s", u.Opaque, s)
	return u
}

func tagsEncoder(t map[string]string) string {
	tags := make([]string, 0, len(t))
	for k, v := range t {
		tags = append(tags, fmt.Sprintf("%s:%s", k, v))
	}
	j := strings.Join(tags, ",")
	return j
}
<commit_msg>Fix StartTimeFilter & EndTimeFilter<commit_after>package metrics

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

// TODO Instrumentation? To get statistics?

// More detailed error
type HawkularClientError struct {
	msg  string
	Code int
}

func (self *HawkularClientError) Error() string {
	return fmt.Sprintf("Hawkular returned status code %d, error message: %s", self.Code, self.msg)
}

// Client creation and instance config

const (
	base_url string        = "hawkular/metrics"
	timeout  time.Duration = time.Duration(30 * time.Second)
)

type Parameters struct {
	Tenant    string // Technically optional, but requires setting Tenant() option everytime
	Url       string
	TLSConfig *tls.Config
	Token     string
}

type Client struct {
	Tenant string
	url    *url.URL
	client *http.Client
	Token  string
}

type HawkularClient interface {
	Send(*http.Request) (*http.Response, error)
}

// Modifiers

type Modifier func(*http.Request) error

// Override function to replace the Tenant (defaults to Client default)
func Tenant(tenant string) Modifier {
	return func(r *http.Request) error {
		r.Header.Set("Hawkular-Tenant", tenant)
		return nil
	}
}

// Add payload to the request
func Data(data interface{}) Modifier {
	return func(r *http.Request) error {
		jsonb, err := json.Marshal(data)
		if err != nil {
			return err
		}

		b := bytes.NewBuffer(jsonb)
		rc := ioutil.NopCloser(b)
		r.Body = rc

		// fmt.Printf("Sending: %s\n", string(jsonb))

		if b != nil {
			r.ContentLength = int64(b.Len())
		}
		return nil
	}
}

func (self *Client) Url(method string, e ...Endpoint) Modifier {
	// TODO Create composite URLs? Add().Add().. etc? Easier to modify on the fly..
	return func(r *http.Request) error {
		u := self.createUrl(e...)
		r.URL = u
		r.Method = method
		return nil
	}
}

// Filters for querying

type Filter func(r *http.Request)

func Filters(f ...Filter) Modifier {
	return func(r *http.Request) error {
		for _, filter := range f {
			filter(r)
		}
		return nil // Or should filter return err?
	}
}

// Add query parameters
func Param(k string, v string) Filter {
	return func(r *http.Request) {
		q := r.URL.Query()
		q.Set(k, v)
		r.URL.RawQuery = q.Encode()
	}
}

func TypeFilter(t MetricType) Filter {
	return Param("type", t.shortForm())
}

func TagsFilter(t map[string]string) Filter {
	j := tagsEncoder(t)
	return Param("tags", j)
}

// Requires HWKMETRICS-233
func IdFilter(regexp string) Filter {
	return Param("id", regexp)
}

func StartTimeFilter(startTime time.Time) Filter {
	return Param("start", strconv.Itoa(int(startTime.Unix())))
}

func EndTimeFilter(endTime time.Time) Filter {
	return Param("end", strconv.Itoa(int(endTime.Unix())))
}

func BucketsFilter(buckets int) Filter {
	return Param("buckets", strconv.Itoa(buckets))
}

// The SEND method..

func (self *Client) createRequest() *http.Request {
	req := &http.Request{
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       self.url.Host,
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Hawkular-Tenant", self.Tenant)

	if len(self.Token) > 0 {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", self.Token))
	}

	return req
}

func (self *Client) Send(o ...Modifier) (*http.Response, error) {
	// Initialize
	r := self.createRequest()

	// Run all the modifiers
	for _, f := range o {
		err := f(r)
		if err != nil {
			return nil, err
		}
	}

	return self.client.Do(r)
}

// Commands

func prepend(slice []Modifier, a ...Modifier) []Modifier {
	p := make([]Modifier, 0, len(slice)+len(a))
	p = append(p, a...)
	p = append(p, slice...)
	return p
}

// Create new Definition
func (self *Client) Create(md MetricDefinition, o ...Modifier) (bool, error) {
	// Keep the order, add custom prepend
	o = prepend(o, self.Url("POST", TypeEndpoint(md.Type)), Data(md))

	r, err := self.Send(o...)
	if err != nil {
		return false, err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		err = self.parseErrorResponse(r)
		if err, ok := err.(*HawkularClientError); ok {
			if err.Code != http.StatusConflict {
				return false, err
			} else {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Fetch definitions
func (self *Client) Definitions(o ...Modifier) ([]*MetricDefinition, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(Generic)))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		md := []*MetricDefinition{}
		if b != nil {
			if err = json.Unmarshal(b, &md); err != nil {
				return nil, err
			}
		}
		return md, err
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Return a single definition
func (self *Client) Definition(t MetricType, id string, o ...Modifier) (*MetricDefinition, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id)))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		md := &MetricDefinition{}
		if b != nil {
			if err = json.Unmarshal(b, md); err != nil {
				return nil, err
			}
		}
		return md, err
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Update tags
func (self *Client) UpdateTags(t MetricType, id string, tags map[string]string, o ...Modifier) error {
	o = prepend(o, self.Url("PUT", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint()), Data(tags))

	r, err := self.Send(o...)
	if err != nil {
		return err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		return self.parseErrorResponse(r)
	}

	return nil
}

// Delete given tags from the definition
func (self *Client) DeleteTags(t MetricType, id string, tags map[string]string, o ...Modifier) error {
	o = prepend(o, self.Url("DELETE", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint(), TagsEndpoint(tags)))

	r, err := self.Send(o...)
	if err != nil {
		return err
	}

	defer r.Body.Close()

	if r.StatusCode > 399 {
		return self.parseErrorResponse(r)
	}

	return nil
}

// Fetch metric definition tags
func (self *Client) Tags(t MetricType, id string, o ...Modifier) (map[string]string, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id), TagEndpoint()))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}
		tags := make(map[string]string)
		if b != nil {
			if err = json.Unmarshal(b, &tags); err != nil {
				return nil, err
			}
		}
		return tags, nil
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Write datapoints to the server
func (self *Client) Write(metrics []MetricHeader, o ...Modifier) error {
	if len(metrics) > 0 {
		mHs := make(map[MetricType][]MetricHeader)
		for _, m := range metrics {
			if _, found := mHs[m.Type]; !found {
				mHs[m.Type] = make([]MetricHeader, 0, 1)
			}
			mHs[m.Type] = append(mHs[m.Type], m)
		}

		wg := &sync.WaitGroup{}
		errorsChan := make(chan error, len(mHs))

		for k, v := range mHs {
			wg.Add(1)
			go func(k MetricType, v []MetricHeader) {
				defer wg.Done()

				// Should be sorted and splitted by type & tenant..
				on := o
				on = prepend(on, self.Url("POST", TypeEndpoint(k), DataEndpoint()), Data(v))

				r, err := self.Send(on...)
				if err != nil {
					errorsChan <- err
					return
				}

				defer r.Body.Close()

				if r.StatusCode > 399 {
					errorsChan <- self.parseErrorResponse(r)
				}
			}(k, v)
		}
		wg.Wait()
		select {
		case err, ok := <-errorsChan:
			if ok {
				return err
			}
			// If channel is closed, we're done
		default:
			// Nothing to do
		}

	}
	return nil
}

// Read data from the server
func (self *Client) ReadMetric(t MetricType, id string, o ...Modifier) ([]*Datapoint, error) {
	o = prepend(o, self.Url("GET", TypeEndpoint(t), SingleMetricEndpoint(id), DataEndpoint()))

	r, err := self.Send(o...)
	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if r.StatusCode == http.StatusOK {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, err
		}

		// Check for GaugeBucketpoint and so on for the rest.. uh
		dp := []*Datapoint{}
		if b != nil {
			if err = json.Unmarshal(b, &dp); err != nil {
				return nil, err
			}
		}
		return dp, nil
	} else if r.StatusCode > 399 {
		return nil, self.parseErrorResponse(r)
	}

	return nil, nil
}

// Initialization

func NewHawkularClient(p Parameters) (*Client, error) {
	uri, err := url.Parse(p.Url)
	if err != nil {
		return nil, err
	}

	if uri.Path == "" {
		uri.Path = base_url
	}

	u := &url.URL{
		Host:   uri.Host,
		Path:   uri.Path,
		Scheme: uri.Scheme,
		Opaque: fmt.Sprintf("//%s/%s", uri.Host, uri.Path),
	}

	c := &http.Client{
		Timeout: timeout,
	}
	if p.TLSConfig != nil {
		transport := &http.Transport{TLSClientConfig: p.TLSConfig}
		c.Transport = transport
	}

	return &Client{
		url:    u,
		Tenant: p.Tenant,
		Token:  p.Token,
		client: c,
	}, nil
}

// HTTP Helper functions

func cleanId(id string) string {
	return url.QueryEscape(id)
}

func (self *Client) parseErrorResponse(resp *http.Response) error {
	// Parse error messages here correctly..
	reply, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return &HawkularClientError{Code: resp.StatusCode,
			msg: fmt.Sprintf("Reply could not be read: %s", err.Error()),
		}
	}

	details := &HawkularError{}

	err = json.Unmarshal(reply, details)
	if err != nil {
		return &HawkularClientError{Code: resp.StatusCode,
			msg: fmt.Sprintf("Reply could not be parsed: %s", err.Error()),
		}
	}

	return &HawkularClientError{Code: resp.StatusCode,
		msg: details.ErrorMsg,
	}
}

// URL functions (...)

type Endpoint func(u *url.URL)

func (self *Client) createUrl(e ...Endpoint) *url.URL {
	mu := *self.url
	for _, f := range e {
		f(&mu)
	}
	return &mu
}

func TypeEndpoint(t MetricType) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, t.String())
	}
}

func SingleMetricEndpoint(id string) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, url.QueryEscape(id))
	}
}

func TagEndpoint() Endpoint {
	return func(u *url.URL) {
		addToUrl(u, "tags")
	}
}

func TagsEndpoint(tags map[string]string) Endpoint {
	return func(u *url.URL) {
		addToUrl(u, tagsEncoder(tags))
	}
}

func DataEndpoint() Endpoint {
	return func(u *url.URL) {
		addToUrl(u, "data")
	}
}

func addToUrl(u *url.URL, s string) *url.URL {
	u.Opaque = fmt.Sprintf("%s/%s", u.Opaque, s)
	return u
}

func tagsEncoder(t map[string]string) string {
	tags := make([]string, 0, len(t))
	for k, v := range t {
		tags = append(tags, fmt.Sprintf("%s:%s", k, v))
	}
	j := strings.Join(tags, ",")
	return j
}
<|endoftext|>


<commit_before>package mid

import (
	"gitlab.com/gomidi/midi/midimessage/meta/meter"
	// "bytes"
	// "encoding/binary"
	"fmt"
	// "github.com/gomidi/midi/internal/midilib"
	"gitlab.com/gomidi/midi/midimessage/channel"
	"gitlab.com/gomidi/midi/midimessage/meta"

	// "github.com/gomidi/midi/midimessage/realtime"
	// "github.com/gomidi/midi/midimessage/syscommon"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfwriter"
	// "time"
)

// SMFWriter writes SMF MIDI data. Its methods must not be called concurrently
type SMFWriter struct {
	wr smf.Writer
	*midiWriter
	finishedTracks uint16
	dest           io.Writer
}

// NewSMF returns a new SMFWriter that writes to dest.
// It panics if numtracks is == 0.
func NewSMF(dest io.Writer, numtracks uint16, options ...smfwriter.Option) *SMFWriter {
	if numtracks == 0 {
		panic("numtracks must be > 0")
	}

	options = append(
		[]smfwriter.Option{
			smfwriter.NumTracks(numtracks),
			smfwriter.TimeFormat(smf.MetricTicks(0)),
		}, options...)

	wr := smfwriter.New(dest, options...)
	return &SMFWriter{
		dest:       dest,
		wr:         wr,
		midiWriter: &midiWriter{wr: wr, ch: channel.Channel0},
	}
}

// NewSMFFile creates a new SMF file and allows writer to write to it.
// The file is guaranteed to be closed when returning.
// The last track is closed automatically, if needed.
// It panics if numtracks is == 0.
func NewSMFFile(file string, numtracks uint16, writer func(*SMFWriter) error, options ...smfwriter.Option) error {
	if numtracks == 0 {
		panic("numtracks must be > 0")
	}

	f, err := os.Create(file)
	if err != nil {
		return err
	}

	defer f.Close()

	wr := NewSMF(f, numtracks, options...)
	if writer != nil {
		err = writer(wr)
		if err != nil {
			return err
		}
	}

	if no := wr.wr.Header().NumTracks; wr.finishedTracks < no {
		err := wr.EndOfTrack()
		if err != nil {
			return err
		}
	}

	return err
}

// SetDelta sets the delta ticks to the next message
func (w *SMFWriter) SetDelta(deltatime uint32) {
	w.wr.SetDelta(deltatime)
}

// EndOfTrack signals the end of a track
func (w *SMFWriter) EndOfTrack() error {
	w.midiWriter.noteState = [16][128]bool{}
	if no := w.wr.Header().NumTracks; w.finishedTracks >= no {
		return fmt.Errorf("too many tracks: in header: %v, closed: %v", no, w.finishedTracks+1)
	}
	w.finishedTracks++
	return w.wr.Write(meta.EndOfTrack)
}

// Copyright writes the copyright meta message
func (w *SMFWriter) Copyright(text string) error {
	return w.wr.Write(meta.Copyright(text))
}

// Cuepoint writes the cuepoint meta message
func (w *SMFWriter) Cuepoint(text string) error {
	return w.wr.Write(meta.Cuepoint(text))
}

// Device writes the device port meta message
func (w *SMFWriter) Device(port string) error {
	return w.wr.Write(meta.Device(port))
}

// KeySig writes the key signature meta message.
// A more comfortable way is to use the Key method in conjunction
// with the gomidi/midi/midimessage/meta/key package
func (w *SMFWriter) KeySig(key uint8, ismajor bool, num uint8, isflat bool) error {
	return w.wr.Write(meta.Key{Key: key, IsMajor: ismajor, Num: num, IsFlat: isflat})
}

// Key writes the given key signature meta message.
// It is supposed to be used with the gomidi/midi/midimessage/meta/key package
func (w *SMFWriter) Key(keysig meta.Key) error {
	return w.wr.Write(keysig)
}

// Lyric writes the lyric meta message
func (w *SMFWriter) Lyric(text string) error {
	return w.wr.Write(meta.Lyric(text))
}

// Marker writes the marker meta message
func (w *SMFWriter) Marker(text string) error {
	return w.wr.Write(meta.Marker(text))
}

// DeprecatedChannel writes the deprecated MIDI channel meta message
func (w *SMFWriter) DeprecatedChannel(ch uint8) error {
	return w.wr.Write(meta.Channel(ch))
}

// DeprecatedPort writes the deprecated MIDI port meta message
func (w *SMFWriter) DeprecatedPort(port uint8) error {
	return w.wr.Write(meta.Port(port))
}

// Program writes the program name meta message
func (w *SMFWriter) Program(text string) error {
	return w.wr.Write(meta.Program(text))
}

// Sequence writes the sequence (name) meta message
func (w *SMFWriter) Sequence(text string) error {
	return w.wr.Write(meta.Sequence(text))
}

// SequenceNo writes the sequence number meta message
func (w *SMFWriter) SequenceNo(no uint16) error {
	return w.wr.Write(meta.SequenceNo(no))
}

// SequencerData writes a custom sequences specific meta message
func (w *SMFWriter) SequencerData(data []byte) error {
	return w.wr.Write(meta.SequencerData(data))
}

// SMPTE writes the SMPTE offset meta message
func (w *SMFWriter) SMPTE(hour, minute, second, frame, fractionalFrame byte) error {
	return w.wr.Write(meta.SMPTE{
		Hour:            hour,
		Minute:          minute,
		Second:          second,
		Frame:           frame,
		FractionalFrame: fractionalFrame,
	})
}

// Tempo writes the tempo meta message
func (w *SMFWriter) TempoBPM(bpm float64) error {
	return w.wr.Write(meta.FractionalBPM(bpm))
}

// Text writes the text meta message
func (w *SMFWriter) Text(text string) error {
	return w.wr.Write(meta.Text(text))
}

// Meter writes the time signature meta message in a more comfortable way.
// Numerator and Denominator are decimalw.
func (w *SMFWriter) Meter(numerator, denominator uint8) error {
	return w.wr.Write(meter.Meter(numerator, denominator))
}

// TimeSig writes the time signature meta message.
// Numerator and Denominator are decimalw.
// If you don't want to deal with clocks per click and demisemiquaverperquarter,
// user the Meter method instead.
func (w *SMFWriter) TimeSig(numerator, denominator, clocksPerClick, demiSemiQuaverPerQuarter uint8) error {
	return w.wr.Write(meta.TimeSig{
		Numerator:                numerator,
		Denominator:              denominator,
		ClocksPerClick:           clocksPerClick,
		DemiSemiQuaverPerQuarter: demiSemiQuaverPerQuarter,
	})
}

// Track writes the track name aka instrument name meta message
func (w *SMFWriter) Track(track string) error {
	return w.wr.Write(meta.Track(track))
}
<commit_msg>add metricticks shortcuts to mid.SMFWriter<commit_after>package mid

import (
	"gitlab.com/gomidi/midi/midimessage/meta/meter"
	// "bytes"
	// "encoding/binary"
	"fmt"
	// "github.com/gomidi/midi/internal/midilib"
	"gitlab.com/gomidi/midi/midimessage/channel"
	"gitlab.com/gomidi/midi/midimessage/meta"

	// "github.com/gomidi/midi/midimessage/realtime"
	// "github.com/gomidi/midi/midimessage/syscommon"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfwriter"
	// "time"
)

// SMFWriter writes SMF MIDI data. Its methods must not be called concurrently
type SMFWriter struct {
	wr smf.Writer
	*midiWriter
	finishedTracks uint16
	dest           io.Writer
	smf.MetricTicks
}

// NewSMF returns a new SMFWriter that writes to dest.
// It panics if numtracks is == 0.
func NewSMF(dest io.Writer, numtracks uint16, options ...smfwriter.Option) *SMFWriter {
	if numtracks == 0 {
		panic("numtracks must be > 0")
	}

	options = append(
		[]smfwriter.Option{
			smfwriter.NumTracks(numtracks),
			smfwriter.TimeFormat(smf.MetricTicks(960)),
		}, options...)

	wr := smfwriter.New(dest, options...)

	smfwr := &SMFWriter{
		dest:       dest,
		wr:         wr,
		midiWriter: &midiWriter{wr: wr, ch: channel.Channel0},
	}

	if metr, isMetric := wr.Header().TimeFormat.(smf.MetricTicks); isMetric {
		smfwr.MetricTicks = metr
	}
	return smfwr
}

// NewSMFFile creates a new SMF file and allows writer to write to it.
// The file is guaranteed to be closed when returning.
// The last track is closed automatically, if needed.
// It panics if numtracks is == 0.
func NewSMFFile(file string, numtracks uint16, writer func(*SMFWriter) error, options ...smfwriter.Option) error {
	if numtracks == 0 {
		panic("numtracks must be > 0")
	}

	f, err := os.Create(file)
	if err != nil {
		return err
	}

	defer f.Close()

	wr := NewSMF(f, numtracks, options...)
	if writer != nil {
		err = writer(wr)
		if err != nil {
			return err
		}
	}

	if no := wr.wr.Header().NumTracks; wr.finishedTracks < no {
		err := wr.EndOfTrack()
		if err != nil {
			return err
		}
	}

	return err
}

// SetDelta sets the delta ticks to the next message
func (w *SMFWriter) SetDelta(deltatime uint32) {
	w.wr.SetDelta(deltatime)
}

// EndOfTrack signals the end of a track
func (w *SMFWriter) EndOfTrack() error {
	w.midiWriter.noteState = [16][128]bool{}
	if no := w.wr.Header().NumTracks; w.finishedTracks >= no {
		return fmt.Errorf("too many tracks: in header: %v, closed: %v", no, w.finishedTracks+1)
	}
	w.finishedTracks++
	return w.wr.Write(meta.EndOfTrack)
}

// Copyright writes the copyright meta message
func (w *SMFWriter) Copyright(text string) error {
	return w.wr.Write(meta.Copyright(text))
}

// Cuepoint writes the cuepoint meta message
func (w *SMFWriter) Cuepoint(text string) error {
	return w.wr.Write(meta.Cuepoint(text))
}

// Device writes the device port meta message
func (w *SMFWriter) Device(port string) error {
	return w.wr.Write(meta.Device(port))
}

// KeySig writes the key signature meta message.
// A more comfortable way is to use the Key method in conjunction
// with the gomidi/midi/midimessage/meta/key package
func (w *SMFWriter) KeySig(key uint8, ismajor bool, num uint8, isflat bool) error {
	return w.wr.Write(meta.Key{Key: key, IsMajor: ismajor, Num: num, IsFlat: isflat})
}

// Key writes the given key signature meta message.
// It is supposed to be used with the gomidi/midi/midimessage/meta/key package
func (w *SMFWriter) Key(keysig meta.Key) error {
	return w.wr.Write(keysig)
}

// Lyric writes the lyric meta message
func (w *SMFWriter) Lyric(text string) error {
	return w.wr.Write(meta.Lyric(text))
}

// Marker writes the marker meta message
func (w *SMFWriter) Marker(text string) error {
	return w.wr.Write(meta.Marker(text))
}

// DeprecatedChannel writes the deprecated MIDI channel meta message
func (w *SMFWriter) DeprecatedChannel(ch uint8) error {
	return w.wr.Write(meta.Channel(ch))
}

// DeprecatedPort writes the deprecated MIDI port meta message
func (w *SMFWriter) DeprecatedPort(port uint8) error {
	return w.wr.Write(meta.Port(port))
}

// Program writes the program name meta message
func (w *SMFWriter) Program(text string) error {
	return w.wr.Write(meta.Program(text))
}

// Sequence writes the sequence (name) meta message
func (w *SMFWriter) Sequence(text string) error {
	return w.wr.Write(meta.Sequence(text))
}

// SequenceNo writes the sequence number meta message
func (w *SMFWriter) SequenceNo(no uint16) error {
	return w.wr.Write(meta.SequenceNo(no))
}

// SequencerData writes a custom sequences specific meta message
func (w *SMFWriter) SequencerData(data []byte) error {
	return w.wr.Write(meta.SequencerData(data))
}

// SMPTE writes the SMPTE offset meta message
func (w *SMFWriter) SMPTE(hour, minute, second, frame, fractionalFrame byte) error {
	return w.wr.Write(meta.SMPTE{
		Hour:            hour,
		Minute:          minute,
		Second:          second,
		Frame:           frame,
		FractionalFrame: fractionalFrame,
	})
}

// Tempo writes the tempo meta message
func (w *SMFWriter) TempoBPM(bpm float64) error {
	return w.wr.Write(meta.FractionalBPM(bpm))
}

// Text writes the text meta message
func (w *SMFWriter) Text(text string) error {
	return w.wr.Write(meta.Text(text))
}

// Meter writes the time signature meta message in a more comfortable way.
// Numerator and Denominator are decimalw.
func (w *SMFWriter) Meter(numerator, denominator uint8) error {
	return w.wr.Write(meter.Meter(numerator, denominator))
}

// TimeSig writes the time signature meta message.
// Numerator and Denominator are decimalw.
// If you don't want to deal with clocks per click and demisemiquaverperquarter,
// user the Meter method instead.
func (w *SMFWriter) TimeSig(numerator, denominator, clocksPerClick, demiSemiQuaverPerQuarter uint8) error {
	return w.wr.Write(meta.TimeSig{
		Numerator:                numerator,
		Denominator:              denominator,
		ClocksPerClick:           clocksPerClick,
		DemiSemiQuaverPerQuarter: demiSemiQuaverPerQuarter,
	})
}

// Track writes the track name aka instrument name meta message
func (w *SMFWriter) Track(track string) error {
	return w.wr.Write(meta.Track(track))
}
<|endoftext|>


<commit_before>//go:build integration
// +build integration

package knative_test

import (
	"context"
	"strings"
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"
	eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
	v1 "knative.dev/pkg/apis/duck/v1"

	fn "knative.dev/func"
	"knative.dev/func/k8s"
	"knative.dev/func/knative"
)

// Basic happy path test of deploy->describe->list->re-deploy->delete.
func TestIntegration(t *testing.T) {
	var err error
	functionName := "fn-testing"

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
	t.Cleanup(cancel)

	cliSet, err := k8s.NewKubernetesClientset()
	if err != nil {
		t.Fatal(err)
	}

	namespace := "knative-integration-test-ns-" + rand.String(5)

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namespace,
		},
		Spec: corev1.NamespaceSpec{},
	}
	_, err = cliSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = cliSet.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) })
	t.Log("created namespace: ", namespace)

	secret := "credentials-secret"
	sc := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: secret,
		},
		Data: map[string][]byte{
			"FUNC_TEST_SC_A": []byte("A"),
			"FUNC_TEST_SC_B": []byte("B"),
		},
		StringData: nil,
		Type:       corev1.SecretTypeOpaque,
	}

	sc, err = cliSet.CoreV1().Secrets(namespace).Create(ctx, sc, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	configMap := "testing-config-map"
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name: configMap,
		},
		Data: map[string]string{"FUNC_TEST_CM_A": "1"},
	}
	cm, err = cliSet.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	trigger := "testing-trigger"
	tr := &eventingv1.Trigger{
		ObjectMeta: metav1.ObjectMeta{
			Name: trigger,
		},
		Spec: eventingv1.TriggerSpec{
			Broker: "testing-broker",
			Subscriber: v1.Destination{Ref: &v1.KReference{
				Kind:       "Service",
				Namespace:  namespace,
				Name:       functionName,
				APIVersion: "serving.knative.dev/v1",
			}},
			Filter: &eventingv1.TriggerFilter{
				Attributes: map[string]string{
					"source": "test-event-source",
					"type":   "test-event-type",
				},
			},
		},
	}

	eventingClient, err := knative.NewEventingClient(namespace)
	if err != nil {
		t.Fatal(err)
	}
	err = eventingClient.CreateTrigger(ctx, tr)
	if err != nil {
		t.Fatal(err)
	}

	minScale := int64(2)
	maxScale := int64(100)

	now := time.Now()
	function := fn.Function{
		SpecVersion: "SNAPSHOT",
		Root:        "/non/existent",
		Name:        functionName,
		Runtime:     "blub",
		Template:    "cloudevents",
		// Basic "echo" Go Function that in addition:
		// * prints environment variables starting which name starts with FUNC_TEST to stderr,
		// * lists files under /etc/cm and /etc/sc stderr.
		Image:       "quay.io/mvasek/func-test-service",
		ImageDigest: "sha256:69251ac335693c4d5a503e9f8a829a25b93bff6e6bddbff5b78971fe668dc71a",
		Created:     now,
		Deploy: fn.DeploySpec{
			Namespace: namespace,
			Labels:    []fn.Label{{Key: ptr("my-label"), Value: ptr("my-label-value")}},
			Options: fn.Options{
				Scale: &fn.ScaleOptions{
					Min: &minScale,
					Max: &maxScale,
				},
			},
		},
		Run: fn.RunSpec{
			Envs: []fn.Env{
				{Name: ptr("FUNC_TEST_VAR"), Value: ptr("nbusr123")},
				{Name: ptr("FUNC_TEST_SC_A"), Value: ptr("{{ secret: " + secret + ":FUNC_TEST_SC_A }}")},
				{Value: ptr("{{configMap:" + configMap + "}}")},
			},
			Volumes: []fn.Volume{
				{Secret: ptr(secret), Path: ptr("/etc/sc")},
				{ConfigMap: ptr(configMap), Path: ptr("/etc/cm")},
			},
		},
	}

	var buff = &knative.SynchronizedBuffer{}
	go func() {
		_ = knative.GetKServiceLogs(ctx, namespace, functionName, function.ImageWithDigest(), &now, buff)
	}()

	deployer := knative.NewDeployer(knative.WithDeployerNamespace(namespace), knative.WithDeployerVerbose(false))

	depRes, err := deployer.Deploy(ctx, function)
	if err != nil {
		t.Fatal(err)
	}

	outStr := buff.String()
	t.Logf("deploy result: %+v", depRes)
	t.Log("function output:\n" + outStr)

	if strings.Count(outStr, "starting app") < int(minScale) {
		t.Errorf("application should be scaled at least to %d pods", minScale)
	}

	// verify that environment variables and volumes works
	if !strings.Contains(outStr, "FUNC_TEST_VAR=nbusr123") {
		t.Error("plain environment variable was not propagated")
	}
	if !strings.Contains(outStr, "FUNC_TEST_SC_A=A") {
		t.Error("environment variables from secret was not propagated")
	}
	if strings.Contains(outStr, "FUNC_TEST_SC_B=") {
		t.Error("environment variables from secret was propagated but should have not been")
	}
	if !strings.Contains(outStr, "FUNC_TEST_CM_A=1") {
		t.Error("environment variable from config-map was not propagated")
	}
	if !strings.Contains(outStr, "/etc/sc/FUNC_TEST_SC_A") || !strings.Contains(outStr, "/etc/sc/FUNC_TEST_SC_A") {
		t.Error("secret was not mounted")
	}
	if !strings.Contains(outStr, "/etc/cm/FUNC_TEST_CM_A") {
		t.Error("config-map was not mounted")
	}

	describer := knative.NewDescriber(namespace, false)
	instance, err := describer.Describe(ctx, functionName)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("instance: %+v", instance)

	// verify that trigger info is included in describe output
	if len(instance.Subscriptions) != 1 {
		t.Error("at exactly one subscription is expected")
		if instance.Subscriptions[0].Broker != "testing-broker" {
			t.Fatal("bad broker")
		}
		if instance.Subscriptions[0].Source != "test-event-source" {
			t.Fatal("bad source")
		}
		if instance.Subscriptions[0].Type != "test-event-type" {
			t.Fatal("bad type")
		}
	}

	lister := knative.NewLister(namespace, false)
	list, err := lister.List(ctx)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("functions list: %+v", list)

	if len(list) != 1 {
		t.Errorf("expected exactly one functions but got: %d", len(list))
	} else {
		if list[0].URL != instance.Route {
			t.Error("URL mismatch")
		}
	}

	buff.Reset()
	t.Setenv("LOCAL_ENV_TO_DEPLOY", "iddqd")
	function.Run.Envs = []fn.Env{
		{Name: ptr("FUNC_TEST_VAR"), Value: ptr("{{ env:LOCAL_ENV_TO_DEPLOY }}")},
		{Value: ptr("{{ secret: " + secret + " }}")},
		{Name: ptr("FUNC_TEST_CM_A_ALIASED"), Value: ptr("{{configMap:" + configMap + ":FUNC_TEST_CM_A}}")},
	}
	depRes, err = deployer.Deploy(ctx, function)
	if err != nil {
		t.Fatal(err)
	}
	outStr = buff.String()
	t.Log("function output:\n" + outStr)

	// verify that environment variables has been changed by re-deploy
	if strings.Contains(outStr, "FUNC_TEST_CM_A=") {
		t.Error("environment variables from previous deployment was not removed")
	}
	if !strings.Contains(outStr, "FUNC_TEST_SC_A=A") || !strings.Contains(outStr, "FUNC_TEST_SC_B=B") {
		t.Error("environment variables were not imported from secret")
	}
	if !strings.Contains(outStr, "FUNC_TEST_VAR=iddqd") {
		t.Error("environment variable was not set from local environment variable")
	}
	if !strings.Contains(outStr, "FUNC_TEST_CM_A_ALIASED=1") {
		t.Error("environment variable was not set from config-map")
	}

	remover := knative.NewRemover(namespace, false)
	err = remover.Remove(ctx, functionName)
	if err != nil {
		t.Fatal(err)
	}

	list, err = lister.List(ctx)
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 0 {
		t.Errorf("expected exactly zero functions but got: %d", len(list))
	}
}

func ptr[T interface{}](s T) *T {
	return &s
}
<commit_msg>test: fix condition and error message (#1361)<commit_after>//go:build integration
// +build integration

package knative_test

import (
	"context"
	"strings"
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"
	eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
	v1 "knative.dev/pkg/apis/duck/v1"

	fn "knative.dev/func"
	"knative.dev/func/k8s"
	"knative.dev/func/knative"
)

// Basic happy path test of deploy->describe->list->re-deploy->delete.
func TestIntegration(t *testing.T) {
	var err error
	functionName := "fn-testing"

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
	t.Cleanup(cancel)

	cliSet, err := k8s.NewKubernetesClientset()
	if err != nil {
		t.Fatal(err)
	}

	namespace := "knative-integration-test-ns-" + rand.String(5)

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namespace,
		},
		Spec: corev1.NamespaceSpec{},
	}
	_, err = cliSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = cliSet.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) })
	t.Log("created namespace: ", namespace)

	secret := "credentials-secret"
	sc := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: secret,
		},
		Data: map[string][]byte{
			"FUNC_TEST_SC_A": []byte("A"),
			"FUNC_TEST_SC_B": []byte("B"),
		},
		StringData: nil,
		Type:       corev1.SecretTypeOpaque,
	}

	sc, err = cliSet.CoreV1().Secrets(namespace).Create(ctx, sc, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	configMap := "testing-config-map"
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name: configMap,
		},
		Data: map[string]string{"FUNC_TEST_CM_A": "1"},
	}
	cm, err = cliSet.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	trigger := "testing-trigger"
	tr := &eventingv1.Trigger{
		ObjectMeta: metav1.ObjectMeta{
			Name: trigger,
		},
		Spec: eventingv1.TriggerSpec{
			Broker: "testing-broker",
			Subscriber: v1.Destination{Ref: &v1.KReference{
				Kind:       "Service",
				Namespace:  namespace,
				Name:       functionName,
				APIVersion: "serving.knative.dev/v1",
			}},
			Filter: &eventingv1.TriggerFilter{
				Attributes: map[string]string{
					"source": "test-event-source",
					"type":   "test-event-type",
				},
			},
		},
	}

	eventingClient, err := knative.NewEventingClient(namespace)
	if err != nil {
		t.Fatal(err)
	}
	err = eventingClient.CreateTrigger(ctx, tr)
	if err != nil {
		t.Fatal(err)
	}

	minScale := int64(2)
	maxScale := int64(100)

	now := time.Now()
	function := fn.Function{
		SpecVersion: "SNAPSHOT",
		Root:        "/non/existent",
		Name:        functionName,
		Runtime:     "blub",
		Template:    "cloudevents",
		// Basic "echo" Go Function that in addition:
		// * prints environment variables starting which name starts with FUNC_TEST to stderr,
		// * lists files under /etc/cm and /etc/sc stderr.
		Image:       "quay.io/mvasek/func-test-service",
		ImageDigest: "sha256:69251ac335693c4d5a503e9f8a829a25b93bff6e6bddbff5b78971fe668dc71a",
		Created:     now,
		Deploy: fn.DeploySpec{
			Namespace: namespace,
			Labels:    []fn.Label{{Key: ptr("my-label"), Value: ptr("my-label-value")}},
			Options: fn.Options{
				Scale: &fn.ScaleOptions{
					Min: &minScale,
					Max: &maxScale,
				},
			},
		},
		Run: fn.RunSpec{
			Envs: []fn.Env{
				{Name: ptr("FUNC_TEST_VAR"), Value: ptr("nbusr123")},
				{Name: ptr("FUNC_TEST_SC_A"), Value: ptr("{{ secret: " + secret + ":FUNC_TEST_SC_A }}")},
				{Value: ptr("{{configMap:" + configMap + "}}")},
			},
			Volumes: []fn.Volume{
				{Secret: ptr(secret), Path: ptr("/etc/sc")},
				{ConfigMap: ptr(configMap), Path: ptr("/etc/cm")},
			},
		},
	}

	var buff = &knative.SynchronizedBuffer{}
	go func() {
		_ = knative.GetKServiceLogs(ctx, namespace, functionName, function.ImageWithDigest(), &now, buff)
	}()

	deployer := knative.NewDeployer(knative.WithDeployerNamespace(namespace), knative.WithDeployerVerbose(false))

	depRes, err := deployer.Deploy(ctx, function)
	if err != nil {
		t.Fatal(err)
	}

	outStr := buff.String()
	t.Logf("deploy result: %+v", depRes)
	t.Log("function output:\n" + outStr)

	if strings.Count(outStr, "starting app") < int(minScale) {
		t.Errorf("application should be scaled at least to %d pods", minScale)
	}

	// verify that environment variables and volumes works
	if !strings.Contains(outStr, "FUNC_TEST_VAR=nbusr123") {
		t.Error("plain environment variable was not propagated")
	}
	if !strings.Contains(outStr, "FUNC_TEST_SC_A=A") {
		t.Error("environment variables from secret was not propagated")
	}
	if strings.Contains(outStr, "FUNC_TEST_SC_B=") {
		t.Error("environment variables from secret was propagated but should have not been")
	}
	if !strings.Contains(outStr, "FUNC_TEST_CM_A=1") {
		t.Error("environment variable from config-map was not propagated")
	}
	if !strings.Contains(outStr, "/etc/sc/FUNC_TEST_SC_A") || !strings.Contains(outStr, "/etc/sc/FUNC_TEST_SC_A") {
		t.Error("secret was not mounted")
	}
	if !strings.Contains(outStr, "/etc/cm/FUNC_TEST_CM_A") {
		t.Error("config-map was not mounted")
	}

	describer := knative.NewDescriber(namespace, false)
	instance, err := describer.Describe(ctx, functionName)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("instance: %+v", instance)

	// verify that trigger info is included in describe output
	if len(instance.Subscriptions) != 1 {
		t.Error("exactly one subscription is expected")
	} else {
		if instance.Subscriptions[0].Broker != "testing-broker" {
			t.Fatal("bad broker")
		}
		if instance.Subscriptions[0].Source != "test-event-source" {
			t.Fatal("bad source")
		}
		if instance.Subscriptions[0].Type != "test-event-type" {
			t.Fatal("bad type")
		}
	}

	lister := knative.NewLister(namespace, false)
	list, err := lister.List(ctx)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("functions list: %+v", list)

	if len(list) != 1 {
		t.Errorf("expected exactly one functions but got: %d", len(list))
	} else {
		if list[0].URL != instance.Route {
			t.Error("URL mismatch")
		}
	}

	buff.Reset()
	t.Setenv("LOCAL_ENV_TO_DEPLOY", "iddqd")
	function.Run.Envs = []fn.Env{
		{Name: ptr("FUNC_TEST_VAR"), Value: ptr("{{ env:LOCAL_ENV_TO_DEPLOY }}")},
		{Value: ptr("{{ secret: " + secret + " }}")},
		{Name: ptr("FUNC_TEST_CM_A_ALIASED"), Value: ptr("{{configMap:" + configMap + ":FUNC_TEST_CM_A}}")},
	}
	depRes, err = deployer.Deploy(ctx, function)
	if err != nil {
		t.Fatal(err)
	}
	outStr = buff.String()
	t.Log("function output:\n" + outStr)

	// verify that environment variables has been changed by re-deploy
	if strings.Contains(outStr, "FUNC_TEST_CM_A=") {
		t.Error("environment variables from previous deployment was not removed")
	}
	if !strings.Contains(outStr, "FUNC_TEST_SC_A=A") || !strings.Contains(outStr, "FUNC_TEST_SC_B=B") {
		t.Error("environment variables were not imported from secret")
	}
	if !strings.Contains(outStr, "FUNC_TEST_VAR=iddqd") {
		t.Error("environment variable was not set from local environment variable")
	}
	if !strings.Contains(outStr, "FUNC_TEST_CM_A_ALIASED=1") {
		t.Error("environment variable was not set from config-map")
	}

	remover := knative.NewRemover(namespace, false)
	err = remover.Remove(ctx, functionName)
	if err != nil {
		t.Fatal(err)
	}

	list, err = lister.List(ctx)
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != 0 {
		t.Errorf("expected exactly zero functions but got: %d", len(list))
	}
}

func ptr[T interface{}](s T) *T {
	return &s
}
<|endoftext|>


<commit_before>package manifest

import (
	"os"
	"net/http"
	"path/filepath"
)

// "dir" is the default "library directory"; returns the parsed version of (in order) the file "repo", the file "dir/repo", or the remote file fetched from the URL "repo"
func Fetch(dir, repo string) (*Manifest2822, error) {
	// try file paths first
	for _, fileName := range []string{
		repo,
		filepath.Join(dir, repo),
	} {
		f, err := os.Open(fileName)
		if err != nil && !os.IsNotExist(err) {
			return nil, err
		}
		if err == nil {
			defer f.Close()
			return Parse(f)
		}
	}

	// must be remote URL
	resp, err := http.Get(repo)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return Parse(resp.Body)

	//return nil, fmt.Errorf("unable to find a manifest named %q (in %q or as a remote URL)", repo, dir)
}
<commit_msg>Adjust the signature of Fetch to include the "repo name"<commit_after>package manifest

import (
	"os"
	"net/http"
	"path"
	"path/filepath"
)

// "dir" is the default "library directory"; returns the parsed version of (in order) the file "repo", the file "dir/repo", or the remote file fetched from the URL "repo"
func Fetch(dir, repo string) (string, *Manifest2822, error) {
	repoName := path.Base(repo)

	// try file paths first
	for _, fileName := range []string{
		repo,
		filepath.Join(dir, repo),
	} {
		f, err := os.Open(fileName)
		if err != nil && !os.IsNotExist(err) {
			return repoName, nil, err
		}
		if err == nil {
			defer f.Close()
			man, err := Parse(f)
			return repoName, man, err
		}
	}

	// must be remote URL
	resp, err := http.Get(repo)
	if err != nil {
		return repoName, nil, err
	}
	defer resp.Body.Close()
	man, err := Parse(resp.Body)
	return repoName, man, err

	//return repoName, nil, fmt.Errorf("unable to find a manifest named %q (in %q or as a remote URL)", repo, dir)
}
<|endoftext|>


<commit_before>package integration

import (
	"os/exec"

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

var _ = Describe("Help Command", func() {
	DescribeTable("displays help for common commands",
		func(setup func() *exec.Cmd) {
			cmd := setup()
			session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(session).Should(Say("Cloud Foundry command line tool"))
			Eventually(session).Should(Say("\\[global options\\] command \\[arguments...\\] \\[command options\\]"))
			Eventually(session).Should(Say("Before getting started:"))
			Eventually(session).Should(Say("config\\s+login,l\\s+target,t"))
			Eventually(session).Should(Say("Global options:"))
			Eventually(session).Should(Exit(0))
		},

		Entry("when cf is run without providing a command or a flag", func() *exec.Cmd {
			return exec.Command("cf")
		}),

		Entry("when cf help is run", func() *exec.Cmd {
			return exec.Command("cf", "help")
		}),

		Entry("when cf is run with -h flag alone", func() *exec.Cmd {
			return exec.Command("cf", "-h")
		}),

		Entry("when cf is run with --help flag alone", func() *exec.Cmd {
			return exec.Command("cf", "--help")
		}),
	)

	DescribeTable("displays help for all commands",
		func(setup func() *exec.Cmd) {
			cmd := setup()
			session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Say("USAGE:"))
			Eventually(session).Should(Say("VERSION:"))
			Eventually(session).Should(Say("GETTING STARTED:"))
			Eventually(session).Should(Say("ENVIRONMENT VARIABLES:"))
			Eventually(session).Should(Say("GLOBAL OPTIONS:"))
			Eventually(session).Should(Exit(0))
		},

		Entry("when cf is run without providing a command or a flag", func() *exec.Cmd {
			Skip("Ask dies what should happen in this case")
			return exec.Command("cf", "-a")
		}),

		Entry("when cf help is run", func() *exec.Cmd {
			return exec.Command("cf", "help", "-a")
		}),

		Entry("when cf is run with -h flag alone", func() *exec.Cmd {
			return exec.Command("cf", "-h", "-a")
		}),

		Entry("when cf is run with --help flag alone", func() *exec.Cmd {
			return exec.Command("cf", "--help", "-a")
		}),
	)

	DescribeTable("displays the help text for a given command",
		func(setup func() (*exec.Cmd, int)) {
			cmd, exitCode := setup()
			session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Say("create-user-provided-service - Make a user-provided service instance available to CF apps"))
			Eventually(session).Should(Say("cf create-user-provided-service SERVICE_INSTANCE \\[-p CREDENTIALS\\] \\[-l SYSLOG_DRAIN_URL\\] \\[-r ROUTE_SERVICE_URL\\]"))
			Eventually(session).Should(Say("-l\\s+URL to which logs for bound applications will be streamed"))
			Eventually(session).Should(Exit(exitCode))
		},

		Entry("when a command is called with the --help flag", func() (*exec.Cmd, int) {
			return exec.Command("cf", "create-user-provided-service", "--help"), 0
		}),

		Entry("when a command is called with the --help flag and command arguments", func() (*exec.Cmd, int) {
			return exec.Command("cf", "create-user-provided-service", "-l", "http://example.com", "--help"), 0
		}),

		Entry("when a command is called with the --help flag and command arguments prior to the command", func() (*exec.Cmd, int) {
			return exec.Command("cf", "-l", "create-user-provided-service", "--help"), 1
		}),

		Entry("when the help command is passed a command name", func() (*exec.Cmd, int) {
			return exec.Command("cf", "help", "create-user-provided-service"), 0
		}),

		Entry("when the --help flag is passed with a command name", func() (*exec.Cmd, int) {
			return exec.Command("cf", "--help", "create-user-provided-service"), 0
		}),

		Entry("when the help command is passed a command alias", func() (*exec.Cmd, int) {
			return exec.Command("cf", "help", "cups"), 0
		}),

		Entry("when the --help flag is passed with a command alias", func() (*exec.Cmd, int) {
			return exec.Command("cf", "--help", "cups"), 0
		}),

		Entry("when the --help flag is passed after a command alias", func() (*exec.Cmd, int) {
			return exec.Command("cf", "cups", "--help"), 0
		}),

		Entry("when an invalid flag is passed", func() (*exec.Cmd, int) {
			return exec.Command("cf", "create-user-provided-service", "--invalid-flag"), 1
		}),

		Entry("when missing required arguments", func() (*exec.Cmd, int) {
			return exec.Command("cf", "create-user-provided-service"), 1
		}),

		Entry("when missing arguments to flags", func() (*exec.Cmd, int) {
			return exec.Command("cf", "create-user-provided-service", "foo", "-l"), 1
		}),
	)

	Context("when the command does not exist", func() {
		DescribeTable("help displays an error message",
			func(command func() *exec.Cmd) {
				session, err := Start(command(), GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(session.Err).Should(Say("'rock' is not a registered command. See 'cf help'"))
				Eventually(session).Should(Exit(1))
			},

			Entry("passing the --help flag", func() *exec.Cmd {
				return exec.Command("cf", "--help", "rock")
			}),

			Entry("calling the help command directly", func() *exec.Cmd {
				return exec.Command("cf", "help", "rock")
			}),
		)

	})
})
<commit_msg>integration tests for help command to display timeout environment variables<commit_after>package integration

import (
	"os/exec"

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

var _ = Describe("Help Command", func() {
	DescribeTable("displays help for common commands",
		func(setup func() *exec.Cmd) {
			cmd := setup()
			session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(session).Should(Say("Cloud Foundry command line tool"))
			Eventually(session).Should(Say("\\[global options\\] command \\[arguments...\\] \\[command options\\]"))
			Eventually(session).Should(Say("Before getting started:"))
			Eventually(session).Should(Say("config\\s+login,l\\s+target,t"))
			Eventually(session).Should(Say("Global options:"))
			Eventually(session).Should(Exit(0))
		},

		Entry("when cf is run without providing a command or a flag", func() *exec.Cmd {
			return exec.Command("cf")
		}),

		Entry("when cf help is run", func() *exec.Cmd {
			return exec.Command("cf", "help")
		}),

		Entry("when cf is run with -h flag alone", func() *exec.Cmd {
			return exec.Command("cf", "-h")
		}),

		Entry("when cf is run with --help flag alone", func() *exec.Cmd {
			return exec.Command("cf", "--help")
		}),
	)

	DescribeTable("displays help for all commands",
		func(setup func() *exec.Cmd) {
			cmd := setup()
			session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Say("USAGE:"))
			Eventually(session).Should(Say("VERSION:"))
			Eventually(session).Should(Say("GETTING STARTED:"))
			Eventually(session).Should(Say("ENVIRONMENT VARIABLES:"))
			Eventually(session).Should(Say("GLOBAL OPTIONS:"))
			Eventually(session).Should(Exit(0))
		},

		Entry("when cf is run without providing a command or a flag", func() *exec.Cmd {
			Skip("Ask dies what should happen in this case")
			return exec.Command("cf", "-a")
		}),

		Entry("when cf help is run", func() *exec.Cmd {
			return exec.Command("cf", "help", "-a")
		}),

		Entry("when cf is run with -h flag alone", func() *exec.Cmd {
			return exec.Command("cf", "-h", "-a")
		}),

		Entry("when cf is run with --help flag alone", func() *exec.Cmd {
			return exec.Command("cf", "--help", "-a")
		}),
	)

	Context("displays the help text for a given command", func() {
		DescribeTable("displays the help",
			func(setup func() (*exec.Cmd, int)) {
				cmd, exitCode := setup()
				session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(session).Should(Say("NAME:"))
				Eventually(session).Should(Say("create-user-provided-service - Make a user-provided service instance available to CF apps"))
				Eventually(session).Should(Say("cf create-user-provided-service SERVICE_INSTANCE \\[-p CREDENTIALS\\] \\[-l SYSLOG_DRAIN_URL\\] \\[-r ROUTE_SERVICE_URL\\]"))
				Eventually(session).Should(Say("-l\\s+URL to which logs for bound applications will be streamed"))
				Eventually(session).Should(Exit(exitCode))
			},

			Entry("when a command is called with the --help flag", func() (*exec.Cmd, int) {
				return exec.Command("cf", "create-user-provided-service", "--help"), 0
			}),

			Entry("when a command is called with the --help flag and command arguments", func() (*exec.Cmd, int) {
				return exec.Command("cf", "create-user-provided-service", "-l", "http://example.com", "--help"), 0
			}),

			Entry("when a command is called with the --help flag and command arguments prior to the command", func() (*exec.Cmd, int) {
				return exec.Command("cf", "-l", "create-user-provided-service", "--help"), 1
			}),

			Entry("when the help command is passed a command name", func() (*exec.Cmd, int) {
				return exec.Command("cf", "help", "create-user-provided-service"), 0
			}),

			Entry("when the --help flag is passed with a command name", func() (*exec.Cmd, int) {
				return exec.Command("cf", "--help", "create-user-provided-service"), 0
			}),

			Entry("when the help command is passed a command alias", func() (*exec.Cmd, int) {
				return exec.Command("cf", "help", "cups"), 0
			}),

			Entry("when the --help flag is passed with a command alias", func() (*exec.Cmd, int) {
				return exec.Command("cf", "--help", "cups"), 0
			}),

			Entry("when the --help flag is passed after a command alias", func() (*exec.Cmd, int) {
				return exec.Command("cf", "cups", "--help"), 0
			}),

			Entry("when an invalid flag is passed", func() (*exec.Cmd, int) {
				return exec.Command("cf", "create-user-provided-service", "--invalid-flag"), 1
			}),

			Entry("when missing required arguments", func() (*exec.Cmd, int) {
				return exec.Command("cf", "create-user-provided-service"), 1
			}),

			Entry("when missing arguments to flags", func() (*exec.Cmd, int) {
				return exec.Command("cf", "create-user-provided-service", "foo", "-l"), 1
			}),
		)

		Context("when the command uses timeout environment variables", func() {
			DescribeTable("shows the CF_STAGING_TIMEOUT and CF_STARTUP_TIMEOUT environment variables",
				func(setup func() (*exec.Cmd, int)) {
					cmd, exitCode := setup()
					session, err := Start(cmd, GinkgoWriter, GinkgoWriter)
					Expect(err).NotTo(HaveOccurred())

					Eventually(session).Should(Say(`
ENVIRONMENT:
   CF_STAGING_TIMEOUT=15        Max wait time for buildpack staging, in minutes
   CF_STARTUP_TIMEOUT=5         Max wait time for app instance startup, in minutes
`))
					Eventually(session).Should(Exit(exitCode))
				},

				Entry("cf push", func() (*exec.Cmd, int) {
					return exec.Command("cf", "h", "push"), 0
				}),

				Entry("cf start", func() (*exec.Cmd, int) {
					return exec.Command("cf", "h", "start"), 0
				}),

				Entry("cf restart", func() (*exec.Cmd, int) {
					return exec.Command("cf", "h", "restart"), 0
				}),

				Entry("cf restage", func() (*exec.Cmd, int) {
					return exec.Command("cf", "h", "restage"), 0
				}),

				Entry("cf copy-source", func() (*exec.Cmd, int) {
					return exec.Command("cf", "h", "copy-source"), 0
				}),
			)
		})
	})

	Context("when the command does not exist", func() {
		DescribeTable("help displays an error message",
			func(command func() *exec.Cmd) {
				session, err := Start(command(), GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(session.Err).Should(Say("'rock' is not a registered command. See 'cf help'"))
				Eventually(session).Should(Exit(1))
			},

			Entry("passing the --help flag", func() *exec.Cmd {
				return exec.Command("cf", "--help", "rock")
			}),

			Entry("calling the help command directly", func() *exec.Cmd {
				return exec.Command("cf", "help", "rock")
			}),
		)

	})
})
<|endoftext|>