<commit_before>// Copyright 2017-present The Hugo 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 releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"

	"github.com/gohugoio/hugo/helpers"
)

const commitPrefix = "releaser:"

type ReleaseHandler struct {
	cliVersion string

	// If set, we do the releases in 3 steps:
	// 1: Create and write a draft release notes
	// 2: Prepare files for new version.
	// 3: Release
	step        int
	skipPublish bool

	// Just simulate, no actual changes.
	try bool

	git func(args ...string) (string, error)
}

func (r ReleaseHandler) shouldRelease() bool {
	return r.step < 1 || r.shouldContinue()
}

func (r ReleaseHandler) shouldContinue() bool {
	return r.step == 3
}

func (r ReleaseHandler) shouldPrepareReleasenotes() bool {
	return r.step < 1 || r.step == 1
}

func (r ReleaseHandler) shouldPrepareVersions() bool {
	return r.step < 1 || r.step == 2
}

func (r ReleaseHandler) calculateVersions() (helpers.HugoVersion, helpers.HugoVersion) {

	newVersion := helpers.MustParseHugoVersion(r.cliVersion)
	finalVersion := newVersion
	finalVersion.PatchLevel = 0

	newVersion.Suffix = ""

	if newVersion.PatchLevel == 0 {
		finalVersion = finalVersion.Next()
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

func New(version string, step int, skipPublish, try bool) *ReleaseHandler {
	rh := &ReleaseHandler{cliVersion: version, step: step, skipPublish: skipPublish, try: try}

	if try {
		rh.git = func(args ...string) (string, error) {
			fmt.Println("git", args)
			return "", nil
		}
	} else {
		rh.git = git
	}

	return rh
}

func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions()

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("Tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases inbetween, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var gitCommits gitInfos

	if r.shouldPrepareReleasenotes() || r.shouldRelease() {
		gitCommits, err = getGitInfos(changeLogFromTag, !r.try)
		if err != nil {
			return err
		}
	}

	if r.shouldPrepareReleasenotes() {
		releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits)
		if err != nil {
			return err
		}

		if _, err := r.git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if r.shouldPrepareVersions() {
		if newVersion.PatchLevel == 0 {
			// Make sure the docs submodule is up to date.
			// TODO(bep) improve this. Maybe it was not such a good idea to do
			// this in the sobmodule directly.
			if _, err := r.git("submodule", "update", "--init"); err != nil {
				return err
			}
			//git submodule update
			if _, err := r.git("submodule", "update", "--remote", "--merge"); err != nil {
				return err
			}

			// TODO(bep) the above may not have changed anything.
			if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Update /docs [ci skip]", commitPrefix)); err != nil {
				return err
			}
		}

		if err := r.bumpVersions(newVersion); err != nil {
			return err
		}

		for _, repo := range []string{"docs", "."} {
			if _, err := r.git("-C", repo, "commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
				return err
			}
		}
	}

	if !r.shouldRelease() {
		fmt.Println("Skip release ... Use --state=3 to continue.")
		return nil
	}

	releaseNotesFile := getReleaseNotesDocsTempFilename(version)

	// Write the release notes to the docs site as well.
	docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := r.git("-C", "docs", "add", docFile); err != nil {
		return err
	}
	if _, err := r.git("-C", "docs", "commit", "-m", fmt.Sprintf("%s Add release notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	for i, repo := range []string{"docs", "."} {
		if i == 1 {
			if _, err := r.git("add", "docs"); err != nil {
				return err
			}
			if _, err := r.git("commit", "-m", fmt.Sprintf("%s Update /docs to %s [ci skip]", commitPrefix, newVersion)); err != nil {
				return err
			}
		}
		if _, err := r.git("-C", repo, "tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
			return err
		}

		repoURL := "git@github.com:gohugoio/hugo.git"
		if i == 0 {
			repoURL = "git@github.com:gohugoio/hugoDocs.git"
		}
		if _, err := r.git("-C", repo, "push", repoURL, "origin/master", tag); err != nil {
			return err
		}
	}

	// We make changes to the submodule, which is in detached state. Reconsider this
	// to get changes pushed to both.
	// TODO(bep) git fetch git@github.com:gohugoio/hugoDocs.git -- master
	// git branch -f master 8c9359b

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := r.bumpVersions(finalVersion); err != nil {
		return err
	}

	if !r.try {
		// No longer needed.
		if err := os.Remove(releaseNotesFile); err != nil {
			return err
		}
	}

	for _, repo := range []string{"docs", "."} {
		if _, err := r.git("-C", repo, "commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
			return err
		}
	}

	return nil
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	if r.try {
		fmt.Println("Skip goreleaser...")
		return nil
	}

	cmd := exec.Command("goreleaser", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("goreleaser failed: %s", err)
	}
	return nil
}

func (r *ReleaseHandler) bumpVersions(ver helpers.HugoVersion) error {
	fromDev := ""
	toDev := ""

	if ver.Suffix != "" {
		toDev = "-DEV"
	} else {
		fromDev = "-DEV"
	}

	if err := r.replaceInFile("helpers/hugo.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := r.replaceInFile("snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := r.replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	// docs/config.toml
	if err := r.replaceInFile("docs/config.toml",
		`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	if r.try {
		fmt.Printf("Replace in %q: %q\n", filename, oldNew)
		return nil
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}
<commit_msg>releaser: Revise the docs handling to match new subtree<commit_after>// Copyright 2017-present The Hugo 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 releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"

	"github.com/gohugoio/hugo/helpers"
)

const commitPrefix = "releaser:"

type ReleaseHandler struct {
	cliVersion string

	// If set, we do the releases in 3 steps:
	// 1: Create and write a draft release notes
	// 2: Prepare files for new version.
	// 3: Release
	step        int
	skipPublish bool

	// Just simulate, no actual changes.
	try bool

	git func(args ...string) (string, error)
}

func (r ReleaseHandler) shouldRelease() bool {
	return r.step < 1 || r.shouldContinue()
}

func (r ReleaseHandler) shouldContinue() bool {
	return r.step >= 3
}

func (r ReleaseHandler) shouldPrepareReleasenotes() bool {
	return r.step < 1 || r.step == 1
}

func (r ReleaseHandler) shouldPrepareVersions() bool {
	return r.step < 1 || r.step == 2 || r.step > 3
}

func (r ReleaseHandler) calculateVersions() (helpers.HugoVersion, helpers.HugoVersion) {

	newVersion := helpers.MustParseHugoVersion(r.cliVersion)
	finalVersion := newVersion
	finalVersion.PatchLevel = 0

	newVersion.Suffix = ""

	if newVersion.PatchLevel == 0 {
		finalVersion = finalVersion.Next()
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

func New(version string, step int, skipPublish, try bool) *ReleaseHandler {
	rh := &ReleaseHandler{cliVersion: version, step: step, skipPublish: skipPublish, try: try}

	if try {
		rh.git = func(args ...string) (string, error) {
			fmt.Println("git", args)
			return "", nil
		}
	} else {
		rh.git = git
	}

	return rh
}

func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions()

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("Tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases inbetween, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var gitCommits gitInfos

	if r.shouldPrepareReleasenotes() || r.shouldRelease() {
		gitCommits, err = getGitInfos(changeLogFromTag, !r.try)
		if err != nil {
			return err
		}
	}

	if r.shouldPrepareReleasenotes() {
		releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits)
		if err != nil {
			return err
		}

		if _, err := r.git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if r.shouldPrepareVersions() {

		// For docs, for now we assume that:
		// The /docs subtree is up to date and ready to go.
		// The hugoDocs/dev and hugoDocs/master must be merged manually after release.
		// TODO(bep) improve this when we see how it works.

		if err := r.bumpVersions(newVersion); err != nil {
			return err
		}

		if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if !r.shouldRelease() {
		fmt.Printf("Skip release ... Use --state=%d for next or --state=4 to finish\n", r.step+1)
		return nil
	}

	releaseNotesFile := getReleaseNotesDocsTempFilename(version)

	// Write the release notes to the docs site as well.
	docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := r.git("add", docFile); err != nil {
		return err
	}
	if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := r.git("push", tag); err != nil {
		return err
	}

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := r.bumpVersions(finalVersion); err != nil {
		return err
	}

	if !r.try {
		// No longer needed.
		if err := os.Remove(releaseNotesFile); err != nil {
			return err
		}
	}

	if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	if r.try {
		fmt.Println("Skip goreleaser...")
		return nil
	}

	cmd := exec.Command("goreleaser", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("goreleaser failed: %s", err)
	}
	return nil
}

func (r *ReleaseHandler) bumpVersions(ver helpers.HugoVersion) error {
	fromDev := ""
	toDev := ""

	if ver.Suffix != "" {
		toDev = "-DEV"
	} else {
		fromDev = "-DEV"
	}

	if err := r.replaceInFile("helpers/hugo.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := r.replaceInFile("snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := r.replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	// docs/config.toml
	if err := r.replaceInFile("docs/config.toml",
		`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	if r.try {
		fmt.Printf("Replace in %q: %q\n", filename, oldNew)
		return nil
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}
<|endoftext|>


<commit_before>// Copyright 2017-present The Hugo 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 releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"

	"github.com/spf13/hugo/helpers"
)

const commitPrefix = "releaser:"

type ReleaseHandler struct {
	patch int

	// If set, we do the relases in 3 steps:
	// 1: Create and write a draft release notes
	// 2: Prepare files for new version.
	// 3: Release
	step        int
	skipPublish bool
}

func (r ReleaseHandler) shouldRelease() bool {
	return r.step < 1 || r.shouldContinue()
}

func (r ReleaseHandler) shouldContinue() bool {
	return r.step == 3
}

func (r ReleaseHandler) shouldPrepareReleasenotes() bool {
	return r.step < 1 || r.step == 1
}

func (r ReleaseHandler) shouldPrepareVersions() bool {
	return r.step < 1 || r.step == 2
}

func (r ReleaseHandler) calculateVersions(current helpers.HugoVersion) (helpers.HugoVersion, helpers.HugoVersion) {
	var (
		newVersion   = current
		finalVersion = current
	)

	newVersion.Suffix = ""

	if r.shouldContinue() {
		// The version in the current code base is in the state we want for
		// the release.
		if r.patch == 0 {
			finalVersion = newVersion.Next()
		}
	} else if r.patch > 0 {
		newVersion = helpers.CurrentHugoVersion.NextPatchLevel(r.patch)
	} else {
		finalVersion = newVersion.Next()
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

func New(patch, step int, skipPublish bool) *ReleaseHandler {
	return &ReleaseHandler{patch: patch, step: step, skipPublish: skipPublish}
}

func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions(helpers.CurrentHugoVersion)

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("Tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases inbetween, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var gitCommits gitInfos

	if r.shouldPrepareReleasenotes() || r.shouldRelease() {
		gitCommits, err = getGitInfos(changeLogFromTag, true)
		if err != nil {
			return err
		}
	}

	if r.shouldPrepareReleasenotes() {
		releaseNotesFile, err := writeReleaseNotesToDocsTemp(version, gitCommits)
		if err != nil {
			return err
		}

		if _, err := git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := git("commit", "-m", fmt.Sprintf("%s Add relase notes draft for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if r.shouldPrepareVersions() {
		if err := bumpVersions(newVersion); err != nil {
			return err
		}

		if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if !r.shouldRelease() {
		fmt.Println("Skip release ... Use --state=3 to continue.")
		return nil
	}

	releaseNotesFile := getRelaseNotesDocsTempFilename(version)

	// Write the release notes to the docs site as well.
	docFile, err := writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := git("add", docFile); err != nil {
		return err
	}
	if _, err := git("commit", "-m", fmt.Sprintf("%s Add relase notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := git("push", "origin", tag); err != nil {
		return err
	}

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := bumpVersions(finalVersion); err != nil {
		return err
	}

	// No longer needed.
	if err := os.Remove(releaseNotesFile); err != nil {
		return err
	}

	if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	cmd := exec.Command("goreleaser", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("goreleaser failed: %s", err)
	}
	return nil
}

func bumpVersions(ver helpers.HugoVersion) error {
	fromDev := ""
	toDev := ""

	if ver.Suffix != "" {
		toDev = "-DEV"
	} else {
		fromDev = "-DEV"
	}

	if err := replaceInFile("helpers/hugo.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := replaceInFile("snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	// docs/config.toml
	if err := replaceInFile("docs/config.toml",
		`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
		return err
	}

	return nil
}

func replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}
<commit_msg>releaser: Spelling<commit_after>// Copyright 2017-present The Hugo 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 releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"

	"github.com/spf13/hugo/helpers"
)

const commitPrefix = "releaser:"

type ReleaseHandler struct {
	patch int

	// If set, we do the relases in 3 steps:
	// 1: Create and write a draft release notes
	// 2: Prepare files for new version.
	// 3: Release
	step        int
	skipPublish bool
}

func (r ReleaseHandler) shouldRelease() bool {
	return r.step < 1 || r.shouldContinue()
}

func (r ReleaseHandler) shouldContinue() bool {
	return r.step == 3
}

func (r ReleaseHandler) shouldPrepareReleasenotes() bool {
	return r.step < 1 || r.step == 1
}

func (r ReleaseHandler) shouldPrepareVersions() bool {
	return r.step < 1 || r.step == 2
}

func (r ReleaseHandler) calculateVersions(current helpers.HugoVersion) (helpers.HugoVersion, helpers.HugoVersion) {
	var (
		newVersion   = current
		finalVersion = current
	)

	newVersion.Suffix = ""

	if r.shouldContinue() {
		// The version in the current code base is in the state we want for
		// the release.
		if r.patch == 0 {
			finalVersion = newVersion.Next()
		}
	} else if r.patch > 0 {
		newVersion = helpers.CurrentHugoVersion.NextPatchLevel(r.patch)
	} else {
		finalVersion = newVersion.Next()
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

func New(patch, step int, skipPublish bool) *ReleaseHandler {
	return &ReleaseHandler{patch: patch, step: step, skipPublish: skipPublish}
}

func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions(helpers.CurrentHugoVersion)

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("Tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases inbetween, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var gitCommits gitInfos

	if r.shouldPrepareReleasenotes() || r.shouldRelease() {
		gitCommits, err = getGitInfos(changeLogFromTag, true)
		if err != nil {
			return err
		}
	}

	if r.shouldPrepareReleasenotes() {
		releaseNotesFile, err := writeReleaseNotesToDocsTemp(version, gitCommits)
		if err != nil {
			return err
		}

		if _, err := git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if r.shouldPrepareVersions() {
		if err := bumpVersions(newVersion); err != nil {
			return err
		}

		if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if !r.shouldRelease() {
		fmt.Println("Skip release ... Use --state=3 to continue.")
		return nil
	}

	releaseNotesFile := getRelaseNotesDocsTempFilename(version)

	// Write the release notes to the docs site as well.
	docFile, err := writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := git("add", docFile); err != nil {
		return err
	}
	if _, err := git("commit", "-m", fmt.Sprintf("%s Add relase notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := git("push", "origin", tag); err != nil {
		return err
	}

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := bumpVersions(finalVersion); err != nil {
		return err
	}

	// No longer needed.
	if err := os.Remove(releaseNotesFile); err != nil {
		return err
	}

	if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	cmd := exec.Command("goreleaser", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("goreleaser failed: %s", err)
	}
	return nil
}

func bumpVersions(ver helpers.HugoVersion) error {
	fromDev := ""
	toDev := ""

	if ver.Suffix != "" {
		toDev = "-DEV"
	} else {
		fromDev = "-DEV"
	}

	if err := replaceInFile("helpers/hugo.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := replaceInFile("snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	// docs/config.toml
	if err := replaceInFile("docs/config.toml",
		`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
		return err
	}

	return nil
}

func replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/motemen/ghq/utils"
)

// A RemoteRepository represents a remote repository.
type RemoteRepository interface {
	// The repository URL.
	URL() *url.URL
	// Checks if the URL is valid.
	IsValid() bool
	// The VCS backend that hosts the repository.
	VCS() *VCSBackend
}

// A GitHubRepository represents a GitHub repository. Impliments RemoteRepository.
type GitHubRepository struct {
	url *url.URL
}

func (repo *GitHubRepository) URL() *url.URL {
	return repo.url
}

func (repo *GitHubRepository) IsValid() bool {
	if strings.HasPrefix(repo.url.Path, "/blog/") {
		return false
	}

	// must be /{user}/{project}/?
	pathComponents := strings.Split(strings.TrimRight(repo.url.Path, "/"), "/")
	if len(pathComponents) != 3 {
		return false
	}

	return true
}

func (repo *GitHubRepository) VCS() *VCSBackend {
	return GitBackend
}

// A GitHubGistRepository represents a GitHub Gist repository.
type GitHubGistRepository struct {
	url *url.URL
}

func (repo *GitHubGistRepository) URL() *url.URL {
	return repo.url
}

func (repo *GitHubGistRepository) IsValid() bool {
	return true
}

func (repo *GitHubGistRepository) VCS() *VCSBackend {
	return GitBackend
}

type GoogleCodeRepository struct {
	url *url.URL
}

func (repo *GoogleCodeRepository) URL() *url.URL {
	return repo.url
}

var validGoogleCodePathPattern = regexp.MustCompile(`^/p/[^/]+/?$`)

func (repo *GoogleCodeRepository) IsValid() bool {
	return validGoogleCodePathPattern.MatchString(repo.url.Path)
}

func (repo *GoogleCodeRepository) VCS() *VCSBackend {
	if utils.RunSilently("hg", "identify", repo.url.String()) == nil {
		return MercurialBackend
	} else if utils.RunSilently("git", "ls-remote", repo.url.String()) == nil {
		return GitBackend
	} else {
		return nil
	}
}

type DarksHubRepository struct {
	url *url.URL
}

func (repo *DarksHubRepository) URL() *url.URL {
	return repo.url
}

func (repo *DarksHubRepository) IsValid() bool {
	return strings.Count(repo.url.Path, "/") == 2
}

func (repo *DarksHubRepository) VCS() *VCSBackend {
	return DarcsBackend
}

type BluemixRepository struct {
	url *url.URL
}

func (repo *BluemixRepository) URL() *url.URL {
	return repo.url
}

var validBluemixPathPattern = regexp.MustCompile(`^/git/[^/]+/[^/]+$`)

func (repo *BluemixRepository) IsValid() bool {
	return validBluemixPathPattern.MatchString(repo.url.Path)
}

func (repo *BluemixRepository) VCS() *VCSBackend {
	return GitBackend
}

type OtherRepository struct {
	url *url.URL
}

func (repo *OtherRepository) URL() *url.URL {
	return repo.url
}

func (repo *OtherRepository) IsValid() bool {
	return true
}

func (repo *OtherRepository) VCS() *VCSBackend {
	if GitHasFeatureConfigURLMatch() {
		// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
		// (in gitconfig:)
		//     [ghq "https://ghe.example.com/"]
		//     vcs = github
		vcs, err := GitConfig("--get-urlmatch", "ghq.vcs", repo.URL().String())
		if err != nil {
			utils.Log("error", err.Error())
		}

		if vcs == "git" || vcs == "github" {
			return GitBackend
		}

		if vcs == "svn" || vcs == "subversion" {
			return SubversionBackend
		}

		if vcs == "git-svn" {
			return GitsvnBackend
		}

		if vcs == "hg" || vcs == "mercurial" {
			return MercurialBackend
		}

		if vcs == "darcs" {
			return DarcsBackend
		}
	} else {
		utils.Log("warning", "This version of Git does not support `config --get-urlmatch`; per-URL settings are not available")
	}

	// Detect VCS backend automatically
	if utils.RunSilently("hg", "identify", repo.url.String()) == nil {
		return MercurialBackend
	} else if utils.RunSilently("git", "ls-remote", repo.url.String()) == nil {
		return GitBackend
	} else if utils.RunSilently("svn", "info", repo.url.String()) == nil {
		return SubversionBackend
	} else {
		return nil
	}
}

func NewRemoteRepository(url *url.URL) (RemoteRepository, error) {
	if url.Host == "github.com" {
		return &GitHubRepository{url}, nil
	}

	if url.Host == "gist.github.com" {
		return &GitHubGistRepository{url}, nil
	}

	if url.Host == "code.google.com" {
		return &GoogleCodeRepository{url}, nil
	}

	if url.Host == "hub.darcs.net" {
		return &DarksHubRepository{url}, nil
	}

	if url.Host == "hub.jazz.net" {
		return &BluemixRepository{url}, nil
	}

	gheHosts, err := GitConfigAll("ghq.ghe.host")

	if err != nil {
		return nil, fmt.Errorf("failed to retrieve GH:E hostname from .gitconfig: %s", err)
	}

	for _, host := range gheHosts {
		if url.Host == host {
			return &GitHubRepository{url}, nil
		}
	}

	return &OtherRepository{url}, nil
}
<commit_msg>try git first when detecting VCS<commit_after>package main

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/motemen/ghq/utils"
)

// A RemoteRepository represents a remote repository.
type RemoteRepository interface {
	// The repository URL.
	URL() *url.URL
	// Checks if the URL is valid.
	IsValid() bool
	// The VCS backend that hosts the repository.
	VCS() *VCSBackend
}

// A GitHubRepository represents a GitHub repository. Impliments RemoteRepository.
type GitHubRepository struct {
	url *url.URL
}

func (repo *GitHubRepository) URL() *url.URL {
	return repo.url
}

func (repo *GitHubRepository) IsValid() bool {
	if strings.HasPrefix(repo.url.Path, "/blog/") {
		return false
	}

	// must be /{user}/{project}/?
	pathComponents := strings.Split(strings.TrimRight(repo.url.Path, "/"), "/")
	if len(pathComponents) != 3 {
		return false
	}

	return true
}

func (repo *GitHubRepository) VCS() *VCSBackend {
	return GitBackend
}

// A GitHubGistRepository represents a GitHub Gist repository.
type GitHubGistRepository struct {
	url *url.URL
}

func (repo *GitHubGistRepository) URL() *url.URL {
	return repo.url
}

func (repo *GitHubGistRepository) IsValid() bool {
	return true
}

func (repo *GitHubGistRepository) VCS() *VCSBackend {
	return GitBackend
}

type GoogleCodeRepository struct {
	url *url.URL
}

func (repo *GoogleCodeRepository) URL() *url.URL {
	return repo.url
}

var validGoogleCodePathPattern = regexp.MustCompile(`^/p/[^/]+/?$`)

func (repo *GoogleCodeRepository) IsValid() bool {
	return validGoogleCodePathPattern.MatchString(repo.url.Path)
}

func (repo *GoogleCodeRepository) VCS() *VCSBackend {
	if utils.RunSilently("hg", "identify", repo.url.String()) == nil {
		return MercurialBackend
	} else if utils.RunSilently("git", "ls-remote", repo.url.String()) == nil {
		return GitBackend
	} else {
		return nil
	}
}

type DarksHubRepository struct {
	url *url.URL
}

func (repo *DarksHubRepository) URL() *url.URL {
	return repo.url
}

func (repo *DarksHubRepository) IsValid() bool {
	return strings.Count(repo.url.Path, "/") == 2
}

func (repo *DarksHubRepository) VCS() *VCSBackend {
	return DarcsBackend
}

type BluemixRepository struct {
	url *url.URL
}

func (repo *BluemixRepository) URL() *url.URL {
	return repo.url
}

var validBluemixPathPattern = regexp.MustCompile(`^/git/[^/]+/[^/]+$`)

func (repo *BluemixRepository) IsValid() bool {
	return validBluemixPathPattern.MatchString(repo.url.Path)
}

func (repo *BluemixRepository) VCS() *VCSBackend {
	return GitBackend
}

type OtherRepository struct {
	url *url.URL
}

func (repo *OtherRepository) URL() *url.URL {
	return repo.url
}

func (repo *OtherRepository) IsValid() bool {
	return true
}

func (repo *OtherRepository) VCS() *VCSBackend {
	if GitHasFeatureConfigURLMatch() {
		// Respect 'ghq.url.https://ghe.example.com/.vcs' config variable
		// (in gitconfig:)
		//     [ghq "https://ghe.example.com/"]
		//     vcs = github
		vcs, err := GitConfig("--get-urlmatch", "ghq.vcs", repo.URL().String())
		if err != nil {
			utils.Log("error", err.Error())
		}

		if vcs == "git" || vcs == "github" {
			return GitBackend
		}

		if vcs == "svn" || vcs == "subversion" {
			return SubversionBackend
		}

		if vcs == "git-svn" {
			return GitsvnBackend
		}

		if vcs == "hg" || vcs == "mercurial" {
			return MercurialBackend
		}

		if vcs == "darcs" {
			return DarcsBackend
		}
	} else {
		utils.Log("warning", "This version of Git does not support `config --get-urlmatch`; per-URL settings are not available")
	}

	// Detect VCS backend automatically
	if utils.RunSilently("git", "ls-remote", repo.url.String()) == nil {
		return GitBackend
	} else if utils.RunSilently("hg", "identify", repo.url.String()) == nil {
		return MercurialBackend
	} else if utils.RunSilently("svn", "info", repo.url.String()) == nil {
		return SubversionBackend
	} else {
		return nil
	}
}

func NewRemoteRepository(url *url.URL) (RemoteRepository, error) {
	if url.Host == "github.com" {
		return &GitHubRepository{url}, nil
	}

	if url.Host == "gist.github.com" {
		return &GitHubGistRepository{url}, nil
	}

	if url.Host == "code.google.com" {
		return &GoogleCodeRepository{url}, nil
	}

	if url.Host == "hub.darcs.net" {
		return &DarksHubRepository{url}, nil
	}

	if url.Host == "hub.jazz.net" {
		return &BluemixRepository{url}, nil
	}

	gheHosts, err := GitConfigAll("ghq.ghe.host")

	if err != nil {
		return nil, fmt.Errorf("failed to retrieve GH:E hostname from .gitconfig: %s", err)
	}

	for _, host := range gheHosts {
		if url.Host == host {
			return &GitHubRepository{url}, nil
		}
	}

	return &OtherRepository{url}, nil
}
<|endoftext|>


<commit_before>package remoting

import (
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	"github.com/rogeralsing/gam"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

var _ gam.ActorRef = &RemoteActorRef{}

type server struct{}

func (s *server) Receive(stream gam.Remoting_ReceiveServer) error {
	for {
		envelope, err := stream.Recv()
		if err != nil {
			return err
		}
		pid := envelope.Target
		message := UnpackMessage(envelope)
		pid.Tell(message)
		log.Println("Got message ", message)
	}
}

func remoteHandler(pid *gam.PID) (gam.ActorRef, bool) {
	log.Println("Resolving ", pid)
	ref := NewRemoteActorRef(pid)
	return ref, true
}

var endpointManagerPID *gam.PID

func StartServer(host string) {
	gam.GlobalProcessRegistry.AddRemoteHandler(remoteHandler)
	gam.GlobalProcessRegistry.Host = host

	endpointManagerPID = gam.SpawnTemplate(&EndpointManagerActor{})

	lis, err := net.Listen("tcp", host)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	gam.RegisterRemotingServer(s, &server{})
	log.Printf("Starting GAM server on %v.", host)
	go s.Serve(lis)
}

type RemoteActorRef struct {
	pid *gam.PID
}

func NewRemoteActorRef(pid *gam.PID) gam.ActorRef {
	return &RemoteActorRef{
		pid: pid,
	}
}

func (ref *RemoteActorRef) Tell(message interface{}) {
	switch msg := message.(type) {
	case proto.Message:
		envelope, _ := PackMessage(msg, ref.pid)
		endpointManagerPID.Tell(envelope)
	default:
		log.Printf("failed, trying to send non Proto %v message to %v", msg, ref.pid)
	}
}

func (ref *RemoteActorRef) SendSystemMessage(message gam.SystemMessage) {

}

func (ref *RemoteActorRef) Stop() {

}

func sendMessage(message proto.Message, target *gam.PID) {

}

type EndpointManagerActor struct {
	connections map[string]*gam.PID
}

func (state *EndpointManagerActor) Receive(ctx gam.Context) {
	switch msg := ctx.Message().(type) {
	case gam.Started:
		state.connections = make(map[string]*gam.PID)
		log.Println("Started EndpointManagerActor")
	case *gam.MessageEnvelope:
		pid := state.connections[msg.Target.Host]
		if pid == nil {
			pid = gam.SpawnTemplate(&EndpointSenderActor{host: msg.Target.Host})
			state.connections[msg.Target.Host] = pid
		}
		pid.Tell(msg)
	}
}

type EndpointSenderActor struct {
	host   string
	conn   *grpc.ClientConn
	stream gam.Remoting_ReceiveClient
}

func (state *EndpointSenderActor) Receive(ctx gam.Context) {
	switch msg := ctx.Message().(type) {
	case gam.Started:
		log.Println("Started EndpointSenderActor for host ", state.host)
		conn, err := grpc.Dial(state.host, grpc.WithInsecure())
		state.conn = conn
		if err != nil {
			log.Fatalf("did not connect: %v", err)
		}
		c := gam.NewRemotingClient(conn)
		stream, err := c.Receive(context.Background())
		state.stream = stream
	case gam.Stopped:
		state.conn.Close()
	case *gam.MessageEnvelope:
		state.stream.Send(msg)
	}
}
<commit_msg>bidirectional<commit_after>package remoting

import (
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	"github.com/rogeralsing/gam"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

var _ gam.ActorRef = &RemoteActorRef{}

type server struct{}

func (s *server) Receive(stream gam.Remoting_ReceiveServer) error {
	for {
		envelope, err := stream.Recv()
		if err != nil {
			return err
		}
		pid := envelope.Target
		message := UnpackMessage(envelope)
		pid.Tell(message)
	}
}

func remoteHandler(pid *gam.PID) (gam.ActorRef, bool) {
	ref := NewRemoteActorRef(pid)
	return ref, true
}

var endpointManagerPID *gam.PID

func StartServer(host string) {
	gam.GlobalProcessRegistry.AddRemoteHandler(remoteHandler)
	gam.GlobalProcessRegistry.Host = host

	endpointManagerPID = gam.SpawnTemplate(&EndpointManagerActor{})

	lis, err := net.Listen("tcp", host)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	gam.RegisterRemotingServer(s, &server{})
	log.Printf("Starting GAM server on %v.", host)
	go s.Serve(lis)
}

type RemoteActorRef struct {
	pid *gam.PID
}

func NewRemoteActorRef(pid *gam.PID) gam.ActorRef {
	return &RemoteActorRef{
		pid: pid,
	}
}

func (ref *RemoteActorRef) Tell(message interface{}) {
	switch msg := message.(type) {
	case proto.Message:
		envelope, _ := PackMessage(msg, ref.pid)
		endpointManagerPID.Tell(envelope)
	default:
		log.Printf("failed, trying to send non Proto %v message to %v", msg, ref.pid)
	}
}

func (ref *RemoteActorRef) SendSystemMessage(message gam.SystemMessage) {

}

func (ref *RemoteActorRef) Stop() {

}

func sendMessage(message proto.Message, target *gam.PID) {

}

type EndpointManagerActor struct {
	connections map[string]*gam.PID
}

func (state *EndpointManagerActor) Receive(ctx gam.Context) {
	switch msg := ctx.Message().(type) {
	case gam.Started:
		state.connections = make(map[string]*gam.PID)
		log.Println("Started EndpointManagerActor")
	case *gam.MessageEnvelope:
		pid := state.connections[msg.Target.Host]
		if pid == nil {
			pid = gam.SpawnTemplate(&EndpointSenderActor{host: msg.Target.Host})
			state.connections[msg.Target.Host] = pid
		}
		pid.Tell(msg)
	}
}

type EndpointSenderActor struct {
	host   string
	conn   *grpc.ClientConn
	stream gam.Remoting_ReceiveClient
}

func (state *EndpointSenderActor) Receive(ctx gam.Context) {
	switch msg := ctx.Message().(type) {
	case gam.Started:
		log.Println("Started EndpointSenderActor for host ", state.host)
		conn, err := grpc.Dial(state.host, grpc.WithInsecure())
		state.conn = conn
		if err != nil {
			log.Fatalf("did not connect: %v", err)
		}
		c := gam.NewRemotingClient(conn)
		stream, err := c.Receive(context.Background())
		state.stream = stream
	case gam.Stopped:
		state.conn.Close()
	case *gam.MessageEnvelope:
		state.stream.Send(msg)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Tim Foerster <github@mailserver.1n3t.de>
//
// 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 resources

import (
  "github.com/gin-gonic/gin"
  "gopkg.in/redis.v3"

  log "github.com/Sirupsen/logrus"
)

type GistResource struct {
  Redis *redis.Client
  Engine *gin.RouterGroup
}

func (g GistResource) Routes() {
  g.Engine.GET("/gists/:uuid", g.Get)
}

func (g GistResource) Get(c *gin.Context) {
  gist := Gist{
    GistResource: &g,
    Uuid: c.Param("uuid"),
  }
  if gist.Exists() == false {
    NotFound("Gist", c)
  } else {
    c.JSON(200, gin.H{
      "gist": map[string]string{
        "uuid": gist.Uuid,
      },
      "snippets": gist.GetSnippets(),
    })
  }
}

type Gist struct {
  GistResource *GistResource
  Uuid string
}

func (g *Gist) Exists() bool {
  val, err := g.GistResource.Redis.Exists("gists::"+g.Uuid).Result()
  if err != nil {
    return false
  } else {
    return val
  }
}

func (g *Gist) GetSnippets() map[string]string {
  snippets, err := g.GistResource.Redis.SMembers("gists::"+g.Uuid).Result()
  snippets_pre_collection := map[string]*redis.StringCmd{}
  snippets_collection := map[string]string{}
  if err != nil {
    log.Error(err)
  } else {
    pipe := g.GistResource.Redis.Pipeline()
    defer pipe.Close()
    for _, snipp := range snippets {
      snippets_pre_collection[snipp] = pipe.Get("snippets::"+snipp)
    }
    _, err := pipe.Exec()
    if err != nil {
      log.Error(err)
    } else {
      for k, v := range snippets_pre_collection {
        snippets_collection[k] = v.Val()
      }
    }
  }
  return snippets_collection
}
<commit_msg>Add advanced gist features.<commit_after>// Copyright 2016 Tim Foerster <github@mailserver.1n3t.de>
//
// 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 resources

import (
  "github.com/gin-gonic/gin"
  "gopkg.in/redis.v3"
  "github.com/satori/go.uuid"
  "encoding/json"
  "strconv"

  log "github.com/Sirupsen/logrus"
)

type GistResource struct {
  Redis *redis.Client
  Engine *gin.RouterGroup
}

func (g GistResource) Routes() {
  g.Engine.GET("/gists/:uuid", g.Get)
  g.Engine.POST("/gists/:uuid", g.CreateSnippets)
  g.Engine.POST("/gists", g.CreateSnippets)
}

func (g GistResource) Get(c *gin.Context) {
  gist := Gist{
    GistResource: &g,
    Uuid: c.Param("uuid"),
  }
  if gist.Exists() == false {
    NotFound("Gist", c)
  } else {
    c.JSON(200, gin.H{
      "gist": map[string]string{
        "uuid": gist.Uuid,
      },
      "snippets": gist.GetSnippets(),
    })
  }
}

func (g GistResource) CreateSnippets(c *gin.Context) {
  gist := Gist{
    GistResource: &g,
  }
  if c.Param("uuid") != "" {
    gist.Uuid = c.Param("uuid")
  }
  snippets := []map[string]string{}
  c.Request.ParseForm()
  for i := 0; i < len(c.Request.Form)/2; i++ {
    si := strconv.Itoa(i)
    snippets = append(snippets, map[string]string{
      "paste": string(c.Request.PostFormValue("snippet["+si+"]paste")),
      "lang": string(c.Request.PostFormValue("snippet["+si+"]lang")),
    })
  }
  gist.AddSnippets(snippets)
  c.JSON(200, gin.H{
    "message": gist.Uuid,
  })
}

type Gist struct {
  GistResource *GistResource
  Uuid string
}

func (g *Gist) Exists() bool {
  val, err := g.GistResource.Redis.Exists("gists::"+g.Uuid).Result()
  if err != nil {
    return false
  } else {
    return val
  }
}

func (g *Gist) AddSnippets(snippets []map[string]string) bool {
  pipe := g.GistResource.Redis.Pipeline()
  defer pipe.Close()
  g.Uuid = uuid.NewV4().String()
  for _, v := range snippets {
    temp_uuid := uuid.NewV4().String()
    pipe.SAdd("gists::"+g.Uuid, temp_uuid)
    json, _ := json.Marshal(v)
    pipe.Set("snipptes::"+temp_uuid, string(json), 10000)
  }
  _, err := pipe.Exec()
  if err != nil {
    log.Error(err, "Error on setting snippets")
    return false
  } else {
    return true
  }
}

func (g *Gist) GetSnippets() map[string]string {
  snippets, err := g.GistResource.Redis.SMembers("gists::"+g.Uuid).Result()
  snippets_pre_collection := map[string]*redis.StringCmd{}
  snippets_collection := map[string]string{}
  if err != nil {
    log.Error(err)
  } else {
    pipe := g.GistResource.Redis.Pipeline()
    defer pipe.Close()
    for _, snipp := range snippets {
      snippets_pre_collection[snipp] = pipe.Get("snippets::"+snipp)
    }
    _, err := pipe.Exec()
    if err != nil {
      log.Error(err)
    } else {
      for k, v := range snippets_pre_collection {
        snippets_collection[k] = v.Val()
      }
    }
  }
  return snippets_collection
}
<|endoftext|>


<commit_before>package vault

import (
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/vault/helper/namespace"
)

var rawPolicy = strings.TrimSpace(`
# Developer policy
name = "dev"

# Deny all paths by default
path "*" {
	policy = "deny"
}

# Allow full access to staging
path "stage/*" {
	policy = "sudo"
}

# Limited read privilege to production
path "prod/version" {
	policy = "read"
}

# Read access to foobar
# Also tests stripping of leading slash and parsing of min/max as string and
# integer
path "/foo/bar" {
	policy = "read"
	min_wrapping_ttl = 300
	max_wrapping_ttl = "1h"
}

# Add capabilities for creation and sudo to foobar
# This will be separate; they are combined when compiled into an ACL
# Also tests reverse string/int handling to the above
path "foo/bar" {
	capabilities = ["create", "sudo"]
	min_wrapping_ttl = "300s"
	max_wrapping_ttl = 3600
}

# Check that only allowed_parameters are being added to foobar
path "foo/bar" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
	  "zip" = []
	  "zap" = []
	}
}

# Check that only denied_parameters are being added to bazbar
path "baz/bar" {
	capabilities = ["create", "sudo"]
	denied_parameters = {
	  "zip" = []
	  "zap" = []
	}
}

# Check that both allowed and denied parameters are being added to bizbar
path "biz/bar" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
	  "zim" = []
	  "zam" = []
	}
	denied_parameters = {
	  "zip" = []
	  "zap" = []
	}
}
path "test/types" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
		"map" = [{"good" = "one"}]
		"int" = [1, 2]
	}
	denied_parameters = {
		"string" = ["test"]
		"bool" = [false]
	}
}
path "test/req" {
	capabilities = ["create", "sudo"]
	required_parameters = ["foo"]
}
path "test/mfa" {
	capabilities = ["create", "sudo"]
	mfa_methods = ["my_totp", "my_totp2"]
}
`)

func TestPolicy_Parse(t *testing.T) {
	p, err := ParseACLPolicy(namespace.RootNamespace, rawPolicy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if p.Name != "dev" {
		t.Fatalf("bad name: %q", p.Name)
	}

	expect := []*PathRules{
		&PathRules{
			Path:   "",
			Policy: "deny",
			Capabilities: []string{
				"deny",
			},
			Permissions: &ACLPermissions{CapabilitiesBitmap: DenyCapabilityInt},
			IsPrefix:    true,
		},
		&PathRules{
			Path:   "stage/",
			Policy: "sudo",
			Capabilities: []string{
				"create",
				"read",
				"update",
				"delete",
				"list",
				"sudo",
			},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt | DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt),
			},
			IsPrefix: true,
		},
		&PathRules{
			Path:   "prod/version",
			Policy: "read",
			Capabilities: []string{
				"read",
				"list",
			},
			Permissions: &ACLPermissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
			IsPrefix:    false,
		},
		&PathRules{
			Path:   "foo/bar",
			Policy: "read",
			Capabilities: []string{
				"read",
				"list",
			},
			MinWrappingTTLHCL: 300,
			MaxWrappingTTLHCL: "1h",
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt),
				MinWrappingTTL:     300 * time.Second,
				MaxWrappingTTL:     3600 * time.Second,
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "foo/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			MinWrappingTTLHCL: "300s",
			MaxWrappingTTLHCL: 3600,
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				MinWrappingTTL:     300 * time.Second,
				MaxWrappingTTL:     3600 * time.Second,
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "foo/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"zip": {}, "zap": {}},
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "baz/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			DeniedParametersHCL: map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				DeniedParameters:   map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "biz/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"zim": {}, "zam": {}},
			DeniedParametersHCL:  map[string][]interface{}{"zip": {}, "zap": {}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"zim": {}, "zam": {}},
				DeniedParameters:   map[string][]interface{}{"zip": {}, "zap": {}},
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "test/types",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
			DeniedParametersHCL:  map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
				DeniedParameters:   map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "test/req",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			RequiredParametersHCL: []string{"foo"},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				RequiredParameters: []string{"foo"},
			},
			IsPrefix: false,
		},
		&PathRules{
			Path:   "test/mfa",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			MFAMethodsHCL: []string{
				"my_totp",
				"my_totp2",
			},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				MFAMethods: []string{
					"my_totp",
					"my_totp2",
				},
			},
			IsPrefix: false,
		},
	}

	if !reflect.DeepEqual(p.Paths, expect) {
		t.Errorf("expected \n\n%#v\n\n to be \n\n%#v\n\n", p.Paths, expect)
	}
}

func TestPolicy_ParseBadRoot(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
name = "test"
bad  = "foo"
nope = "yes"
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `invalid key "bad" on line 2`) {
		t.Errorf("bad error: %q", err)
	}

	if !strings.Contains(err.Error(), `invalid key "nope" on line 3`) {
		t.Errorf("bad error: %q", err)
	}
}

func TestPolicy_ParseBadPath(t *testing.T) {
	// The wrong spelling is intended here
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	capabilities = ["read"]
	capabilites  = ["read"]
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `invalid key "capabilites" on line 3`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadPolicy(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	policy = "banana"
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `path "/": invalid policy "banana"`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadWrapping(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	policy = "read"
	min_wrapping_ttl = 400
	max_wrapping_ttl = 200
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `max_wrapping_ttl cannot be less than min_wrapping_ttl`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadCapabilities(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	capabilities = ["read", "banana"]
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `path "/": invalid capability "banana"`) {
		t.Errorf("bad error: %s", err)
	}
}
<commit_msg>minor linting change<commit_after>package vault

import (
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/vault/helper/namespace"
)

var rawPolicy = strings.TrimSpace(`
# Developer policy
name = "dev"

# Deny all paths by default
path "*" {
	policy = "deny"
}

# Allow full access to staging
path "stage/*" {
	policy = "sudo"
}

# Limited read privilege to production
path "prod/version" {
	policy = "read"
}

# Read access to foobar
# Also tests stripping of leading slash and parsing of min/max as string and
# integer
path "/foo/bar" {
	policy = "read"
	min_wrapping_ttl = 300
	max_wrapping_ttl = "1h"
}

# Add capabilities for creation and sudo to foobar
# This will be separate; they are combined when compiled into an ACL
# Also tests reverse string/int handling to the above
path "foo/bar" {
	capabilities = ["create", "sudo"]
	min_wrapping_ttl = "300s"
	max_wrapping_ttl = 3600
}

# Check that only allowed_parameters are being added to foobar
path "foo/bar" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
	  "zip" = []
	  "zap" = []
	}
}

# Check that only denied_parameters are being added to bazbar
path "baz/bar" {
	capabilities = ["create", "sudo"]
	denied_parameters = {
	  "zip" = []
	  "zap" = []
	}
}

# Check that both allowed and denied parameters are being added to bizbar
path "biz/bar" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
	  "zim" = []
	  "zam" = []
	}
	denied_parameters = {
	  "zip" = []
	  "zap" = []
	}
}
path "test/types" {
	capabilities = ["create", "sudo"]
	allowed_parameters = {
		"map" = [{"good" = "one"}]
		"int" = [1, 2]
	}
	denied_parameters = {
		"string" = ["test"]
		"bool" = [false]
	}
}
path "test/req" {
	capabilities = ["create", "sudo"]
	required_parameters = ["foo"]
}
path "test/mfa" {
	capabilities = ["create", "sudo"]
	mfa_methods = ["my_totp", "my_totp2"]
}
`)

func TestPolicy_Parse(t *testing.T) {
	p, err := ParseACLPolicy(namespace.RootNamespace, rawPolicy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if p.Name != "dev" {
		t.Fatalf("bad name: %q", p.Name)
	}

	expect := []*PathRules{
		{
			Path:   "",
			Policy: "deny",
			Capabilities: []string{
				"deny",
			},
			Permissions: &ACLPermissions{CapabilitiesBitmap: DenyCapabilityInt},
			IsPrefix:    true,
		},
		{
			Path:   "stage/",
			Policy: "sudo",
			Capabilities: []string{
				"create",
				"read",
				"update",
				"delete",
				"list",
				"sudo",
			},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | ReadCapabilityInt | UpdateCapabilityInt | DeleteCapabilityInt | ListCapabilityInt | SudoCapabilityInt),
			},
			IsPrefix: true,
		},
		{
			Path:   "prod/version",
			Policy: "read",
			Capabilities: []string{
				"read",
				"list",
			},
			Permissions: &ACLPermissions{CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt)},
			IsPrefix:    false,
		},
		{
			Path:   "foo/bar",
			Policy: "read",
			Capabilities: []string{
				"read",
				"list",
			},
			MinWrappingTTLHCL: 300,
			MaxWrappingTTLHCL: "1h",
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (ReadCapabilityInt | ListCapabilityInt),
				MinWrappingTTL:     300 * time.Second,
				MaxWrappingTTL:     3600 * time.Second,
			},
			IsPrefix: false,
		},
		{
			Path:   "foo/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			MinWrappingTTLHCL: "300s",
			MaxWrappingTTLHCL: 3600,
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				MinWrappingTTL:     300 * time.Second,
				MaxWrappingTTL:     3600 * time.Second,
			},
			IsPrefix: false,
		},
		{
			Path:   "foo/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"zip": {}, "zap": {}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"zip": {}, "zap": {}},
			},
			IsPrefix: false,
		},
		{
			Path:   "baz/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			DeniedParametersHCL: map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				DeniedParameters:   map[string][]interface{}{"zip": []interface{}{}, "zap": []interface{}{}},
			},
			IsPrefix: false,
		},
		{
			Path:   "biz/bar",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"zim": {}, "zam": {}},
			DeniedParametersHCL:  map[string][]interface{}{"zip": {}, "zap": {}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"zim": {}, "zam": {}},
				DeniedParameters:   map[string][]interface{}{"zip": {}, "zap": {}},
			},
			IsPrefix: false,
		},
		{
			Path:   "test/types",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			AllowedParametersHCL: map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
			DeniedParametersHCL:  map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				AllowedParameters:  map[string][]interface{}{"map": []interface{}{map[string]interface{}{"good": "one"}}, "int": []interface{}{1, 2}},
				DeniedParameters:   map[string][]interface{}{"string": []interface{}{"test"}, "bool": []interface{}{false}},
			},
			IsPrefix: false,
		},
		{
			Path:   "test/req",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			RequiredParametersHCL: []string{"foo"},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				RequiredParameters: []string{"foo"},
			},
			IsPrefix: false,
		},
		{
			Path:   "test/mfa",
			Policy: "",
			Capabilities: []string{
				"create",
				"sudo",
			},
			MFAMethodsHCL: []string{
				"my_totp",
				"my_totp2",
			},
			Permissions: &ACLPermissions{
				CapabilitiesBitmap: (CreateCapabilityInt | SudoCapabilityInt),
				MFAMethods: []string{
					"my_totp",
					"my_totp2",
				},
			},
			IsPrefix: false,
		},
	}

	if !reflect.DeepEqual(p.Paths, expect) {
		t.Errorf("expected \n\n%#v\n\n to be \n\n%#v\n\n", p.Paths, expect)
	}
}

func TestPolicy_ParseBadRoot(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
name = "test"
bad  = "foo"
nope = "yes"
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `invalid key "bad" on line 2`) {
		t.Errorf("bad error: %q", err)
	}

	if !strings.Contains(err.Error(), `invalid key "nope" on line 3`) {
		t.Errorf("bad error: %q", err)
	}
}

func TestPolicy_ParseBadPath(t *testing.T) {
	// The wrong spelling is intended here
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	capabilities = ["read"]
	capabilites  = ["read"]
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `invalid key "capabilites" on line 3`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadPolicy(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	policy = "banana"
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `path "/": invalid policy "banana"`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadWrapping(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	policy = "read"
	min_wrapping_ttl = 400
	max_wrapping_ttl = 200
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `max_wrapping_ttl cannot be less than min_wrapping_ttl`) {
		t.Errorf("bad error: %s", err)
	}
}

func TestPolicy_ParseBadCapabilities(t *testing.T) {
	_, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "/" {
	capabilities = ["read", "banana"]
}
`))
	if err == nil {
		t.Fatalf("expected error")
	}

	if !strings.Contains(err.Error(), `path "/": invalid capability "banana"`) {
		t.Errorf("bad error: %s", err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package clientcmd

import (
	"io"
	"sync"

	"k8s.io/klog/v2"

	restclient "k8s.io/client-go/rest"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a client config loader.
// It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that
// the most recent rules are used.  This is useful in cases where you bind flags to loading rule parameters before
// the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid
// passing extraneous information down a call stack
type DeferredLoadingClientConfig struct {
	loader         ClientConfigLoader
	overrides      *ConfigOverrides
	fallbackReader io.Reader

	clientConfig ClientConfig
	loadingLock  sync.Mutex

	// provided for testing
	icc InClusterConfig
}

// InClusterConfig abstracts details of whether the client is running in a cluster for testing.
type InClusterConfig interface {
	ClientConfig
	Possible() bool
}

// NewNonInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name
func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
}

// NewInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name and the fallback auth reader
func NewInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}, fallbackReader: fallbackReader}
}

func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) {
	if config.clientConfig == nil {
		config.loadingLock.Lock()
		defer config.loadingLock.Unlock()

		if config.clientConfig == nil {
			mergedConfig, err := config.loader.Load()
			if err != nil {
				return nil, err
			}

			var mergedClientConfig ClientConfig
			if config.fallbackReader != nil {
				mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader, config.loader)
			} else {
				mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loader)
			}

			config.clientConfig = mergedClientConfig
		}
	}

	return config.clientConfig, nil
}

func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
	mergedConfig, err := config.createClientConfig()
	if err != nil {
		return clientcmdapi.Config{}, err
	}

	return mergedConfig.RawConfig()
}

// ClientConfig implements ClientConfig
func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
	mergedClientConfig, err := config.createClientConfig()
	if err != nil {
		return nil, err
	}

	// load the configuration and return on non-empty errors and if the
	// content differs from the default config
	mergedConfig, err := mergedClientConfig.ClientConfig()
	switch {
	case err != nil:
		if !IsEmptyConfig(err) {
			// return on any error except empty config
			return nil, err
		}
	case mergedConfig != nil:
		// the configuration is valid, but if this is equal to the defaults we should try
		// in-cluster configuration
		if !config.loader.IsDefaultConfig(mergedConfig) {
			return mergedConfig, nil
		}
	}

	// check for in-cluster configuration and use it
	if config.icc.Possible() {
		klog.V(4).Infof("Using in-cluster configuration")
		return config.icc.ClientConfig()
	}

	// return the result of the merged client config
	return mergedConfig, err
}

// Namespace implements KubeConfig
func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
	mergedKubeConfig, err := config.createClientConfig()
	if err != nil {
		return "", false, err
	}

	ns, overridden, err := mergedKubeConfig.Namespace()
	// if we get an error and it is not empty config, or if the merged config defined an explicit namespace, or
	// if in-cluster config is not possible, return immediately
	if (err != nil && !IsEmptyConfig(err)) || overridden || !config.icc.Possible() {
		// return on any error except empty config
		return ns, overridden, err
	}

	if len(ns) > 0 {
		// if we got a non-default namespace from the kubeconfig, use it
		if ns != "default" {
			return ns, false, nil
		}

		// if we got a default namespace, determine whether it was explicit or implicit
		if raw, err := mergedKubeConfig.RawConfig(); err == nil {
			// determine the current context
			currentContext := raw.CurrentContext
			if config.overrides != nil && len(config.overrides.CurrentContext) > 0 {
				currentContext = config.overrides.CurrentContext
			}
			if context := raw.Contexts[currentContext]; context != nil && len(context.Namespace) > 0 {
				return ns, false, nil
			}
		}
	}

	klog.V(4).Infof("Using in-cluster namespace")

	// allow the namespace from the service account token directory to be used.
	return config.icc.Namespace()
}

// ConfigAccess implements ClientConfig
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
	return config.loader
}
<commit_msg>Fix data race on config.clientConfig<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package clientcmd

import (
	"io"
	"sync"

	"k8s.io/klog/v2"

	restclient "k8s.io/client-go/rest"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a client config loader.
// It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that
// the most recent rules are used.  This is useful in cases where you bind flags to loading rule parameters before
// the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid
// passing extraneous information down a call stack
type DeferredLoadingClientConfig struct {
	loader         ClientConfigLoader
	overrides      *ConfigOverrides
	fallbackReader io.Reader

	clientConfig ClientConfig
	loadingLock  sync.Mutex

	// provided for testing
	icc InClusterConfig
}

// InClusterConfig abstracts details of whether the client is running in a cluster for testing.
type InClusterConfig interface {
	ClientConfig
	Possible() bool
}

// NewNonInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name
func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
}

// NewInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name and the fallback auth reader
func NewInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}, fallbackReader: fallbackReader}
}

func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) {
	config.loadingLock.Lock()
	defer config.loadingLock.Unlock()

	if config.clientConfig != nil {
		return config.clientConfig, nil
	}
	mergedConfig, err := config.loader.Load()
	if err != nil {
		return nil, err
	}

	if config.fallbackReader != nil {
		config.clientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader, config.loader)
	} else {
		config.clientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loader)
	}
	return config.clientConfig, nil
}

func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
	mergedConfig, err := config.createClientConfig()
	if err != nil {
		return clientcmdapi.Config{}, err
	}

	return mergedConfig.RawConfig()
}

// ClientConfig implements ClientConfig
func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
	mergedClientConfig, err := config.createClientConfig()
	if err != nil {
		return nil, err
	}

	// load the configuration and return on non-empty errors and if the
	// content differs from the default config
	mergedConfig, err := mergedClientConfig.ClientConfig()
	switch {
	case err != nil:
		if !IsEmptyConfig(err) {
			// return on any error except empty config
			return nil, err
		}
	case mergedConfig != nil:
		// the configuration is valid, but if this is equal to the defaults we should try
		// in-cluster configuration
		if !config.loader.IsDefaultConfig(mergedConfig) {
			return mergedConfig, nil
		}
	}

	// check for in-cluster configuration and use it
	if config.icc.Possible() {
		klog.V(4).Infof("Using in-cluster configuration")
		return config.icc.ClientConfig()
	}

	// return the result of the merged client config
	return mergedConfig, err
}

// Namespace implements KubeConfig
func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
	mergedKubeConfig, err := config.createClientConfig()
	if err != nil {
		return "", false, err
	}

	ns, overridden, err := mergedKubeConfig.Namespace()
	// if we get an error and it is not empty config, or if the merged config defined an explicit namespace, or
	// if in-cluster config is not possible, return immediately
	if (err != nil && !IsEmptyConfig(err)) || overridden || !config.icc.Possible() {
		// return on any error except empty config
		return ns, overridden, err
	}

	if len(ns) > 0 {
		// if we got a non-default namespace from the kubeconfig, use it
		if ns != "default" {
			return ns, false, nil
		}

		// if we got a default namespace, determine whether it was explicit or implicit
		if raw, err := mergedKubeConfig.RawConfig(); err == nil {
			// determine the current context
			currentContext := raw.CurrentContext
			if config.overrides != nil && len(config.overrides.CurrentContext) > 0 {
				currentContext = config.overrides.CurrentContext
			}
			if context := raw.Contexts[currentContext]; context != nil && len(context.Namespace) > 0 {
				return ns, false, nil
			}
		}
	}

	klog.V(4).Infof("Using in-cluster namespace")

	// allow the namespace from the service account token directory to be used.
	return config.icc.Namespace()
}

// ConfigAccess implements ClientConfig
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
	return config.loader
}
<|endoftext|>