<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"strings"

	keybase1 "github.com/keybase/client/go/protocol"
)

// IsSocialAssertion returns true for strings that are valid
// social assertions.  They do not need to be normalized, so
// user@twitter and twitter:user will work, as will
// USER@Twitter.
func IsSocialAssertion(s string) bool {
	_, ok := NormalizeSocialAssertion(s)
	return ok
}

// NormalizeSocialAssertion creates a SocialAssertion from its
// input and normalizes it.  The service name will be lowercased.
// If the service is case-insensitive, then the username will also
// be lowercased.  Colon assertions (twitter:user) will be
// transformed to the user@twitter format.  Only registered
// services are allowed.
func NormalizeSocialAssertion(s string) (keybase1.SocialAssertion, bool) {
	if strings.Count(s, ":")+strings.Count(s, "@") != 1 {
		return keybase1.SocialAssertion{}, false
	}

	var name, service string

	if strings.Contains(s, ":") {
		pieces := strings.Split(s, ":")
		service = pieces[0]
		name = pieces[1]
	} else {
		pieces := strings.Split(s, "@")
		name = pieces[0]
		service = pieces[1]
	}

	service = strings.ToLower(service)
	if !ValidSocialNetwork(service) {
		return keybase1.SocialAssertion{}, false
	}

	st := GetServiceType(service)
	if !st.CaseSensitiveUsername() {
		name = strings.ToLower(name)
	}

	return keybase1.SocialAssertion{
		User:    name,
		Service: keybase1.SocialAssertionService(service),
	}, true
}
<commit_msg>Added helper method to generate social assertion string (#2585)<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"fmt"
	"strings"

	keybase1 "github.com/keybase/client/go/protocol"
)

// IsSocialAssertion returns true for strings that are valid
// social assertions.  They do not need to be normalized, so
// user@twitter and twitter:user will work, as will
// USER@Twitter.
func IsSocialAssertion(s string) bool {
	_, ok := NormalizeSocialAssertion(s)
	return ok
}

// NormalizeSocialAssertion creates a SocialAssertion from its
// input and normalizes it.  The service name will be lowercased.
// If the service is case-insensitive, then the username will also
// be lowercased.  Colon assertions (twitter:user) will be
// transformed to the user@twitter format.  Only registered
// services are allowed.
func NormalizeSocialAssertion(s string) (keybase1.SocialAssertion, bool) {
	if strings.Count(s, ":")+strings.Count(s, "@") != 1 {
		return keybase1.SocialAssertion{}, false
	}

	var name, service string

	if strings.Contains(s, ":") {
		pieces := strings.Split(s, ":")
		service = pieces[0]
		name = pieces[1]
	} else {
		pieces := strings.Split(s, "@")
		name = pieces[0]
		service = pieces[1]
	}

	service = strings.ToLower(service)
	if !ValidSocialNetwork(service) {
		return keybase1.SocialAssertion{}, false
	}

	st := GetServiceType(service)
	if !st.CaseSensitiveUsername() {
		name = strings.ToLower(name)
	}

	return keybase1.SocialAssertion{
		User:    name,
		Service: keybase1.SocialAssertionService(service),
	}, true
}

// SocialAssertionToString returns a string that represents a social assertion.
func SocialAssertionToString(assertion keybase1.SocialAssertion) string {
	return fmt.Sprintf("%s@%s", assertion.User, assertion.Service)
}
<|endoftext|>


<commit_before>// Copyright 2021 The Bazel Authors. All rights reserved.
//
// 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 (
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/google/go-github/v36/github"
	"golang.org/x/mod/semver"
	"golang.org/x/oauth2"
)

var prepareCmd = command{
	name:        "prepare",
	description: "prepares a GitHub release with notes and attached archive",
	help: `prepare -rnotes=file -version=version -githubtoken=token [-mirror]

'prepare' performs most tasks related to a rules_go release. It does everything
except publishing and tagging the release, which must be done manually,
with review. Specifically, prepare does the following:

* Creates the release branch if it doesn't exist locally. Release branches
  have names like "release-X.Y" where X and Y are the major and minor version
  numbers.
* Checks that RULES_GO_VERSION is set in go/def.bzl on the local release branch
  for the minor version being released. RULES_GO_VERSION must be a sematic
  version without the "v" prefix that Go uses, like "1.2.4". It must match
  the -version flag, which does require the "v" prefix.
* Creates an archive zip file from the tip of the local release branch.
* Creates or updates a draft GitHub release with the given release notes.
  http_archive boilerplate is generated and appended to the release notes.
* Uploads and attaches the release archive to the GitHub release.
* Uploads the release archive to mirror.bazel.build. If the file already exists,
  it may be manually removed with 'gsutil rm gs://bazel-mirror/<github-url>'
  or manually updated with 'gsutil cp <file> gs://bazel-mirror/<github-url>'.
  This step may be skipped by setting -mirror=false.

After these steps are completed successfully, 'prepare' prompts the user to
check that CI passes, then review and publish the release.

Note that 'prepare' does not update boilerplate in WORKSPACE or README.rst for
either rules_go or Gazelle.
`,
}

func init() {
	// break init cycle
	prepareCmd.run = runPrepare
}

func runPrepare(ctx context.Context, stderr io.Writer, args []string) error {
	// Parse arguments.
	flags := flag.NewFlagSet("releaser prepare", flag.ContinueOnError)
	var rnotesPath, version string
	var githubToken githubTokenFlag
	var uploadToMirror bool
	flags.Var(&githubToken, "githubtoken", "GitHub personal access token or path to a file containing it")
	flags.BoolVar(&uploadToMirror, "mirror", true, "whether to upload dependency archives to mirror.bazel.build")
	flags.StringVar(&rnotesPath, "rnotes", "", "Name of file containing release notes in Markdown")
	flags.StringVar(&version, "version", "", "Version to release")
	if err := flags.Parse(args); err != nil {
		return err
	}
	if flags.NArg() > 0 {
		return usageErrorf(&prepareCmd, "No arguments expected")
	}
	if githubToken == "" {
		return usageErrorf(&prepareCmd, "-githubtoken must be set")
	}
	if rnotesPath == "" {
		return usageErrorf(&prepareCmd, "-rnotes must be set")
	}
	if version == "" {
		return usageErrorf(&prepareCmd, "-version must be set")
	}
	if semver.Canonical(version) != version || semver.Prerelease(version) != "" || semver.Build(version) != "" {
		return usageErrorf(&prepareCmd, "-version must be a canonical version, like v1.2.3")
	}

	ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(githubToken)})
	tc := oauth2.NewClient(ctx, ts)
	gh := &githubClient{Client: github.NewClient(tc)}

	// Get the GitHub release.
	fmt.Fprintf(stderr, "checking if release %s exists...\n", version)
	release, err := gh.getReleaseByTagIncludingDraft(ctx, "bazelbuild", "rules_go", version)
	if err != nil && errors.Is(err, errReleaseNotFound) {
		return err
	}
	if release != nil && !release.GetDraft() {
		return fmt.Errorf("release %s was already published", version)
	}

	// Check that RULES_GO_VERSION is set correctly on the release branch.
	// If this is a minor release (x.y.0), create the release branch if it
	// does not exist.
	fmt.Fprintf(stderr, "checking RULES_GO_VERSION...\n")
	rootDir, err := repoRoot()
	if err != nil {
		return err
	}
	if err := checkNoGitChanges(ctx, rootDir); err != nil {
		return err
	}
	majorMinor := semver.MajorMinor(version)
	isMinorRelease := semver.Canonical(majorMinor) == version
	branchName := "release-" + majorMinor[len("v"):]
	if !gitBranchExists(ctx, rootDir, branchName) {
		if !isMinorRelease {
			return fmt.Errorf("release branch %q does not exist locally. Fetch it, set RULES_GO_VERSION, add commits, and run this command again.")
		}
		if err := checkRulesGoVersion(ctx, rootDir, "HEAD", version); err != nil {
			return err
		}
		fmt.Fprintf(stderr, "creating branch %s...\n", branchName)
		if err := gitCreateBranch(ctx, rootDir, branchName, "HEAD"); err != nil {
			return err
		}
	} else {
		if err := checkRulesGoVersion(ctx, rootDir, branchName, version); err != nil {
			return err
		}
	}

	// Create an archive.
	fmt.Fprintf(stderr, "creating archive...\n")
	arcFile, err := os.CreateTemp("", "rules_go-%s-*.zip")
	if err != nil {
		return err
	}
	arcName := arcFile.Name()
	arcFile.Close()
	defer func() {
		if rerr := os.Remove(arcName); err == nil && rerr != nil {
			err = rerr
		}
	}()
	if err := gitCreateArchive(ctx, rootDir, branchName, arcName); err != nil {
		return err
	}
	arcSum, err := sha256SumFile(arcName)
	if err != nil {
		return err
	}

	// Read release notes, append boilerplate.
	rnotesData, err := os.ReadFile(rnotesPath)
	if err != nil {
		return err
	}
	rnotesData = bytes.TrimSpace(rnotesData)
	goVersion, err := findLatestGoVersion()
	if err != nil {
		return err
	}
	boilerplate := genBoilerplate(version, arcSum, goVersion)
	rnotesStr := string(rnotesData) + "\n\n## `WORKSPACE` code\n\n```\n" + boilerplate + "\n```\n"

	// Push the release branch.
	fmt.Fprintf(stderr, "pushing branch %s to origin...\n", branchName)
	if err := gitPushBranch(ctx, rootDir, branchName); err != nil {
		return err
	}

	// Upload to mirror.bazel.build.
	arcGHURLWithoutScheme := fmt.Sprintf("github.com/bazelbuild/rules_go/releases/download/%[1]s/rules_go-%[1]s.zip", version)
	if uploadToMirror {
		fmt.Fprintf(stderr, "uploading archive to mirror.bazel.build...\n")
		if err := copyFileToMirror(ctx, arcGHURLWithoutScheme, arcName); err != nil {
			return err
		}
	}

	// Create or update the GitHub release.
	if release == nil {
		fmt.Fprintf(stderr, "creating draft release...\n")
		draft := true
		release = &github.RepositoryRelease{
			TagName:         &version,
			TargetCommitish: &branchName,
			Name:            &version,
			Body:            &rnotesStr,
			Draft:           &draft,
		}
		if release, _, err = gh.Repositories.CreateRelease(ctx, "bazelbuild", "rules_go", release); err != nil {
			return err
		}
	} else {
		fmt.Fprintf(stderr, "updating release...\n")
		release.Body = &rnotesStr
		if release, _, err = gh.Repositories.EditRelease(ctx, "bazelbuild", "rules_go", release.GetID(), release); err != nil {
			return err
		}
		for _, asset := range release.Assets {
			if _, err := gh.Repositories.DeleteReleaseAsset(ctx, "bazelbuild", "rules_go", asset.GetID()); err != nil {
				return err
			}
		}
	}
	arcFile, err = os.Open(arcName)
	if err != nil {
		return err
	}
	defer arcFile.Close()
	uploadOpts := &github.UploadOptions{
		Name:      "rules_go-" + version + ".zip",
		MediaType: "application/zip",
	}
	if _, _, err := gh.Repositories.UploadReleaseAsset(ctx, "bazelbuild", "rules_go", release.GetID(), uploadOpts, arcFile); err != nil {
		return err
	}

	testURL := fmt.Sprintf("https://buildkite.com/bazel/rules-go-golang/builds?branch=%s", branchName)
	fmt.Fprintf(stderr, `
Release %s has been prepared and uploaded.

* Ensure that all tests pass in CI at %s.
* Review and publish the release at %s.
* Update README.rst and WORKSPACE if necessary.
`, version, testURL, release.GetHTMLURL())

	return nil
}

func checkRulesGoVersion(ctx context.Context, dir, refName, version string) error {
	data, err := gitCatFile(ctx, dir, refName, "go/def.bzl")
	if err != nil {
		return err
	}
	rulesGoVersionStr := []byte(fmt.Sprintf(`RULES_GO_VERSION = "%s"`, version[len("v"):]))
	if !bytes.Contains(data, rulesGoVersionStr) {
		return fmt.Errorf("RULES_GO_VERSION was not set to %q in go/def.bzl. Set it, add commits, and run this command again.")
	}
	return nil
}
<commit_msg>releaser: update prepare to expect errFeleaseNotFound (#2980)<commit_after>// Copyright 2021 The Bazel Authors. All rights reserved.
//
// 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 (
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/google/go-github/v36/github"
	"golang.org/x/mod/semver"
	"golang.org/x/oauth2"
)

var prepareCmd = command{
	name:        "prepare",
	description: "prepares a GitHub release with notes and attached archive",
	help: `prepare -rnotes=file -version=version -githubtoken=token [-mirror]

'prepare' performs most tasks related to a rules_go release. It does everything
except publishing and tagging the release, which must be done manually,
with review. Specifically, prepare does the following:

* Creates the release branch if it doesn't exist locally. Release branches
  have names like "release-X.Y" where X and Y are the major and minor version
  numbers.
* Checks that RULES_GO_VERSION is set in go/def.bzl on the local release branch
  for the minor version being released. RULES_GO_VERSION must be a sematic
  version without the "v" prefix that Go uses, like "1.2.4". It must match
  the -version flag, which does require the "v" prefix.
* Creates an archive zip file from the tip of the local release branch.
* Creates or updates a draft GitHub release with the given release notes.
  http_archive boilerplate is generated and appended to the release notes.
* Uploads and attaches the release archive to the GitHub release.
* Uploads the release archive to mirror.bazel.build. If the file already exists,
  it may be manually removed with 'gsutil rm gs://bazel-mirror/<github-url>'
  or manually updated with 'gsutil cp <file> gs://bazel-mirror/<github-url>'.
  This step may be skipped by setting -mirror=false.

After these steps are completed successfully, 'prepare' prompts the user to
check that CI passes, then review and publish the release.

Note that 'prepare' does not update boilerplate in WORKSPACE or README.rst for
either rules_go or Gazelle.
`,
}

func init() {
	// break init cycle
	prepareCmd.run = runPrepare
}

func runPrepare(ctx context.Context, stderr io.Writer, args []string) error {
	// Parse arguments.
	flags := flag.NewFlagSet("releaser prepare", flag.ContinueOnError)
	var rnotesPath, version string
	var githubToken githubTokenFlag
	var uploadToMirror bool
	flags.Var(&githubToken, "githubtoken", "GitHub personal access token or path to a file containing it")
	flags.BoolVar(&uploadToMirror, "mirror", true, "whether to upload dependency archives to mirror.bazel.build")
	flags.StringVar(&rnotesPath, "rnotes", "", "Name of file containing release notes in Markdown")
	flags.StringVar(&version, "version", "", "Version to release")
	if err := flags.Parse(args); err != nil {
		return err
	}
	if flags.NArg() > 0 {
		return usageErrorf(&prepareCmd, "No arguments expected")
	}
	if githubToken == "" {
		return usageErrorf(&prepareCmd, "-githubtoken must be set")
	}
	if rnotesPath == "" {
		return usageErrorf(&prepareCmd, "-rnotes must be set")
	}
	if version == "" {
		return usageErrorf(&prepareCmd, "-version must be set")
	}
	if semver.Canonical(version) != version || semver.Prerelease(version) != "" || semver.Build(version) != "" {
		return usageErrorf(&prepareCmd, "-version must be a canonical version, like v1.2.3")
	}

	ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(githubToken)})
	tc := oauth2.NewClient(ctx, ts)
	gh := &githubClient{Client: github.NewClient(tc)}

	// Get the GitHub release.
	fmt.Fprintf(stderr, "checking if release %s exists...\n", version)
	release, err := gh.getReleaseByTagIncludingDraft(ctx, "bazelbuild", "rules_go", version)
	if err != nil && !errors.Is(err, errReleaseNotFound) {
		return err
	}
	if release != nil && !release.GetDraft() {
		return fmt.Errorf("release %s was already published", version)
	}

	// Check that RULES_GO_VERSION is set correctly on the release branch.
	// If this is a minor release (x.y.0), create the release branch if it
	// does not exist.
	fmt.Fprintf(stderr, "checking RULES_GO_VERSION...\n")
	rootDir, err := repoRoot()
	if err != nil {
		return err
	}
	if err := checkNoGitChanges(ctx, rootDir); err != nil {
		return err
	}
	majorMinor := semver.MajorMinor(version)
	isMinorRelease := semver.Canonical(majorMinor) == version
	branchName := "release-" + majorMinor[len("v"):]
	if !gitBranchExists(ctx, rootDir, branchName) {
		if !isMinorRelease {
			return fmt.Errorf("release branch %q does not exist locally. Fetch it, set RULES_GO_VERSION, add commits, and run this command again.")
		}
		if err := checkRulesGoVersion(ctx, rootDir, "HEAD", version); err != nil {
			return err
		}
		fmt.Fprintf(stderr, "creating branch %s...\n", branchName)
		if err := gitCreateBranch(ctx, rootDir, branchName, "HEAD"); err != nil {
			return err
		}
	} else {
		if err := checkRulesGoVersion(ctx, rootDir, branchName, version); err != nil {
			return err
		}
	}

	// Create an archive.
	fmt.Fprintf(stderr, "creating archive...\n")
	arcFile, err := os.CreateTemp("", "rules_go-%s-*.zip")
	if err != nil {
		return err
	}
	arcName := arcFile.Name()
	arcFile.Close()
	defer func() {
		if rerr := os.Remove(arcName); err == nil && rerr != nil {
			err = rerr
		}
	}()
	if err := gitCreateArchive(ctx, rootDir, branchName, arcName); err != nil {
		return err
	}
	arcSum, err := sha256SumFile(arcName)
	if err != nil {
		return err
	}

	// Read release notes, append boilerplate.
	rnotesData, err := os.ReadFile(rnotesPath)
	if err != nil {
		return err
	}
	rnotesData = bytes.TrimSpace(rnotesData)
	goVersion, err := findLatestGoVersion()
	if err != nil {
		return err
	}
	boilerplate := genBoilerplate(version, arcSum, goVersion)
	rnotesStr := string(rnotesData) + "\n\n## `WORKSPACE` code\n\n```\n" + boilerplate + "\n```\n"

	// Push the release branch.
	fmt.Fprintf(stderr, "pushing branch %s to origin...\n", branchName)
	if err := gitPushBranch(ctx, rootDir, branchName); err != nil {
		return err
	}

	// Upload to mirror.bazel.build.
	arcGHURLWithoutScheme := fmt.Sprintf("github.com/bazelbuild/rules_go/releases/download/%[1]s/rules_go-%[1]s.zip", version)
	if uploadToMirror {
		fmt.Fprintf(stderr, "uploading archive to mirror.bazel.build...\n")
		if err := copyFileToMirror(ctx, arcGHURLWithoutScheme, arcName); err != nil {
			return err
		}
	}

	// Create or update the GitHub release.
	if release == nil {
		fmt.Fprintf(stderr, "creating draft release...\n")
		draft := true
		release = &github.RepositoryRelease{
			TagName:         &version,
			TargetCommitish: &branchName,
			Name:            &version,
			Body:            &rnotesStr,
			Draft:           &draft,
		}
		if release, _, err = gh.Repositories.CreateRelease(ctx, "bazelbuild", "rules_go", release); err != nil {
			return err
		}
	} else {
		fmt.Fprintf(stderr, "updating release...\n")
		release.Body = &rnotesStr
		if release, _, err = gh.Repositories.EditRelease(ctx, "bazelbuild", "rules_go", release.GetID(), release); err != nil {
			return err
		}
		for _, asset := range release.Assets {
			if _, err := gh.Repositories.DeleteReleaseAsset(ctx, "bazelbuild", "rules_go", asset.GetID()); err != nil {
				return err
			}
		}
	}
	arcFile, err = os.Open(arcName)
	if err != nil {
		return err
	}
	defer arcFile.Close()
	uploadOpts := &github.UploadOptions{
		Name:      "rules_go-" + version + ".zip",
		MediaType: "application/zip",
	}
	if _, _, err := gh.Repositories.UploadReleaseAsset(ctx, "bazelbuild", "rules_go", release.GetID(), uploadOpts, arcFile); err != nil {
		return err
	}

	testURL := fmt.Sprintf("https://buildkite.com/bazel/rules-go-golang/builds?branch=%s", branchName)
	fmt.Fprintf(stderr, `
Release %s has been prepared and uploaded.

* Ensure that all tests pass in CI at %s.
* Review and publish the release at %s.
* Update README.rst and WORKSPACE if necessary.
`, version, testURL, release.GetHTMLURL())

	return nil
}

func checkRulesGoVersion(ctx context.Context, dir, refName, version string) error {
	data, err := gitCatFile(ctx, dir, refName, "go/def.bzl")
	if err != nil {
		return err
	}
	rulesGoVersionStr := []byte(fmt.Sprintf(`RULES_GO_VERSION = "%s"`, version[len("v"):]))
	if !bytes.Contains(data, rulesGoVersionStr) {
		return fmt.Errorf("RULES_GO_VERSION was not set to %q in go/def.bzl. Set it, add commits, and run this command again.")
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Vitess 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 wrangler

import (
	"context"
	"fmt"
	"strings"

	"vitess.io/vitess/go/vt/log"
	querypb "vitess.io/vitess/go/vt/proto/query"
	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	"vitess.io/vitess/go/vt/sqlparser"

	"github.com/olekukonko/tablewriter"
)

// vexecPlan contains the final query to be sent to the tablets
type vexecPlan struct {
	opcode      int
	parsedQuery *sqlparser.ParsedQuery
}

// vexecPlannerParams controls how some queries/columns are handled
type vexecPlannerParams struct {
	dbNameColumn         string
	workflowColumn       string
	immutableColumnNames []string
	updatableColumnNames []string
	updateTemplates      []string
}

// vexecPlanner generates and executes a plan
type vexecPlanner interface {
	params() *vexecPlannerParams
	exec(ctx context.Context, masterAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error)
	dryRun() error
}

// vreplicationPlanner is a vexecPlanner implementation, specific to _vt.vreplication table
type vreplicationPlanner struct {
	vx *vexec
	d  *vexecPlannerParams
}

func newVreplicationPlanner(vx *vexec) vexecPlanner {
	return &vreplicationPlanner{
		vx: vx,
		d: &vexecPlannerParams{
			dbNameColumn:         "db_name",
			workflowColumn:       "workflow",
			immutableColumnNames: []string{"id"},
			updatableColumnNames: []string{},
		},
	}
}
func (p vreplicationPlanner) params() *vexecPlannerParams { return p.d }
func (p vreplicationPlanner) exec(
	ctx context.Context, masterAlias *topodatapb.TabletAlias, query string,
) (*querypb.QueryResult, error) {
	qr, err := p.vx.wr.VReplicationExec(ctx, masterAlias, query)
	if err != nil {
		return nil, err
	}
	if qr.RowsAffected == 0 {
		log.Infof("no matching streams found for workflow %s, tablet %s, query %s", p.vx.workflow, masterAlias, query)
	}
	return qr, nil
}
func (p vreplicationPlanner) dryRun() error {
	rsr, err := p.vx.wr.getStreams(p.vx.ctx, p.vx.workflow, p.vx.keyspace)
	if err != nil {
		return err
	}

	p.vx.wr.Logger().Printf("Query: %s\nwill be run on the following streams in keyspace %s for workflow %s:\n\n",
		p.vx.plannedQuery, p.vx.keyspace, p.vx.workflow)
	tableString := &strings.Builder{}
	table := tablewriter.NewWriter(tableString)
	table.SetHeader([]string{"Tablet", "ID", "BinLogSource", "State", "DBName", "Current GTID", "MaxReplicationLag"})
	for _, master := range p.vx.masters {
		key := fmt.Sprintf("%s/%s", master.Shard, master.AliasString())
		for _, stream := range rsr.ShardStatuses[key].MasterReplicationStatuses {
			table.Append([]string{key, fmt.Sprintf("%d", stream.ID), stream.Bls.String(), stream.State, stream.DBName, stream.Pos, fmt.Sprintf("%d", stream.MaxReplicationLag)})
		}
	}
	table.SetAutoMergeCellsByColumnIndex([]int{0})
	table.SetRowLine(true)
	table.Render()
	p.vx.wr.Logger().Printf(tableString.String())
	p.vx.wr.Logger().Printf("\n\n")

	return nil
}

// vreplicationPlanner is a vexecPlanner implementation, specific to _vt.schema_migrations table
type schemaMigrationsPlanner struct {
	vx *vexec
	d  *vexecPlannerParams
}

func newSchemaMigrationsPlanner(vx *vexec) vexecPlanner {
	return &schemaMigrationsPlanner{
		vx: vx,
		d: &vexecPlannerParams{
			dbNameColumn:   "mysql_schema",
			workflowColumn: "migration_uuid",
			updateTemplates: []string{
				`update _vt.schema_migrations set migration_status='val1'`,
				`update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2'`,
				`update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2' and shard='val3'`,
			},
		},
	}
}
func (p schemaMigrationsPlanner) params() *vexecPlannerParams { return p.d }
func (p schemaMigrationsPlanner) exec(ctx context.Context, masterAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error) {
	qr, err := p.vx.wr.GenericVExec(ctx, masterAlias, query, p.vx.workflow, p.vx.keyspace)
	if err != nil {
		return nil, err
	}
	if qr.RowsAffected == 0 {
		return nil, fmt.Errorf("\nno matching migrations found for workflow %s, tablet %s, query %s", p.vx.workflow, masterAlias, query)
	}
	return qr, nil
}
func (p schemaMigrationsPlanner) dryRun() error { return nil }

// make sure these planners implement vexecPlanner interface
var _ vexecPlanner = vreplicationPlanner{}
var _ vexecPlanner = schemaMigrationsPlanner{}

const (
	updateQuery = iota
	deleteQuery
	selectQuery
)

// extractTableName returns the qualified table name (e.g. "_vt.schema_migrations") from a SELECT/DELETE/UPDATE statement
func extractTableName(stmt sqlparser.Statement) (string, error) {
	switch stmt := stmt.(type) {
	case *sqlparser.Update:
		return sqlparser.String(stmt.TableExprs), nil
	case *sqlparser.Delete:
		return sqlparser.String(stmt.TableExprs), nil
	case *sqlparser.Select:
		return sqlparser.String(stmt.From), nil
	}
	return "", fmt.Errorf("query not supported by vexec: %+v", sqlparser.String(stmt))
}

// qualifiedTableName qualifies a table with "_vt." schema
func qualifiedTableName(tableName string) string {
	return fmt.Sprintf("%s.%s", vexecTableQualifier, tableName)
}

// getPlanner returns a specific planner appropriate for the queried table
func (vx *vexec) getPlanner() error {
	switch vx.tableName {
	case qualifiedTableName(schemaMigrationsTableName):
		vx.planner = newSchemaMigrationsPlanner(vx)
	case qualifiedTableName(vreplicationTableName):
		vx.planner = newVreplicationPlanner(vx)
	default:
		return fmt.Errorf("table not supported by vexec: %v", vx.tableName)
	}
	return nil
}

// buildPlan builds an execution plan. More specifically, it generates the query which is then sent to
// relevant vttablet servers
func (vx *vexec) buildPlan() (plan *vexecPlan, err error) {
	switch stmt := vx.stmt.(type) {
	case *sqlparser.Update:
		plan, err = vx.buildUpdatePlan(vx.planner, stmt)
	case *sqlparser.Delete:
		plan, err = vx.buildDeletePlan(vx.planner, stmt)
	case *sqlparser.Select:
		plan, err = vx.buildSelectPlan(vx.planner, stmt)
	default:
		return nil, fmt.Errorf("query not supported by vexec: %s", sqlparser.String(stmt))
	}
	return plan, err
}

// analyzeWhereEqualsColumns identifies column names in a WHERE clause that have a comparison expression
func (vx *vexec) analyzeWhereEqualsColumns(where *sqlparser.Where) []string {
	var cols []string
	if where == nil {
		return cols
	}
	exprs := sqlparser.SplitAndExpression(nil, where.Expr)
	for _, expr := range exprs {
		switch expr := expr.(type) {
		case *sqlparser.ComparisonExpr:
			qualifiedName, ok := expr.Left.(*sqlparser.ColName)
			if ok {
				cols = append(cols, qualifiedName.Name.String())
			}
		}
	}
	return cols
}

// addDefaultWheres modifies the query to add, if appropriate, the workflow and DB-name column modifiers
func (vx *vexec) addDefaultWheres(planner vexecPlanner, where *sqlparser.Where) *sqlparser.Where {
	cols := vx.analyzeWhereEqualsColumns(where)
	var hasDBName, hasWorkflow bool
	plannerParams := planner.params()
	for _, col := range cols {
		if col == plannerParams.dbNameColumn {
			hasDBName = true
		} else if col == plannerParams.workflowColumn {
			hasWorkflow = true
		}
	}
	newWhere := where
	if !hasDBName {
		expr := &sqlparser.ComparisonExpr{
			Left:     &sqlparser.ColName{Name: sqlparser.NewColIdent(plannerParams.dbNameColumn)},
			Operator: sqlparser.EqualStr,
			Right:    sqlparser.NewStrLiteral([]byte(vx.masters[0].DbName())),
		}
		if newWhere == nil {
			newWhere = &sqlparser.Where{
				Type: sqlparser.WhereStr,
				Expr: expr,
			}
		} else {
			newWhere.Expr = &sqlparser.AndExpr{
				Left:  newWhere.Expr,
				Right: expr,
			}
		}
	}
	if !hasWorkflow && vx.workflow != "" {
		expr := &sqlparser.ComparisonExpr{
			Left:     &sqlparser.ColName{Name: sqlparser.NewColIdent(plannerParams.workflowColumn)},
			Operator: sqlparser.EqualStr,
			Right:    sqlparser.NewStrLiteral([]byte(vx.workflow)),
		}
		newWhere.Expr = &sqlparser.AndExpr{
			Left:  newWhere.Expr,
			Right: expr,
		}
	}
	return newWhere
}

// buildUpdatePlan builds a plan for an UPDATE query
func (vx *vexec) buildUpdatePlan(planner vexecPlanner, upd *sqlparser.Update) (*vexecPlan, error) {
	if upd.OrderBy != nil || upd.Limit != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(upd))
	}
	plannerParams := planner.params()
	if immutableColumnNames := plannerParams.immutableColumnNames; len(immutableColumnNames) > 0 {
		// we must never allow changing an immutable column
		for _, expr := range upd.Exprs {
			for _, immutableColName := range plannerParams.immutableColumnNames {
				if expr.Name.Name.EqualString(immutableColName) {
					return nil, fmt.Errorf("%s cannot be changed: %v", immutableColName, sqlparser.String(expr))
				}
			}
		}
	}
	if updatableColumnNames := plannerParams.updatableColumnNames; len(updatableColumnNames) > 0 {
		// if updatableColumnNames is non empty, then we must only accept changes to columns listed there
		for _, expr := range upd.Exprs {
			isUpdatable := false
			for _, updatableColName := range updatableColumnNames {
				if expr.Name.Name.EqualString(updatableColName) {
					isUpdatable = true
				}
			}
			if !isUpdatable {
				return nil, fmt.Errorf("%+v cannot be changed: %v", expr.Name.Name, sqlparser.String(expr))
			}
		}
	}
	if updateTemplates := plannerParams.updateTemplates; len(updateTemplates) > 0 {
		match, err := sqlparser.QueryMatchesTemplates(vx.query, updateTemplates)
		if err != nil {
			return nil, err
		}
		if !match {
			return nil, fmt.Errorf("Query must match one of these templates: %s", strings.Join(updateTemplates, "; "))
		}
	}
	upd.Where = vx.addDefaultWheres(planner, upd.Where)

	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", upd)

	return &vexecPlan{
		opcode:      updateQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}

// buildUpdatePlan builds a plan for a DELETE query
func (vx *vexec) buildDeletePlan(planner vexecPlanner, del *sqlparser.Delete) (*vexecPlan, error) {
	if del.Targets != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}
	if del.Partitions != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}
	if del.OrderBy != nil || del.Limit != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}

	del.Where = vx.addDefaultWheres(planner, del.Where)

	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", del)

	return &vexecPlan{
		opcode:      deleteQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}

// buildUpdatePlan builds a plan for a SELECT query
func (vx *vexec) buildSelectPlan(planner vexecPlanner, sel *sqlparser.Select) (*vexecPlan, error) {
	sel.Where = vx.addDefaultWheres(planner, sel.Where)
	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", sel)

	return &vexecPlan{
		opcode:      selectQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}
<commit_msg>fixing names<commit_after>/*
Copyright 2020 The Vitess 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 wrangler

import (
	"context"
	"fmt"
	"strings"

	"vitess.io/vitess/go/vt/log"
	querypb "vitess.io/vitess/go/vt/proto/query"
	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	"vitess.io/vitess/go/vt/sqlparser"

	"github.com/olekukonko/tablewriter"
)

// vexecPlan contains the final query to be sent to the tablets
type vexecPlan struct {
	opcode      int
	parsedQuery *sqlparser.ParsedQuery
}

// vexecPlannerParams controls how some queries/columns are handled
type vexecPlannerParams struct {
	dbNameColumn         string
	workflowColumn       string
	immutableColumnNames []string
	updatableColumnNames []string
	updateTemplates      []string
}

// vexecPlanner generates and executes a plan
type vexecPlanner interface {
	params() *vexecPlannerParams
	exec(ctx context.Context, masterAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error)
	dryRun() error
}

// vreplicationPlanner is a vexecPlanner implementation, specific to _vt.vreplication table
type vreplicationPlanner struct {
	vx *vexec
	d  *vexecPlannerParams
}

func newVReplicationPlanner(vx *vexec) vexecPlanner {
	return &vreplicationPlanner{
		vx: vx,
		d: &vexecPlannerParams{
			dbNameColumn:         "db_name",
			workflowColumn:       "workflow",
			immutableColumnNames: []string{"id"},
			updatableColumnNames: []string{},
		},
	}
}
func (p vreplicationPlanner) params() *vexecPlannerParams { return p.d }
func (p vreplicationPlanner) exec(
	ctx context.Context, masterAlias *topodatapb.TabletAlias, query string,
) (*querypb.QueryResult, error) {
	qr, err := p.vx.wr.VReplicationExec(ctx, masterAlias, query)
	if err != nil {
		return nil, err
	}
	if qr.RowsAffected == 0 {
		log.Infof("no matching streams found for workflow %s, tablet %s, query %s", p.vx.workflow, masterAlias, query)
	}
	return qr, nil
}
func (p vreplicationPlanner) dryRun() error {
	rsr, err := p.vx.wr.getStreams(p.vx.ctx, p.vx.workflow, p.vx.keyspace)
	if err != nil {
		return err
	}

	p.vx.wr.Logger().Printf("Query: %s\nwill be run on the following streams in keyspace %s for workflow %s:\n\n",
		p.vx.plannedQuery, p.vx.keyspace, p.vx.workflow)
	tableString := &strings.Builder{}
	table := tablewriter.NewWriter(tableString)
	table.SetHeader([]string{"Tablet", "ID", "BinLogSource", "State", "DBName", "Current GTID", "MaxReplicationLag"})
	for _, master := range p.vx.masters {
		key := fmt.Sprintf("%s/%s", master.Shard, master.AliasString())
		for _, stream := range rsr.ShardStatuses[key].MasterReplicationStatuses {
			table.Append([]string{key, fmt.Sprintf("%d", stream.ID), stream.Bls.String(), stream.State, stream.DBName, stream.Pos, fmt.Sprintf("%d", stream.MaxReplicationLag)})
		}
	}
	table.SetAutoMergeCellsByColumnIndex([]int{0})
	table.SetRowLine(true)
	table.Render()
	p.vx.wr.Logger().Printf(tableString.String())
	p.vx.wr.Logger().Printf("\n\n")

	return nil
}

// schemaMigrationsPlanner is a vexecPlanner implementation, specific to _vt.schema_migrations table
type schemaMigrationsPlanner struct {
	vx *vexec
	d  *vexecPlannerParams
}

func newSchemaMigrationsPlanner(vx *vexec) vexecPlanner {
	return &schemaMigrationsPlanner{
		vx: vx,
		d: &vexecPlannerParams{
			dbNameColumn:   "mysql_schema",
			workflowColumn: "migration_uuid",
			updateTemplates: []string{
				`update _vt.schema_migrations set migration_status='val1'`,
				`update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2'`,
				`update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2' and shard='val3'`,
			},
		},
	}
}
func (p schemaMigrationsPlanner) params() *vexecPlannerParams { return p.d }
func (p schemaMigrationsPlanner) exec(ctx context.Context, masterAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error) {
	qr, err := p.vx.wr.GenericVExec(ctx, masterAlias, query, p.vx.workflow, p.vx.keyspace)
	if err != nil {
		return nil, err
	}
	if qr.RowsAffected == 0 {
		return nil, fmt.Errorf("\nno matching migrations found for workflow %s, tablet %s, query %s", p.vx.workflow, masterAlias, query)
	}
	return qr, nil
}
func (p schemaMigrationsPlanner) dryRun() error { return nil }

// make sure these planners implement vexecPlanner interface
var _ vexecPlanner = vreplicationPlanner{}
var _ vexecPlanner = schemaMigrationsPlanner{}

const (
	updateQuery = iota
	deleteQuery
	selectQuery
)

// extractTableName returns the qualified table name (e.g. "_vt.schema_migrations") from a SELECT/DELETE/UPDATE statement
func extractTableName(stmt sqlparser.Statement) (string, error) {
	switch stmt := stmt.(type) {
	case *sqlparser.Update:
		return sqlparser.String(stmt.TableExprs), nil
	case *sqlparser.Delete:
		return sqlparser.String(stmt.TableExprs), nil
	case *sqlparser.Select:
		return sqlparser.String(stmt.From), nil
	}
	return "", fmt.Errorf("query not supported by vexec: %+v", sqlparser.String(stmt))
}

// qualifiedTableName qualifies a table with "_vt." schema
func qualifiedTableName(tableName string) string {
	return fmt.Sprintf("%s.%s", vexecTableQualifier, tableName)
}

// getPlanner returns a specific planner appropriate for the queried table
func (vx *vexec) getPlanner() error {
	switch vx.tableName {
	case qualifiedTableName(schemaMigrationsTableName):
		vx.planner = newSchemaMigrationsPlanner(vx)
	case qualifiedTableName(vreplicationTableName):
		vx.planner = newVReplicationPlanner(vx)
	default:
		return fmt.Errorf("table not supported by vexec: %v", vx.tableName)
	}
	return nil
}

// buildPlan builds an execution plan. More specifically, it generates the query which is then sent to
// relevant vttablet servers
func (vx *vexec) buildPlan() (plan *vexecPlan, err error) {
	switch stmt := vx.stmt.(type) {
	case *sqlparser.Update:
		plan, err = vx.buildUpdatePlan(vx.planner, stmt)
	case *sqlparser.Delete:
		plan, err = vx.buildDeletePlan(vx.planner, stmt)
	case *sqlparser.Select:
		plan, err = vx.buildSelectPlan(vx.planner, stmt)
	default:
		return nil, fmt.Errorf("query not supported by vexec: %s", sqlparser.String(stmt))
	}
	return plan, err
}

// analyzeWhereEqualsColumns identifies column names in a WHERE clause that have a comparison expression
func (vx *vexec) analyzeWhereEqualsColumns(where *sqlparser.Where) []string {
	var cols []string
	if where == nil {
		return cols
	}
	exprs := sqlparser.SplitAndExpression(nil, where.Expr)
	for _, expr := range exprs {
		switch expr := expr.(type) {
		case *sqlparser.ComparisonExpr:
			qualifiedName, ok := expr.Left.(*sqlparser.ColName)
			if ok {
				cols = append(cols, qualifiedName.Name.String())
			}
		}
	}
	return cols
}

// addDefaultWheres modifies the query to add, if appropriate, the workflow and DB-name column modifiers
func (vx *vexec) addDefaultWheres(planner vexecPlanner, where *sqlparser.Where) *sqlparser.Where {
	cols := vx.analyzeWhereEqualsColumns(where)
	var hasDBName, hasWorkflow bool
	plannerParams := planner.params()
	for _, col := range cols {
		if col == plannerParams.dbNameColumn {
			hasDBName = true
		} else if col == plannerParams.workflowColumn {
			hasWorkflow = true
		}
	}
	newWhere := where
	if !hasDBName {
		expr := &sqlparser.ComparisonExpr{
			Left:     &sqlparser.ColName{Name: sqlparser.NewColIdent(plannerParams.dbNameColumn)},
			Operator: sqlparser.EqualStr,
			Right:    sqlparser.NewStrLiteral([]byte(vx.masters[0].DbName())),
		}
		if newWhere == nil {
			newWhere = &sqlparser.Where{
				Type: sqlparser.WhereStr,
				Expr: expr,
			}
		} else {
			newWhere.Expr = &sqlparser.AndExpr{
				Left:  newWhere.Expr,
				Right: expr,
			}
		}
	}
	if !hasWorkflow && vx.workflow != "" {
		expr := &sqlparser.ComparisonExpr{
			Left:     &sqlparser.ColName{Name: sqlparser.NewColIdent(plannerParams.workflowColumn)},
			Operator: sqlparser.EqualStr,
			Right:    sqlparser.NewStrLiteral([]byte(vx.workflow)),
		}
		newWhere.Expr = &sqlparser.AndExpr{
			Left:  newWhere.Expr,
			Right: expr,
		}
	}
	return newWhere
}

// buildUpdatePlan builds a plan for an UPDATE query
func (vx *vexec) buildUpdatePlan(planner vexecPlanner, upd *sqlparser.Update) (*vexecPlan, error) {
	if upd.OrderBy != nil || upd.Limit != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(upd))
	}
	plannerParams := planner.params()
	if immutableColumnNames := plannerParams.immutableColumnNames; len(immutableColumnNames) > 0 {
		// we must never allow changing an immutable column
		for _, expr := range upd.Exprs {
			for _, immutableColName := range plannerParams.immutableColumnNames {
				if expr.Name.Name.EqualString(immutableColName) {
					return nil, fmt.Errorf("%s cannot be changed: %v", immutableColName, sqlparser.String(expr))
				}
			}
		}
	}
	if updatableColumnNames := plannerParams.updatableColumnNames; len(updatableColumnNames) > 0 {
		// if updatableColumnNames is non empty, then we must only accept changes to columns listed there
		for _, expr := range upd.Exprs {
			isUpdatable := false
			for _, updatableColName := range updatableColumnNames {
				if expr.Name.Name.EqualString(updatableColName) {
					isUpdatable = true
				}
			}
			if !isUpdatable {
				return nil, fmt.Errorf("%+v cannot be changed: %v", expr.Name.Name, sqlparser.String(expr))
			}
		}
	}
	if updateTemplates := plannerParams.updateTemplates; len(updateTemplates) > 0 {
		match, err := sqlparser.QueryMatchesTemplates(vx.query, updateTemplates)
		if err != nil {
			return nil, err
		}
		if !match {
			return nil, fmt.Errorf("Query must match one of these templates: %s", strings.Join(updateTemplates, "; "))
		}
	}
	upd.Where = vx.addDefaultWheres(planner, upd.Where)

	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", upd)

	return &vexecPlan{
		opcode:      updateQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}

// buildUpdatePlan builds a plan for a DELETE query
func (vx *vexec) buildDeletePlan(planner vexecPlanner, del *sqlparser.Delete) (*vexecPlan, error) {
	if del.Targets != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}
	if del.Partitions != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}
	if del.OrderBy != nil || del.Limit != nil {
		return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del))
	}

	del.Where = vx.addDefaultWheres(planner, del.Where)

	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", del)

	return &vexecPlan{
		opcode:      deleteQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}

// buildUpdatePlan builds a plan for a SELECT query
func (vx *vexec) buildSelectPlan(planner vexecPlanner, sel *sqlparser.Select) (*vexecPlan, error) {
	sel.Where = vx.addDefaultWheres(planner, sel.Where)
	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", sel)

	return &vexecPlan{
		opcode:      selectQuery,
		parsedQuery: buf.ParsedQuery(),
	}, nil
}
<|endoftext|>


<commit_before>/*
 * find-fast
 *
 * Walks a file system hierarchy using this library.
 */
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/karrick/godirwalk"
	"github.com/karrick/golf"
	"github.com/mattn/go-isatty"
)

var NoColor = os.Getenv("TERM") == "dumb" || !(isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()))

func main() {
	optQuiet := golf.Bool("quiet", false, "Do not print intermediate errors to stderr.")
	optRegex := golf.String("regex", "", "Do not print unless full path matches regex.")
	optSkip := golf.String("skip", "", "Skip and do not descend into entries with this substring in the pathname")
	golf.Parse()

	programName, err := os.Executable()
	if err != nil {
		programName = os.Args[0]
	}
	programName = filepath.Base(programName)

	var nameRE *regexp.Regexp
	if *optRegex != "" {
		nameRE, err = regexp.Compile(*optRegex)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: invalid regex pattern: %s\n", programName, err)
			os.Exit(2)
		}
	}

	var buf []byte // only used when color output

	options := &godirwalk.Options{
		ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
			if !*optQuiet {
				fmt.Fprintf(os.Stderr, "%s: %s\n", programName, err)
			}
			return godirwalk.SkipNode
		},
		Unsorted: true,
	}

	switch {
	case nameRE == nil:
		// When no name pattern provided, print everything.
		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, ".git") {
				return filepath.SkipDir
			}
			_, err := fmt.Println(osPathname)
			return err
		}
	case NoColor:
		// Name pattern was provided, but color not permitted.
		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, ".git") {
				return filepath.SkipDir
			}
			var err error
			if nameRE.FindString(osPathname) != "" {
				_, err = fmt.Println(osPathname)
			}
			return err
		}
	default:
		// Name pattern provided, and color is permitted.
		buf = append(buf, "\033[22m"...) // very first print should set normal intensity

		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, ".git") {
				return filepath.SkipDir
			}
			matches := nameRE.FindAllStringSubmatchIndex(osPathname, -1)
			if len(matches) == 0 {
				return nil // entry does not match pattern
			}

			var prev int
			for _, tuple := range matches {
				buf = append(buf, osPathname[prev:tuple[0]]...)     // print text before match
				buf = append(buf, "\033[1m"...)                     // bold intensity
				buf = append(buf, osPathname[tuple[0]:tuple[1]]...) // print match
				buf = append(buf, "\033[22m"...)                    // normal intensity
				prev = tuple[1]
			}

			buf = append(buf, osPathname[prev:]...)      // print remaining text after final match
			_, err := os.Stdout.Write(append(buf, '\n')) // don't forget newline
			buf = buf[:0]                                // reset buffer for next string
			return err
		}
	}

	dirname := "."
	if golf.NArg() > 0 {
		dirname = golf.Arg(0)
	}

	if err = godirwalk.Walk(dirname, options); err != nil {
		fmt.Fprintf(os.Stderr, "%s: %s\n", programName, err)
		os.Exit(1)
	}
}
<commit_msg>find-fast provides `--skip PATTERN` to skip entries (typo corrected)<commit_after>/*
 * find-fast
 *
 * Walks a file system hierarchy using this library.
 */
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/karrick/godirwalk"
	"github.com/karrick/golf"
	"github.com/mattn/go-isatty"
)

var NoColor = os.Getenv("TERM") == "dumb" || !(isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()))

func main() {
	optQuiet := golf.Bool("quiet", false, "Do not print intermediate errors to stderr.")
	optRegex := golf.String("regex", "", "Do not print unless full path matches regex.")
	optSkip := golf.String("skip", "", "Skip and do not descend into entries with this substring in the pathname")
	golf.Parse()

	programName, err := os.Executable()
	if err != nil {
		programName = os.Args[0]
	}
	programName = filepath.Base(programName)

	var nameRE *regexp.Regexp
	if *optRegex != "" {
		nameRE, err = regexp.Compile(*optRegex)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: invalid regex pattern: %s\n", programName, err)
			os.Exit(2)
		}
	}

	var buf []byte // only used when color output

	options := &godirwalk.Options{
		ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
			if !*optQuiet {
				fmt.Fprintf(os.Stderr, "%s: %s\n", programName, err)
			}
			return godirwalk.SkipNode
		},
		Unsorted: true,
	}

	switch {
	case nameRE == nil:
		// When no name pattern provided, print everything.
		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, *optSkip) {
				return filepath.SkipDir
			}
			_, err := fmt.Println(osPathname)
			return err
		}
	case NoColor:
		// Name pattern was provided, but color not permitted.
		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, *optSkip) {
				return filepath.SkipDir
			}
			var err error
			if nameRE.FindString(osPathname) != "" {
				_, err = fmt.Println(osPathname)
			}
			return err
		}
	default:
		// Name pattern provided, and color is permitted.
		buf = append(buf, "\033[22m"...) // very first print should set normal intensity

		options.Callback = func(osPathname string, _ *godirwalk.Dirent) error {
			if *optSkip != "" && strings.Contains(osPathname, *optSkip) {
				return filepath.SkipDir
			}
			matches := nameRE.FindAllStringSubmatchIndex(osPathname, -1)
			if len(matches) == 0 {
				return nil // entry does not match pattern
			}

			var prev int
			for _, tuple := range matches {
				buf = append(buf, osPathname[prev:tuple[0]]...)     // print text before match
				buf = append(buf, "\033[1m"...)                     // bold intensity
				buf = append(buf, osPathname[tuple[0]:tuple[1]]...) // print match
				buf = append(buf, "\033[22m"...)                    // normal intensity
				prev = tuple[1]
			}

			buf = append(buf, osPathname[prev:]...)      // print remaining text after final match
			_, err := os.Stdout.Write(append(buf, '\n')) // don't forget newline
			buf = buf[:0]                                // reset buffer for next string
			return err
		}
	}

	dirname := "."
	if golf.NArg() > 0 {
		dirname = golf.Arg(0)
	}

	if err = godirwalk.Walk(dirname, options); err != nil {
		fmt.Fprintf(os.Stderr, "%s: %s\n", programName, err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"net/http"

	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api/group"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/project"
	"github.com/ovh/cds/engine/api/workermodel"
	"github.com/ovh/cds/engine/service"
	"github.com/ovh/cds/sdk"
)

func (api *API) postWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		consumer := getAPIConsumer(ctx)

		// parse request and check data validity
		var data sdk.Model
		if err := service.UnmarshalBody(r, &data); err != nil {
			return err
		}
		if err := data.IsValid(); err != nil {
			return err
		}
		if err := data.IsValidType(); err != nil {
			return err
		}

		// check that given group id exits and that the user is admin of the group
		grp, err := group.LoadByID(ctx, api.mustDB(), data.GroupID, group.LoadOptions.WithMembers)
		if err != nil {
			return sdk.NewError(sdk.ErrWrongRequest, err)
		}
		if !isGroupAdmin(ctx, grp) && !isAdmin(ctx) {
			return sdk.NewErrorFrom(sdk.ErrForbidden, "you should be admin of the group to import a worker model")
		}

		if !isAdmin(ctx) {
			// if current user is not admin and model is not restricted, a pattern should be given
			if !data.Restricted && data.PatternName == "" {
				return sdk.NewErrorFrom(sdk.ErrWorkerModelNoPattern, "missing model pattern name")
			}
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot begin transaction")
		}
		defer tx.Rollback() // nolint

		model, err := workermodel.Create(ctx, tx, data, consumer)
		if err != nil {
			return err
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "unable to commit transaction")
		}

		// reload complete worker model
		new, err := workermodel.LoadByID(api.mustDB(), model.ID)
		if err != nil {
			return err
		}

		new.Editable = true

		return service.WriteJSON(w, new, http.StatusOK)
	}
}

func (api *API) putWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName, group.LoadOptions.WithMembers)
		if err != nil {
			return err
		}

		old, errLoad := workermodel.LoadByNameAndGroupIDWithClearPassword(api.mustDB(), modelName, g.ID)
		if errLoad != nil {
			return sdk.WrapError(errLoad, "cannot load worker model")
		}

		// parse request and validate given data
		var data sdk.Model
		if err := service.UnmarshalBody(r, &data); err != nil {
			return err
		}
		if err := data.IsValid(); err != nil {
			return err
		}

		if old.GroupID != data.GroupID {
			// check that given group id exits and that the user is admin of the group
			grp, err := group.LoadByID(ctx, api.mustDB(), data.GroupID, group.LoadOptions.WithMembers)
			if err != nil {
				return sdk.NewError(sdk.ErrWrongRequest, err)
			}
			if !isGroupAdmin(ctx, grp) && !isAdmin(ctx) {
				return sdk.NewErrorFrom(sdk.ErrForbidden, "you should be admin of the group to import a worker model")
			}
		}

		if !isAdmin(ctx) {
			if err := workermodel.CopyModelTypeData(old, &data); err != nil {
				return err
			}
		}

		if err := data.IsValidType(); err != nil {
			return err
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot begin transaction")
		}
		defer tx.Rollback() // nolint

		model, err := workermodel.Update(ctx, tx, old, data)
		if err != nil {
			return err
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "unable to commit transaction")
		}

		new, err := workermodel.LoadByID(api.mustDB(), model.ID)
		if err != nil {
			return err
		}

		new.Editable = true

		return service.WriteJSON(w, new, http.StatusOK)
	}
}

func (api *API) deleteWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot start transaction")
		}

		if err := workermodel.Delete(tx, m.ID); err != nil {
			return sdk.WrapError(err, "cannot delete worker model")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "Cannot commit transaction")
		}

		return nil
	}
}

func (api *API) getWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName, group.LoadOptions.WithMembers)
		if err != nil {
			return err
		}

		// FIXME implements load options for worker model dao
		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		m.Editable = isGroupAdmin(ctx, g) || isAdmin(ctx)

		return service.WriteJSON(w, m, http.StatusOK)
	}
}

func (api *API) getWorkerModelsHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		if err := r.ParseForm(); err != nil {
			return sdk.WrapError(sdk.ErrWrongRequest, "cannot parse form")
		}

		var filter workermodel.LoadFilter

		binary := r.FormValue("binary")
		if binary != "" {
			filter.Binary = binary
		}

		stateString := r.FormValue("state")
		if stateString != "" {
			o := workermodel.StateFilter(stateString)
			if err := o.IsValid(); err != nil {
				return err
			}
			filter.State = o
		}

		models := []sdk.Model{}
		var err error
		if isMaintainer(ctx) || isAdmin(ctx) {
			models, err = workermodel.LoadAll(ctx, api.mustDB(), &filter, workermodel.LoadOptions.Default)
		} else {
			groupIDs := append(getAPIConsumer(ctx).GetGroupIDs(), group.SharedInfraGroup.ID)
			models, err = workermodel.LoadAllByGroupIDs(ctx, api.mustDB(), groupIDs, &filter, workermodel.LoadOptions.Default)
		}
		if err != nil {
			return sdk.WrapError(err, "cannot load worker models")
		}

		return service.WriteJSON(w, models, http.StatusOK)
	}
}

func (api *API) getWorkerModelUsageHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		var pips []sdk.Pipeline
		if isMaintainer(ctx) || isAdmin(ctx) {
			pips, err = pipeline.LoadByWorkerModel(ctx, api.mustDB(), m)
		} else {
			pips, err = pipeline.LoadByWorkerModelAndGroupIDs(ctx, api.mustDB(), m,
				append(getAPIConsumer(ctx).GetGroupIDs(), group.SharedInfraGroup.ID))
		}
		if err != nil {
			return sdk.WrapError(err, "cannot load pipelines linked to worker model")
		}

		return service.WriteJSON(w, pips, http.StatusOK)
	}
}

func (api *API) getWorkerModelsForProjectHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars[permProjectKey]

		proj, err := project.Load(api.mustDB(), api.Cache, key, project.LoadOptions.WithGroups)
		if err != nil {
			return sdk.WrapError(err, "unable to load projet %s", key)
		}

		groupIDs := make([]int64, len(proj.ProjectGroups))
		for i := range proj.ProjectGroups {
			groupIDs[i] = proj.ProjectGroups[i].Group.ID
		}

		models, err := workermodel.LoadAllActiveAndNotDeprecatedForGroupIDs(api.mustDB(), append(groupIDs, group.SharedInfraGroup.ID))
		if err != nil {
			return err
		}

		return service.WriteJSON(w, models, http.StatusOK)
	}
}

func (api *API) getWorkerModelsForGroupHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		if !isGroupMember(ctx, g) && !isAdmin(ctx) {
			return sdk.WithStack(sdk.ErrForbidden)
		}

		wms, err := workermodel.LoadAllActiveAndNotDeprecatedForGroupIDs(api.mustDB(), []int64{g.ID, group.SharedInfraGroup.ID})
		if err != nil {
			return err
		}

		return service.WriteJSON(w, wms, http.StatusOK)
	}
}

func (api *API) getWorkerModelTypesHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		return service.WriteJSON(w, sdk.AvailableWorkerModelType, http.StatusOK)
	}
}
<commit_msg>fix(api): return models for consumer's groups if hatchery<commit_after>package api

import (
	"context"
	"net/http"

	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api/group"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/project"
	"github.com/ovh/cds/engine/api/workermodel"
	"github.com/ovh/cds/engine/service"
	"github.com/ovh/cds/sdk"
)

func (api *API) postWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		consumer := getAPIConsumer(ctx)

		// parse request and check data validity
		var data sdk.Model
		if err := service.UnmarshalBody(r, &data); err != nil {
			return err
		}
		if err := data.IsValid(); err != nil {
			return err
		}
		if err := data.IsValidType(); err != nil {
			return err
		}

		// check that given group id exits and that the user is admin of the group
		grp, err := group.LoadByID(ctx, api.mustDB(), data.GroupID, group.LoadOptions.WithMembers)
		if err != nil {
			return sdk.NewError(sdk.ErrWrongRequest, err)
		}
		if !isGroupAdmin(ctx, grp) && !isAdmin(ctx) {
			return sdk.NewErrorFrom(sdk.ErrForbidden, "you should be admin of the group to import a worker model")
		}

		if !isAdmin(ctx) {
			// if current user is not admin and model is not restricted, a pattern should be given
			if !data.Restricted && data.PatternName == "" {
				return sdk.NewErrorFrom(sdk.ErrWorkerModelNoPattern, "missing model pattern name")
			}
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot begin transaction")
		}
		defer tx.Rollback() // nolint

		model, err := workermodel.Create(ctx, tx, data, consumer)
		if err != nil {
			return err
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "unable to commit transaction")
		}

		// reload complete worker model
		new, err := workermodel.LoadByID(api.mustDB(), model.ID)
		if err != nil {
			return err
		}

		new.Editable = true

		return service.WriteJSON(w, new, http.StatusOK)
	}
}

func (api *API) putWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName, group.LoadOptions.WithMembers)
		if err != nil {
			return err
		}

		old, errLoad := workermodel.LoadByNameAndGroupIDWithClearPassword(api.mustDB(), modelName, g.ID)
		if errLoad != nil {
			return sdk.WrapError(errLoad, "cannot load worker model")
		}

		// parse request and validate given data
		var data sdk.Model
		if err := service.UnmarshalBody(r, &data); err != nil {
			return err
		}
		if err := data.IsValid(); err != nil {
			return err
		}

		if old.GroupID != data.GroupID {
			// check that given group id exits and that the user is admin of the group
			grp, err := group.LoadByID(ctx, api.mustDB(), data.GroupID, group.LoadOptions.WithMembers)
			if err != nil {
				return sdk.NewError(sdk.ErrWrongRequest, err)
			}
			if !isGroupAdmin(ctx, grp) && !isAdmin(ctx) {
				return sdk.NewErrorFrom(sdk.ErrForbidden, "you should be admin of the group to import a worker model")
			}
		}

		if !isAdmin(ctx) {
			if err := workermodel.CopyModelTypeData(old, &data); err != nil {
				return err
			}
		}

		if err := data.IsValidType(); err != nil {
			return err
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot begin transaction")
		}
		defer tx.Rollback() // nolint

		model, err := workermodel.Update(ctx, tx, old, data)
		if err != nil {
			return err
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "unable to commit transaction")
		}

		new, err := workermodel.LoadByID(api.mustDB(), model.ID)
		if err != nil {
			return err
		}

		new.Editable = true

		return service.WriteJSON(w, new, http.StatusOK)
	}
}

func (api *API) deleteWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		tx, err := api.mustDB().Begin()
		if err != nil {
			return sdk.WrapError(err, "cannot start transaction")
		}

		if err := workermodel.Delete(tx, m.ID); err != nil {
			return sdk.WrapError(err, "cannot delete worker model")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "Cannot commit transaction")
		}

		return nil
	}
}

func (api *API) getWorkerModelHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName, group.LoadOptions.WithMembers)
		if err != nil {
			return err
		}

		// FIXME implements load options for worker model dao
		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		m.Editable = isGroupAdmin(ctx, g) || isAdmin(ctx)

		return service.WriteJSON(w, m, http.StatusOK)
	}
}

func (api *API) getWorkerModelsHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		if err := r.ParseForm(); err != nil {
			return sdk.WrapError(sdk.ErrWrongRequest, "cannot parse form")
		}

		var filter workermodel.LoadFilter

		binary := r.FormValue("binary")
		if binary != "" {
			filter.Binary = binary
		}

		stateString := r.FormValue("state")
		if stateString != "" {
			o := workermodel.StateFilter(stateString)
			if err := o.IsValid(); err != nil {
				return err
			}
			filter.State = o
		}

		consumer := getAPIConsumer(ctx)

		// TODO test if hatchery wildcard vs with groups (wildcard same code as a user)
		models := []sdk.Model{}
		var err error
		if _, ok := api.isHatchery(ctx); ok && len(consumer.GroupIDs) > 0 {
			models, err = workermodel.LoadAllByGroupIDs(ctx, api.mustDB(), consumer.GetGroupIDs(), &filter, workermodel.LoadOptions.Default)
		} else if isMaintainer(ctx) || isAdmin(ctx) {
			models, err = workermodel.LoadAll(ctx, api.mustDB(), &filter, workermodel.LoadOptions.Default)
		} else {
			groupIDs := append(consumer.GetGroupIDs(), group.SharedInfraGroup.ID)
			models, err = workermodel.LoadAllByGroupIDs(ctx, api.mustDB(), groupIDs, &filter, workermodel.LoadOptions.Default)
		}
		if err != nil {
			return sdk.WrapError(err, "cannot load worker models")
		}

		return service.WriteJSON(w, models, http.StatusOK)
	}
}

func (api *API) getWorkerModelUsageHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]
		modelName := vars["permModelName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		m, err := workermodel.LoadByNameAndGroupID(api.mustDB(), modelName, g.ID)
		if err != nil {
			return sdk.WrapError(err, "cannot load worker model")
		}

		var pips []sdk.Pipeline
		if isMaintainer(ctx) || isAdmin(ctx) {
			pips, err = pipeline.LoadByWorkerModel(ctx, api.mustDB(), m)
		} else {
			pips, err = pipeline.LoadByWorkerModelAndGroupIDs(ctx, api.mustDB(), m,
				append(getAPIConsumer(ctx).GetGroupIDs(), group.SharedInfraGroup.ID))
		}
		if err != nil {
			return sdk.WrapError(err, "cannot load pipelines linked to worker model")
		}

		return service.WriteJSON(w, pips, http.StatusOK)
	}
}

func (api *API) getWorkerModelsForProjectHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars[permProjectKey]

		proj, err := project.Load(api.mustDB(), api.Cache, key, project.LoadOptions.WithGroups)
		if err != nil {
			return sdk.WrapError(err, "unable to load projet %s", key)
		}

		groupIDs := make([]int64, len(proj.ProjectGroups))
		for i := range proj.ProjectGroups {
			groupIDs[i] = proj.ProjectGroups[i].Group.ID
		}

		models, err := workermodel.LoadAllActiveAndNotDeprecatedForGroupIDs(api.mustDB(), append(groupIDs, group.SharedInfraGroup.ID))
		if err != nil {
			return err
		}

		return service.WriteJSON(w, models, http.StatusOK)
	}
}

func (api *API) getWorkerModelsForGroupHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)

		groupName := vars["permGroupName"]

		g, err := group.LoadByName(ctx, api.mustDB(), groupName)
		if err != nil {
			return err
		}

		if !isGroupMember(ctx, g) && !isAdmin(ctx) {
			return sdk.WithStack(sdk.ErrForbidden)
		}

		wms, err := workermodel.LoadAllActiveAndNotDeprecatedForGroupIDs(api.mustDB(), []int64{g.ID, group.SharedInfraGroup.ID})
		if err != nil {
			return err
		}

		return service.WriteJSON(w, wms, http.StatusOK)
	}
}

func (api *API) getWorkerModelTypesHandler() service.Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		return service.WriteJSON(w, sdk.AvailableWorkerModelType, http.StatusOK)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc.
//
// 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 view

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/google/shenzhen-go/graph"
)

// TODO: Replace these cobbled-together UIs with Polymer or something.
// TODO: Some way of deleting nodes.
const nodeEditorTemplateSrc = `{{with .Node -}}

	<title>{{if .Name}}{{.Name}}{{else}}[New]{{end}}</title>

{{if .Name}}{{.Name}}{{else}}[New]{{end}}

	Clone |
	Delete |
	Part type: {{.Part.TypeKey}}
	<form method="post">

			Name
			<input name="Name" type="text" required value="{{.Name}}">

			Multiplicity
			<input name="Multiplicity" type="text" required pattern="^[1-9][0-9]*$" title="Must be a whole number, at least 1." value="{{if .Multiplicity}}{{.Multiplicity}}{{else}}1{{end}}">

			Wait for this to finish
			<input name="Wait" type="checkbox" {{if .Wait}}checked{{end}}>

		{{template "part_view" $ }}

			<input type="submit" value="Save">
			<input type="button" value="Return" onclick="window.location.href='?'">

	</form>

{{- end}}`

var nodeEditorTemplate = template.Must(template.New("nodeEditor").Parse(nodeEditorTemplateSrc))

// Node handles viewing/editing a node.
func Node(g *graph.Graph, name string, w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s", r.Method, r.URL)

	q := r.URL.Query()

	_, clone := q["clone"]
	_, del := q["delete"]

	var n *graph.Node
	if name == "new" {
		if clone || del {
			http.Error(w, "Asked for a new node, but also to clone or delete the node", http.StatusBadRequest)
			return
		}
		t := q.Get("type")
		pf, ok := graph.PartFactories[t]
		if !ok {
			http.Error(w, "Asked for a new node, but didn't supply a valid type", http.StatusBadRequest)
			return
		}
		n = &graph.Node{
			Part: pf(),
		}
	} else {
		n1, found := g.Nodes[name]
		if !found {
			http.Error(w, fmt.Sprintf("Node %q not found", name), http.StatusNotFound)
			return
		}
		n = n1
	}

	switch {
	case clone:
		n = n.Copy()
	case del:
		delete(g.Nodes, n.Name)
		u := *r.URL
		u.RawQuery = ""
		log.Printf("redirecting to %v", &u)
		http.Redirect(w, r, u.String(), http.StatusSeeOther) // should cause GET
		return
	}

	var err error
	switch r.Method {
	case "POST":
		err = handleNodePost(g, n, w, r)
	case "GET":
		err = renderNodeEditor(w, g, n)
	default:
		err = fmt.Errorf("unsupported verb %q", r.Method)
	}

	if err != nil {
		msg := fmt.Sprintf("Could not handle request: %v", err)
		log.Printf(msg)
		http.Error(w, msg, http.StatusInternalServerError)
	}
}

func renderNodeEditor(dst io.Writer, g *graph.Graph, n *graph.Node) error {
	t, err := nodeEditorTemplate.Clone()
	if err != nil {
		return err
	}
	if err := n.Part.AssociateEditor(t); err != nil {
		return err
	}
	return t.Execute(dst, &struct {
		*graph.Graph
		*graph.Node
	}{g, n})
}

func handleNodePost(g *graph.Graph, n *graph.Node, w http.ResponseWriter, r *http.Request) error {
	if err := r.ParseForm(); err != nil {
		return err
	}

	// Validate.
	nm := strings.TrimSpace(r.FormValue("Name"))
	if nm == "" {
		return fmt.Errorf(`name is empty [%q == ""]`, nm)
	}

	mult, err := strconv.Atoi(r.FormValue("Multiplicity"))
	if err != nil {
		return err
	}
	if mult < 1 {
		return fmt.Errorf("multiplicity too small [%d < 1]", mult)
	}

	// Create a new part of the same type, and update it.
	// This ensures the settings for the part are valid before
	// updating the node.
	part := graph.PartFactories[n.Part.TypeKey()]()
	if err := part.Update(r); err != nil {
		return err
	}

	// Update.
	n.Multiplicity = uint(mult)
	n.Wait = (r.FormValue("Wait") == "on")
	n.Part = part

	// No name change? No need to readjust the map or redirect.
	// So render the usual editor.
	if nm == n.Name {
		return renderNodeEditor(w, g, n)
	}

	// Do name changes last since they cause a redirect.
	if n.Name != "" {
		delete(g.Nodes, n.Name)
	}
	n.Name = nm
	g.Nodes[nm] = n

	q := url.Values{"node": []string{nm}}
	u := *r.URL
	u.RawQuery = q.Encode()
	log.Printf("redirecting to %v", &u)
	http.Redirect(w, r, u.String(), http.StatusSeeOther) // should cause GET
	return nil
}
<commit_msg>Node template doesnt show Clone or Delete for new nodes<commit_after>// Copyright 2016 Google Inc.
//
// 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 view

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/google/shenzhen-go/graph"
)

// TODO: Replace these cobbled-together UIs with Polymer or something.
// TODO: Some way of deleting nodes.
const nodeEditorTemplateSrc = `{{with .Node -}}

	<title>{{if .Name}}{{.Name}}{{else}}[New]{{end}}</title>

{{if .Name}}{{.Name}}{{else}}[New]{{end}}

	{{if .Name}}
	Clone |
	Delete |
	{{end}}
	Part type: {{.Part.TypeKey}}
	<form method="post">

			Name
			<input name="Name" type="text" required value="{{.Name}}">

			Multiplicity
			<input name="Multiplicity" type="text" required pattern="^[1-9][0-9]*$" title="Must be a whole number, at least 1." value="{{if .Multiplicity}}{{.Multiplicity}}{{else}}1{{end}}">

			Wait for this to finish
			<input name="Wait" type="checkbox" {{if .Wait}}checked{{end}}>

		{{template "part_view" $ }}

			<input type="submit" value="Save">
			<input type="button" value="Return" onclick="window.location.href='?'">

	</form>

{{- end}}`

var nodeEditorTemplate = template.Must(template.New("nodeEditor").Parse(nodeEditorTemplateSrc))

// Node handles viewing/editing a node.
func Node(g *graph.Graph, name string, w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s", r.Method, r.URL)

	q := r.URL.Query()

	_, clone := q["clone"]
	_, del := q["delete"]

	var n *graph.Node
	if name == "new" {
		if clone || del {
			http.Error(w, "Asked for a new node, but also to clone or delete the node", http.StatusBadRequest)
			return
		}
		t := q.Get("type")
		pf, ok := graph.PartFactories[t]
		if !ok {
			http.Error(w, "Asked for a new node, but didn't supply a valid type", http.StatusBadRequest)
			return
		}
		n = &graph.Node{
			Part: pf(),
		}
	} else {
		n1, found := g.Nodes[name]
		if !found {
			http.Error(w, fmt.Sprintf("Node %q not found", name), http.StatusNotFound)
			return
		}
		n = n1
	}

	switch {
	case clone:
		n = n.Copy()
	case del:
		delete(g.Nodes, n.Name)
		u := *r.URL
		u.RawQuery = ""
		log.Printf("redirecting to %v", &u)
		http.Redirect(w, r, u.String(), http.StatusSeeOther) // should cause GET
		return
	}

	var err error
	switch r.Method {
	case "POST":
		err = handleNodePost(g, n, w, r)
	case "GET":
		err = renderNodeEditor(w, g, n)
	default:
		err = fmt.Errorf("unsupported verb %q", r.Method)
	}

	if err != nil {
		msg := fmt.Sprintf("Could not handle request: %v", err)
		log.Printf(msg)
		http.Error(w, msg, http.StatusInternalServerError)
	}
}

func renderNodeEditor(dst io.Writer, g *graph.Graph, n *graph.Node) error {
	t, err := nodeEditorTemplate.Clone()
	if err != nil {
		return err
	}
	if err := n.Part.AssociateEditor(t); err != nil {
		return err
	}
	return t.Execute(dst, &struct {
		*graph.Graph
		*graph.Node
	}{g, n})
}

func handleNodePost(g *graph.Graph, n *graph.Node, w http.ResponseWriter, r *http.Request) error {
	if err := r.ParseForm(); err != nil {
		return err
	}

	// Validate.
	nm := strings.TrimSpace(r.FormValue("Name"))
	if nm == "" {
		return fmt.Errorf(`name is empty [%q == ""]`, nm)
	}

	mult, err := strconv.Atoi(r.FormValue("Multiplicity"))
	if err != nil {
		return err
	}
	if mult < 1 {
		return fmt.Errorf("multiplicity too small [%d < 1]", mult)
	}

	// Create a new part of the same type, and update it.
	// This ensures the settings for the part are valid before
	// updating the node.
	part := graph.PartFactories[n.Part.TypeKey()]()
	if err := part.Update(r); err != nil {
		return err
	}

	// Update.
	n.Multiplicity = uint(mult)
	n.Wait = (r.FormValue("Wait") == "on")
	n.Part = part

	// No name change? No need to readjust the map or redirect.
	// So render the usual editor.
	if nm == n.Name {
		return renderNodeEditor(w, g, n)
	}

	// Do name changes last since they cause a redirect.
	if n.Name != "" {
		delete(g.Nodes, n.Name)
	}
	n.Name = nm
	g.Nodes[nm] = n

	q := url.Values{"node": []string{nm}}
	u := *r.URL
	u.RawQuery = q.Encode()
	log.Printf("redirecting to %v", &u)
	http.Redirect(w, r, u.String(), http.StatusSeeOther) // should cause GET
	return nil
}
<|endoftext|>


<commit_before>package main

// Import formatting and IO libraries
import (
	"html/template"
	"io/ioutil"
	"net/http"
)

// Define a structure to hold our page
type Page struct {
	Title string
	Body  []byte // IO libs expect a byte slice rather than a string
}

// Add a save method to our Page struct so we can persist our data
// This method's signature reads: "This is a method named save that takes as its receiver p, a pointer to Page . It takes no parameters, and returns a value of type error."
func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

// Load pages too
func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl+".html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/view/"):]
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/edit/"):]
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/save/"):]
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save()
        if err != nil {
          http.Error(w, err.Error(), http.StatusInternalServerError)
          return
        }
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func main() {
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>Improve error handling and protect URL/paths from writing anywhere.<commit_after>package main

// Import formatting and IO libraries
import (
	"html/template"
	"io/ioutil"
	"net/http"
)

// Define a structure to hold our page
type Page struct {
	Title string
	Body  []byte // IO libs expect a byte slice rather than a string
}

// Add a save method to our Page struct so we can persist our data
// This method's signature reads: "This is a method named save that takes as its receiver p, a pointer to Page . It takes no parameters, and returns a value of type error."
func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

// Load pages too
func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
	m := validPath.FindStringSubmatch(r.URL.Path)
	if m == nil {
		http.NotFound(w, r)
		return "", errors.New("Invalid Page Title")
	}
	return m[2]
}

var templates = template.Must(template.ParseFiles("edit.html", "view.html"))

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl+".html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func main() {
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package vips

/*
#cgo pkg-config: vips
#include "vips.h"
*/
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"unsafe"
)

var once sync.Once

// Initialize libvips if it's not already started
func Initialize() error {
	var err error

	once.Do(func() {
		// vips_init needs to run on the main thread
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		if C.VIPS_MAJOR_VERSION != 8 || C.VIPS_MINOR_VERSION < 6 {
			err = fmt.Errorf("unsupported libvips version")
		}

		errorCode := C.vips_init(C.CString("picsum-photos"))
		if errorCode != 0 {
			err = fmt.Errorf("unable to initialize vips: %v", catchVipsError())
			return
		}

		// Disable the cache for now
		C.vips_cache_set_max_mem(0)
		C.vips_cache_set_max(0)

		// Set concurrency to 1 so that each job only uses one thread
		C.vips_concurrency_set(1)
	})

	return err
}

// Shutdown libvips
func Shutdown() {
	C.vips_shutdown()
}

// PrintDebugInfo prints libvips debug info to stdout
func PrintDebugInfo() {
	C.im__print_all()
}

// catchVipsError returns the vips error buffer as an error
func catchVipsError() error {
	defer C.vips_thread_shutdown()
	defer C.vips_error_clear()

	s := C.GoString(C.vips_error_buffer())
	return fmt.Errorf("%s", s)
}

// ResizeImage loads an image from a buffer and resizes it.
func ResizeImage(buffer []byte, width int, height int) (*C.VipsImage, error) {
	if len(buffer) == 0 {
		return nil, fmt.Errorf("empty buffer")
	}

	// Prevent buffer from being garbage collected
	defer runtime.KeepAlive(buffer)

	imageBuffer := unsafe.Pointer(&buffer[0])
	imageBufferSize := C.size_t(len(buffer))

	var image *C.VipsImage

	errCode := C.resize_image(imageBuffer, imageBufferSize, &image, C.int(width), C.int(height), C.VIPS_INTERESTING_CENTRE)

	if errCode != 0 {
		return nil, fmt.Errorf("error processing image from buffer %s", catchVipsError())
	}

	return image, nil
}

// SaveToBuffer saves an image to a buffer
func SaveToBuffer(image *C.VipsImage) ([]byte, error) {
	defer unrefImage(image)

	var bufferPointer unsafe.Pointer
	bufferLength := C.size_t(0)

	err := C.saveImageToJpegBuffer(image, &bufferPointer, &bufferLength)

	if err != 0 {
		return nil, fmt.Errorf("error saving to buffer %s", catchVipsError())
	}

	buffer := C.GoBytes(bufferPointer, C.int(bufferLength))

	C.g_free(C.gpointer(bufferPointer))

	return buffer, nil
}

// Grayscale converts an image to grayscale
func Grayscale(image *C.VipsImage) (*C.VipsImage, error) {
	defer unrefImage(image)

	var result *C.VipsImage

	err := C.change_colorspace(image, &result, C.VIPS_INTERPRETATION_B_W)

	if err != 0 {
		return nil, fmt.Errorf("error changing image colorspace %s", catchVipsError())
	}

	return result, nil
}

// Blur applies gaussian blur to an image
func Blur(image *C.VipsImage, blur int) (*C.VipsImage, error) {
	defer unrefImage(image)

	var result *C.VipsImage

	err := C.blur_image(image, &result, C.double(blur))

	if err != 0 {
		return nil, fmt.Errorf("error applying blur to image %s", catchVipsError())
	}

	return result, nil
}
func unrefImage(image *C.VipsImage) {
	C.g_object_unref(C.gpointer(image))
}

func emptyImage() *C.VipsImage {
	return C.vips_image_new()
}
<commit_msg>Always shut down the vips thread<commit_after>package vips

/*
#cgo pkg-config: vips
#include "vips.h"
*/
import "C"

import (
	"fmt"
	"runtime"
	"sync"
	"unsafe"
)

var once sync.Once

// Initialize libvips if it's not already started
func Initialize() error {
	var err error

	once.Do(func() {
		// vips_init needs to run on the main thread
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		if C.VIPS_MAJOR_VERSION != 8 || C.VIPS_MINOR_VERSION < 6 {
			err = fmt.Errorf("unsupported libvips version")
		}

		errorCode := C.vips_init(C.CString("picsum-photos"))
		if errorCode != 0 {
			err = fmt.Errorf("unable to initialize vips: %v", catchVipsError())
			return
		}

		// Disable the cache for now
		C.vips_cache_set_max_mem(0)
		C.vips_cache_set_max(0)

		// Set concurrency to 1 so that each job only uses one thread
		C.vips_concurrency_set(1)
	})

	return err
}

// Shutdown libvips
func Shutdown() {
	C.vips_shutdown()
}

// PrintDebugInfo prints libvips debug info to stdout
func PrintDebugInfo() {
	C.im__print_all()
}

// catchVipsError returns the vips error buffer as an error
func catchVipsError() error {
	defer C.vips_thread_shutdown()
	defer C.vips_error_clear()

	s := C.GoString(C.vips_error_buffer())
	return fmt.Errorf("%s", s)
}

// ResizeImage loads an image from a buffer and resizes it.
func ResizeImage(buffer []byte, width int, height int) (*C.VipsImage, error) {
	if len(buffer) == 0 {
		return nil, fmt.Errorf("empty buffer")
	}

	// Prevent buffer from being garbage collected
	defer C.vips_thread_shutdown()
	defer runtime.KeepAlive(buffer)

	imageBuffer := unsafe.Pointer(&buffer[0])
	imageBufferSize := C.size_t(len(buffer))

	var image *C.VipsImage

	errCode := C.resize_image(imageBuffer, imageBufferSize, &image, C.int(width), C.int(height), C.VIPS_INTERESTING_CENTRE)

	if errCode != 0 {
		return nil, fmt.Errorf("error processing image from buffer %s", catchVipsError())
	}

	return image, nil
}

// SaveToBuffer saves an image to a buffer
func SaveToBuffer(image *C.VipsImage) ([]byte, error) {
	defer C.vips_thread_shutdown()
	defer unrefImage(image)

	var bufferPointer unsafe.Pointer
	bufferLength := C.size_t(0)

	err := C.saveImageToJpegBuffer(image, &bufferPointer, &bufferLength)

	if err != 0 {
		return nil, fmt.Errorf("error saving to buffer %s", catchVipsError())
	}

	buffer := C.GoBytes(bufferPointer, C.int(bufferLength))

	C.g_free(C.gpointer(bufferPointer))

	return buffer, nil
}

// Grayscale converts an image to grayscale
func Grayscale(image *C.VipsImage) (*C.VipsImage, error) {
	defer C.vips_thread_shutdown()
	defer unrefImage(image)

	var result *C.VipsImage

	err := C.change_colorspace(image, &result, C.VIPS_INTERPRETATION_B_W)

	if err != 0 {
		return nil, fmt.Errorf("error changing image colorspace %s", catchVipsError())
	}

	return result, nil
}

// Blur applies gaussian blur to an image
func Blur(image *C.VipsImage, blur int) (*C.VipsImage, error) {
	defer C.vips_thread_shutdown()
	defer unrefImage(image)

	var result *C.VipsImage

	err := C.blur_image(image, &result, C.double(blur))

	if err != 0 {
		return nil, fmt.Errorf("error applying blur to image %s", catchVipsError())
	}

	return result, nil
}
func unrefImage(image *C.VipsImage) {
	C.g_object_unref(C.gpointer(image))
}

func emptyImage() *C.VipsImage {
	return C.vips_image_new()
}
<|endoftext|>