<commit_before>package main

import (
	"fmt"
	"log"
	"path/filepath"
	"runtime"
)

// globalPluginDirs returns directories that should be searched for
// globally-installed plugins (not specific to the current configuration).
//
// Earlier entries in this slice get priority over later when multiple copies
// of the same plugin version are found, but newer versions always override
// older versions where both satisfy the provider version constraints.
func globalPluginDirs() []string {
	var ret []string
	// Look in ~/.terraform.d/plugins/ , or its equivalent on non-UNIX
	dir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERROR] Error finding global config directory: %s", err)
	} else {
		machineDir := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
		ret = append(ret, filepath.Join(dir, "plugins", machineDir))
	}

	return ret
}
<commit_msg>keep .terraform.d/plugins for discovery<commit_after>package main

import (
	"fmt"
	"log"
	"path/filepath"
	"runtime"
)

// globalPluginDirs returns directories that should be searched for
// globally-installed plugins (not specific to the current configuration).
//
// Earlier entries in this slice get priority over later when multiple copies
// of the same plugin version are found, but newer versions always override
// older versions where both satisfy the provider version constraints.
func globalPluginDirs() []string {
	var ret []string
	// Look in ~/.terraform.d/plugins/ , or its equivalent on non-UNIX
	dir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERROR] Error finding global config directory: %s", err)
	} else {
		machineDir := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
		ret = append(ret, filepath.Join(dir, "plugins"))
		ret = append(ret, filepath.Join(dir, "plugins", machineDir))
	}

	return ret
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run `npm install`?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if len(plugin.Commands) > 0 {
				Println(plugin.Name, plugin.Version)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for _, plugin := range plugins {
		for _, command := range plugin.Commands {
			command.Run = runFn(plugin, command.Topic, command.Command)
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		names = append(names, plugin.Name)
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	plugins := make([]*Plugin, 0, len(pluginNames))
	for _, name := range pluginNames {
		plugins = append(plugins, getPlugin(name, false))
	}
	AddPluginsToCache(plugins...)
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(plugins ...string) error {
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(plugins...)
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}
<commit_msg>Quick fix for nil plugin pointer problem<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run `npm install`?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		AddPluginsToCache(plugin)
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if len(plugin.Commands) > 0 {
				Println(plugin.Name, plugin.Version)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for _, plugin := range plugins {
		for _, command := range plugin.Commands {
			command.Run = runFn(plugin, command.Topic, command.Command)
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		if plugin != nil {
			names = append(names, plugin.Name)
		}
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	plugins := make([]*Plugin, 0, len(pluginNames))
	for _, name := range pluginNames {
		plugins = append(plugins, getPlugin(name, false))
	}
	AddPluginsToCache(plugins...)
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(plugins ...string) error {
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(plugins...)
	for _, plugin := range plugins {
		lockfile := updateLockPath + "." + plugin
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/stvp/rollbar"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	node.Registry = "http://54.173.158.18"
	node.NpmVersion = "2.1.8"
	if !node.IsSetup() {
		Log("setting up plugins... ")
		must(node.Setup())
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errln("WARNING: command %s has already been defined", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		must(node.InstallPackage(name))
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			must(node.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		must(err)
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		must(err)
		script := fmt.Sprintf(`
		require('%s')
		.commands.filter(function (command) {
			return command.topic == '%s' && command.command == '%s'
		})[0]
		.run(%s)`, module, topic, command, ctxJSON)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		r, w, _ := os.Pipe()
		cmd.Stderr = w
		stderr := captureText(r)
		if err := cmd.Run(); err != nil {
			Errf("Error in %s\n", ctx.Command.Plugin)
			if Channel != "?" {
				nodeErr := fmt.Errorf("%s %s\n%s", ctx.Command.Plugin, ctx.Command, stderr.String())
				rollbar.ErrorWithStack(rollbar.ERR, nodeErr, rollbar.Stack{})
				rollbar.Wait()
			}
			// Exit with the same exit code
			Exit(getExitCode(err))
		}
	}
}

func captureText(r io.Reader) *bytes.Buffer {
	var b bytes.Buffer
	go func() {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			b.WriteString(scanner.Text() + "\n")
			Errln(scanner.Text())
		}
	}()
	return &b
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	must(err)
	must(cmd.Start())
	var plugin Plugin
	err = json.NewDecoder(output).Decode(&plugin)
	if err != nil {
		Errln("Error reading plugin:", name)
		return nil
	}
	must(cmd.Wait())
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<commit_msg>no need to check for required argument<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/stvp/rollbar"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	node.Registry = "http://54.173.158.18"
	node.NpmVersion = "2.1.8"
	if !node.IsSetup() {
		Log("setting up plugins... ")
		must(node.Setup())
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errln("WARNING: command %s has already been defined", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args["name"]
		Errf("Installing plugin %s... ", name)
		must(node.InstallPackage(name))
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			must(node.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		must(err)
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		must(err)
		script := fmt.Sprintf(`
		require('%s')
		.commands.filter(function (command) {
			return command.topic == '%s' && command.command == '%s'
		})[0]
		.run(%s)`, module, topic, command, ctxJSON)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		r, w, _ := os.Pipe()
		cmd.Stderr = w
		stderr := captureText(r)
		if err := cmd.Run(); err != nil {
			Errf("Error in %s\n", ctx.Command.Plugin)
			if Channel != "?" {
				nodeErr := fmt.Errorf("%s %s\n%s", ctx.Command.Plugin, ctx.Command, stderr.String())
				rollbar.ErrorWithStack(rollbar.ERR, nodeErr, rollbar.Stack{})
				rollbar.Wait()
			}
			// Exit with the same exit code
			Exit(getExitCode(err))
		}
	}
}

func captureText(r io.Reader) *bytes.Buffer {
	var b bytes.Buffer
	go func() {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			b.WriteString(scanner.Text() + "\n")
			Errln(scanner.Text())
		}
	}()
	return &b
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	must(err)
	must(cmd.Start())
	var plugin Plugin
	err = json.NewDecoder(output).Decode(&plugin)
	if err != nil {
		Errln("Error reading plugin:", name)
		return nil
	}
	must(cmd.Wait())
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   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 render

import (
	"net/http"

	"github.com/webx-top/echo"
)

var (
	DefaultOptions = &Options{
		Skipper:              echo.DefaultSkipper,
		DataKey:              `data`,
		TmplKey:              `tmpl`,
		DefaultTmpl:          `index`,
		JSONPCallbackName:    `callback`,
		OutputFunc:           Output,
		DefaultErrorHTTPCode: http.StatusInternalServerError,
	}
)

type Options struct {
	Skipper              echo.Skipper
	DataKey              string
	TmplKey              string
	DefaultTmpl          string
	DefaultErrorTmpl     string
	JSONPCallbackName    string
	OutputFunc           func(format string, c echo.Context, opt *Options) error
	DefaultErrorHTTPCode int
}

// Middleware set renderer
func Middleware(d echo.Renderer) echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetRenderer(d)
			return h.Handle(c)
		})
	}
}

func SetDefaultOptions(opt *Options) *Options {
	if opt.Skipper == nil {
		opt.Skipper = DefaultOptions.Skipper
	}
	if opt.OutputFunc == nil {
		opt.OutputFunc = DefaultOptions.OutputFunc
	}
	if len(opt.DataKey) == 0 {
		opt.DataKey = DefaultOptions.DataKey
	}
	if len(opt.TmplKey) == 0 {
		opt.TmplKey = DefaultOptions.TmplKey
	}
	if len(opt.DefaultTmpl) == 0 {
		opt.DefaultTmpl = DefaultOptions.DefaultTmpl
	}
	if len(opt.DefaultErrorTmpl) == 0 {
		opt.DefaultErrorTmpl = DefaultOptions.DefaultErrorTmpl
	}
	if len(opt.JSONPCallbackName) == 0 {
		opt.JSONPCallbackName = DefaultOptions.JSONPCallbackName
	}
	return opt
}

func checkOptions(options ...*Options) *Options {
	var opt *Options
	if len(options) > 0 {
		opt = options[0]
	}
	if opt == nil {
		opt = DefaultOptions
	}
	return opt
}

// AutoOutput Outputs the specified format
func AutoOutput(options ...*Options) echo.MiddlewareFunc {
	opt := checkOptions(options...)
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if opt.Skipper(c) {
				return h.Handle(c)
			}
			if err := h.Handle(c); err != nil {
				return err
			}
			return opt.OutputFunc(c.Format(), c, opt)
		})
	}
}

// Output Outputs the specified format
func Output(format string, c echo.Context, opt *Options) error {
	data := c.Get(opt.DataKey)
	switch format {
	case `json`:
		return c.JSON(data)
	case `jsonp`:
		return c.JSONP(c.Query(opt.JSONPCallbackName), data)
	case `xml`:
		return c.XML(data)
	default:
		tmpl, ok := c.Get(opt.TmplKey).(string)
		if !ok {
			tmpl = opt.DefaultTmpl
		}
		if v, y := data.(echo.Data); y {
			v.SetTmplFuncs()
			return v.Render(tmpl)
		}
		if h, y := data.(echo.H); y {
			v := h.ToData().SetContext(c)
			v.SetTmplFuncs()
			return v.Render(tmpl)
		}
		return c.Render(tmpl, data)
	}
}

func HTTPErrorHandler(templates map[int]string, options ...*Options) echo.HTTPErrorHandler {
	if templates == nil {
		templates = make(map[int]string)
	}
	tmplNum := len(templates)
	opt := checkOptions(options...)
	return func(err error, c echo.Context) {
		code := opt.DefaultErrorHTTPCode
		var msg string
		var panicErr *echo.PanicError
		switch e := err.(type) {
		case *echo.HTTPError:
			if e.Code > 0 {
				code = e.Code
			}
			msg = e.Message
		case *echo.PanicError:
			panicErr = e
		}
		title := http.StatusText(code)
		if c.Echo().Debug() {
			msg = err.Error()
		} else if len(msg) == 0 {
			msg = title
		}
		if !c.Response().Committed() {
			switch {
			case c.Request().Method() == echo.HEAD:
				c.NoContent(code)
			case tmplNum > 0:
				t, y := templates[code]
				if !y && code != 0 {
					t, y = templates[0]
				}
				if y {
					c.Set(opt.TmplKey, t)
					c.Set(opt.DataKey, c.Data().Reset().SetInfo(echo.H{
						"title":   title,
						"content": msg,
						"debug":   c.Echo().Debug(),
						"code":    code,
						"panic":   panicErr,
					}, 0))
					c.SetCode(code)
					if err := opt.OutputFunc(c.Format(), c, opt); err != nil {
						msg += "\n" + err.Error()
						y = false
						c.Logger().Error(err)
					}
				}
				if y {
					break
				}
				fallthrough
			default:
				c.String(msg, code)
			}
		}
		c.Logger().Debug(err)
	}
}
<commit_msg>update<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   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 render

import (
	"net/http"

	"github.com/webx-top/echo"
)

var (
	DefaultOptions = &Options{
		Skipper:              echo.DefaultSkipper,
		DataKey:              `data`,
		TmplKey:              `tmpl`,
		DefaultTmpl:          `index`,
		JSONPCallbackName:    `callback`,
		OutputFunc:           Output,
		DefaultErrorHTTPCode: http.StatusInternalServerError,
	}
)

type Options struct {
	Skipper              echo.Skipper
	DataKey              string
	TmplKey              string
	DefaultTmpl          string
	DefaultErrorTmpl     string
	JSONPCallbackName    string
	OutputFunc           func(format string, c echo.Context, opt *Options) error
	DefaultErrorHTTPCode int
}

// Middleware set renderer
func Middleware(d echo.Renderer) echo.MiddlewareFunc {
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.SetRenderer(d)
			return h.Handle(c)
		})
	}
}

func SetDefaultOptions(opt *Options) *Options {
	if opt.Skipper == nil {
		opt.Skipper = DefaultOptions.Skipper
	}
	if opt.OutputFunc == nil {
		opt.OutputFunc = DefaultOptions.OutputFunc
	}
	if len(opt.DataKey) == 0 {
		opt.DataKey = DefaultOptions.DataKey
	}
	if len(opt.TmplKey) == 0 {
		opt.TmplKey = DefaultOptions.TmplKey
	}
	if len(opt.DefaultTmpl) == 0 {
		opt.DefaultTmpl = DefaultOptions.DefaultTmpl
	}
	if len(opt.DefaultErrorTmpl) == 0 {
		opt.DefaultErrorTmpl = DefaultOptions.DefaultErrorTmpl
	}
	if len(opt.JSONPCallbackName) == 0 {
		opt.JSONPCallbackName = DefaultOptions.JSONPCallbackName
	}
	return opt
}

func checkOptions(options ...*Options) *Options {
	var opt *Options
	if len(options) > 0 {
		opt = options[0]
	}
	if opt == nil {
		opt = DefaultOptions
	}
	return opt
}

// AutoOutput Outputs the specified format
func AutoOutput(options ...*Options) echo.MiddlewareFunc {
	opt := checkOptions(options...)
	return func(h echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if opt.Skipper(c) {
				return h.Handle(c)
			}
			if err := h.Handle(c); err != nil {
				return err
			}
			return opt.OutputFunc(c.Format(), c, opt)
		})
	}
}

func defaultFormatOutput(data interface{}, c echo.Context, opt *Options) error {
	tmpl, ok := c.Get(opt.TmplKey).(string)
	if !ok {
		tmpl = opt.DefaultTmpl
	}
	if v, y := data.(echo.Data); y {
		v.SetTmplFuncs()
		return v.Render(tmpl)
	}
	if h, y := data.(echo.H); y {
		v := h.ToData().SetContext(c)
		v.SetTmplFuncs()
		return v.Render(tmpl)
	}
	return c.Render(tmpl, data)
}

// Output2 Outputs the specified format
func Output2(format string, c echo.Context, opt *Options) error {
	data := c.Get(opt.DataKey)
	switch format {
	case `json`:
		return c.JSON(data, http.StatusOK)
	case `jsonp`:
		return c.JSONP(c.Query(opt.JSONPCallbackName), data, http.StatusOK)
	case `xml`:
		return c.XML(data, http.StatusOK)
	default:
		return defaultFormatOutput(data, c, opt)
	}
}

// Output Outputs the specified format
func Output(format string, c echo.Context, opt *Options) error {
	data := c.Get(opt.DataKey)
	switch format {
	case `json`:
		return c.JSON(data)
	case `jsonp`:
		return c.JSONP(c.Query(opt.JSONPCallbackName), data)
	case `xml`:
		return c.XML(data)
	default:
		return defaultFormatOutput(data, c, opt)
	}
}

func HTTPErrorHandler(templates map[int]string, options ...*Options) echo.HTTPErrorHandler {
	if templates == nil {
		templates = make(map[int]string)
	}
	tmplNum := len(templates)
	opt := checkOptions(options...)
	return func(err error, c echo.Context) {
		code := opt.DefaultErrorHTTPCode
		var msg string
		var panicErr *echo.PanicError
		switch e := err.(type) {
		case *echo.HTTPError:
			if e.Code > 0 {
				code = e.Code
			}
			msg = e.Message
		case *echo.PanicError:
			panicErr = e
		}
		title := http.StatusText(code)
		if c.Echo().Debug() {
			msg = err.Error()
		} else if len(msg) == 0 {
			msg = title
		}
		if !c.Response().Committed() {
			switch {
			case c.Request().Method() == echo.HEAD:
				c.NoContent(code)
			case tmplNum > 0:
				t, y := templates[code]
				if !y && code != 0 {
					t, y = templates[0]
				}
				if y {
					c.Set(opt.TmplKey, t)
					c.Set(opt.DataKey, c.Data().Reset().SetInfo(echo.H{
						"title":   title,
						"content": msg,
						"debug":   c.Echo().Debug(),
						"code":    code,
						"panic":   panicErr,
					}, 0))
					c.SetCode(code)
					if err := opt.OutputFunc(c.Format(), c, opt); err != nil {
						msg += "\n" + err.Error()
						y = false
						c.Logger().Error(err)
					}
				}
				if y {
					break
				}
				fallthrough
			default:
				c.String(msg, code)
			}
		}
		c.Logger().Debug(err)
	}
}
<|endoftext|>


<commit_before>package middlewares

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/cloudfoundry-incubator/bits-service/logger"
)

type PanicMiddleware struct{}

type internalServerErrorResponseBody struct {
	Service       string `json:"service"`
	Error         string `json:"error"`
	VcapRequestID string `json:"vcap-request-id"`
	RequestID     int64  `json:"request-id"`
}

func (middleware *PanicMiddleware) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request, next http.HandlerFunc) {
	defer func() {
		if e := recover(); e != nil {
			logger.From(request).Errorw("Internal Server Error.", "error", fmt.Sprintf("%+v", e))
			responseWriter.WriteHeader(http.StatusInternalServerError)
			body, e := json.Marshal(internalServerErrorResponseBody{
				Service:       "Bits-Service",
				Error:         "Internal Server Error",
				VcapRequestID: safeGetStringValueFrom(request.Context(), "vcap-request-id"),
				RequestID:     safeGetInt64ValueFrom(request.Context(), "request-id"),
			})
			if e != nil {
				// Nothing we can do at this point
				return
			}
			responseWriter.Write(body)
		}
	}()

	next(responseWriter, request)
}

func safeGetStringValueFrom(c context.Context, key string) string {
	if c.Value(key) == nil {
		return ""
	}
	if value, ok := c.Value(key).(string); ok {
		return value
	}
	return ""
}

func safeGetInt64ValueFrom(c context.Context, key string) int64 {
	if c.Value(key) == nil {
		return 0
	}
	if value, ok := c.Value(key).(int64); ok {
		return value
	}
	return 0
}
<commit_msg>Improve panic middleware<commit_after>package middlewares

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"runtime/debug"

	"github.com/cloudfoundry-incubator/bits-service/logger"
	"github.com/pkg/errors"
)

type PanicMiddleware struct{}

type internalServerErrorResponseBody struct {
	Service       string `json:"service"`
	Error         string `json:"error"`
	VcapRequestID string `json:"vcap-request-id"`
	RequestID     int64  `json:"request-id"`
}

func (middleware *PanicMiddleware) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request, next http.HandlerFunc) {
	defer func() {
		if e := recover(); e != nil {
			if _, ok := e.(interface {
				StackTrace() errors.StackTrace
			}); ok {
				logger.From(request).Errorw("Internal Server Error.", "error", fmt.Sprintf("%+v", e))
			} else {
				logger.From(request).Errorw("Internal Server Error.", "error", fmt.Sprintf("%v\n%s", e, debug.Stack()))
			}
			responseWriter.WriteHeader(http.StatusInternalServerError)
			body, e := json.Marshal(internalServerErrorResponseBody{
				Service:       "Bits-Service",
				Error:         "Internal Server Error",
				VcapRequestID: safeGetStringValueFrom(request.Context(), "vcap-request-id"),
				RequestID:     safeGetInt64ValueFrom(request.Context(), "request-id"),
			})
			if e != nil {
				// Nothing we can do at this point
				return
			}
			responseWriter.Write(body)
		}
	}()

	next(responseWriter, request)
}

func safeGetStringValueFrom(c context.Context, key string) string {
	if c.Value(key) == nil {
		return ""
	}
	if value, ok := c.Value(key).(string); ok {
		return value
	}
	return ""
}

func safeGetInt64ValueFrom(c context.Context, key string) int64 {
	if c.Value(key) == nil {
		return 0
	}
	if value, ok := c.Value(key).(int64); ok {
		return value
	}
	return 0
}
<|endoftext|>


<commit_before>package integration_test

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/cloudfoundry/gunk/natsrunner"
	"github.com/cloudfoundry/gunk/timeprovider"
	"github.com/cloudfoundry/yagnats"
	"github.com/pivotal-golang/lager/lagertest"

	Bbs "github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/services_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/cc_messages"
	"github.com/cloudfoundry-incubator/stager/integration/stager_runner"
	"github.com/cloudfoundry/storeadapter/storerunner/etcdstorerunner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var stagerPath string
var etcdRunner *etcdstorerunner.ETCDClusterRunner
var natsRunner *natsrunner.NATSRunner
var runner *stager_runner.StagerRunner

var _ = Describe("Main", func() {
	var (
		natsClient         yagnats.NATSClient
		bbs                *Bbs.BBS
		fileServerPresence services_bbs.Presence
		presenceStatus     <-chan bool
	)

	BeforeEach(func() {
		etcdPort := 5001 + GinkgoParallelNode()
		natsPort := 4001 + GinkgoParallelNode()

		etcdRunner = etcdstorerunner.NewETCDClusterRunner(etcdPort, 1)
		etcdRunner.Start()

		natsRunner = natsrunner.NewNATSRunner(natsPort)
		natsRunner.Start()

		natsClient = natsRunner.MessageBus

		bbs = Bbs.NewBBS(etcdRunner.Adapter(), timeprovider.NewTimeProvider(), lagertest.NewTestLogger("test"))

		var err error

		fileServerPresence, presenceStatus, err = bbs.MaintainFileServerPresence(time.Second, "http://example.com", "file-server-id")
		Ω(err).ShouldNot(HaveOccurred())
		Eventually(presenceStatus).Should(Receive(BeTrue()))

		runner = stager_runner.New(
			stagerPath,
			[]string{fmt.Sprintf("http://127.0.0.1:%d", etcdPort)},
			[]string{fmt.Sprintf("127.0.0.1:%d", natsPort)},
		)
	})

	AfterEach(func(done Done) {
		runner.Stop()
		go func() {
			<-presenceStatus
		}()
		fileServerPresence.Remove()
		etcdRunner.Stop()
		natsRunner.Stop()
		close(done)
	}, 10.0)

	Context("when started", func() {
		BeforeEach(func() {
			runner.Start("--circuses", `{"lucid64":"lifecycle.zip"}`, "--minDiskMB", "2048", "--minMemoryMB", "256", "--minFileDescriptors", "2")
		})

		Describe("when a 'diego.staging.start' message is recieved", func() {
			BeforeEach(func() {
				natsClient.Publish("diego.staging.start", []byte(`
				      {
				        "app_id":"my-app-guid",
                "task_id":"my-task-guid",
                "stack":"lucid64",
                "app_bits_download_uri":"http://example.com/app_bits",
                "file_descriptors":3,
                "memory_mb" : 1024,
                "disk_mb" : 128,
                "buildpacks" : [],
                "environment" : []
				      }
				    `))
			})

			It("desires a staging task via the BBS", func() {
				Eventually(bbs.GetAllPendingTasks, 1.0).Should(HaveLen(1))
				tasks, err := bbs.GetAllPendingTasks()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(tasks[0].MemoryMB).Should(Equal(1024))
				Ω(tasks[0].DiskMB).Should(Equal(2048))
			})

			It("does not exit", func() {
				Consistently(runner.Session()).ShouldNot(gexec.Exit())
			})
		})

		Describe("when a 'diego.docker.staging.start' message is recieved", func() {
			var stagingFinished chan cc_messages.DockerStagingResponseForCC

			BeforeEach(func() {
				stagingFinished = make(chan cc_messages.DockerStagingResponseForCC, 1)

				natsClient.Subscribe("diego.docker.staging.finished", func(msg *yagnats.Message) {
					stagingMsg := cc_messages.DockerStagingResponseForCC{}
					err := json.Unmarshal(msg.Payload, &stagingMsg)
					Ω(err).ShouldNot(HaveOccurred())
					stagingFinished <- stagingMsg
				})

				natsClient.Publish("diego.docker.staging.start", []byte(`
				      {
				        "app_id":"my-app-guid",
                "task_id":"my-task-guid",
                "stack":"lucid64",
                "docker_image_url":"http://docker.docker/docker",
                "file_descriptors":3,
                "memory_mb" : 1024,
                "disk_mb" : 128,
                "environment" : []
				      }
				    `))
			})

			It("sends a docker staging finished NATS message", func() {
				expectedMsg := cc_messages.DockerStagingResponseForCC{
					AppId:  "my-app-guid",
					TaskId: "my-task-guid",
				}
				Eventually(stagingFinished).Should(Receive(&expectedMsg))
			})

			It("does not exit", func() {
				Consistently(runner.Session()).ShouldNot(gexec.Exit())
			})
		})
	})
})

func TestStagerMain(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Integration Suite")
}

var _ = BeforeSuite(func() {
	var err error
	stagerPath, err = gexec.Build("github.com/cloudfoundry-incubator/stager", "-race")
	Ω(err).ShouldNot(HaveOccurred())
})

var _ = AfterSuite(func() {
	gexec.CleanupBuildArtifacts()
	if etcdRunner != nil {
		etcdRunner.Stop()
	}
	if natsRunner != nil {
		natsRunner.Stop()
	}
	if runner != nil {
		runner.Stop()
	}
})
<commit_msg>fix data race in integration test<commit_after>package integration_test

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/cloudfoundry/gunk/natsrunner"
	"github.com/cloudfoundry/gunk/timeprovider"
	"github.com/cloudfoundry/yagnats"
	"github.com/pivotal-golang/lager/lagertest"

	Bbs "github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/services_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/cc_messages"
	"github.com/cloudfoundry-incubator/stager/integration/stager_runner"
	"github.com/cloudfoundry/storeadapter/storerunner/etcdstorerunner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var stagerPath string
var etcdRunner *etcdstorerunner.ETCDClusterRunner
var natsRunner *natsrunner.NATSRunner
var runner *stager_runner.StagerRunner

var _ = Describe("Main", func() {
	var (
		natsClient         yagnats.NATSClient
		bbs                *Bbs.BBS
		fileServerPresence services_bbs.Presence
		presenceStatus     <-chan bool
	)

	BeforeEach(func() {
		etcdPort := 5001 + GinkgoParallelNode()
		natsPort := 4001 + GinkgoParallelNode()

		etcdRunner = etcdstorerunner.NewETCDClusterRunner(etcdPort, 1)
		etcdRunner.Start()

		natsRunner = natsrunner.NewNATSRunner(natsPort)
		natsRunner.Start()

		natsClient = natsRunner.MessageBus

		bbs = Bbs.NewBBS(etcdRunner.Adapter(), timeprovider.NewTimeProvider(), lagertest.NewTestLogger("test"))

		var err error

		fileServerPresence, presenceStatus, err = bbs.MaintainFileServerPresence(time.Second, "http://example.com", "file-server-id")
		Ω(err).ShouldNot(HaveOccurred())
		Eventually(presenceStatus).Should(Receive(BeTrue()))

		runner = stager_runner.New(
			stagerPath,
			[]string{fmt.Sprintf("http://127.0.0.1:%d", etcdPort)},
			[]string{fmt.Sprintf("127.0.0.1:%d", natsPort)},
		)
	})

	AfterEach(func(done Done) {
		runner.Stop()
		go func() {
			<-presenceStatus
		}()
		fileServerPresence.Remove()
		etcdRunner.Stop()
		natsRunner.Stop()
		close(done)
	}, 10.0)

	Context("when started", func() {
		BeforeEach(func() {
			runner.Start("--circuses", `{"lucid64":"lifecycle.zip"}`, "--minDiskMB", "2048", "--minMemoryMB", "256", "--minFileDescriptors", "2")
		})

		Describe("when a 'diego.staging.start' message is recieved", func() {
			BeforeEach(func() {
				natsClient.Publish("diego.staging.start", []byte(`
				      {
				        "app_id":"my-app-guid",
                "task_id":"my-task-guid",
                "stack":"lucid64",
                "app_bits_download_uri":"http://example.com/app_bits",
                "file_descriptors":3,
                "memory_mb" : 1024,
                "disk_mb" : 128,
                "buildpacks" : [],
                "environment" : []
				      }
				    `))
			})

			It("desires a staging task via the BBS", func() {
				Eventually(bbs.GetAllPendingTasks, 1.0).Should(HaveLen(1))
				tasks, err := bbs.GetAllPendingTasks()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(tasks[0].MemoryMB).Should(Equal(1024))
				Ω(tasks[0].DiskMB).Should(Equal(2048))
			})

			It("does not exit", func() {
				Consistently(runner.Session()).ShouldNot(gexec.Exit())
			})
		})

		Describe("when a 'diego.docker.staging.start' message is recieved", func() {
			var stagingFinished chan cc_messages.DockerStagingResponseForCC

			BeforeEach(func() {
				// local var to prevent data race with callback
				finished := make(chan cc_messages.DockerStagingResponseForCC, 1)

				stagingFinished = finished

				natsClient.Subscribe("diego.docker.staging.finished", func(msg *yagnats.Message) {
					stagingMsg := cc_messages.DockerStagingResponseForCC{}
					err := json.Unmarshal(msg.Payload, &stagingMsg)
					Ω(err).ShouldNot(HaveOccurred())
					finished <- stagingMsg
				})

				natsClient.Publish("diego.docker.staging.start", []byte(`
				      {
				        "app_id":"my-app-guid",
                "task_id":"my-task-guid",
                "stack":"lucid64",
                "docker_image_url":"http://docker.docker/docker",
                "file_descriptors":3,
                "memory_mb" : 1024,
                "disk_mb" : 128,
                "environment" : []
				      }
				    `))
			})

			It("sends a docker staging finished NATS message", func() {
				expectedMsg := cc_messages.DockerStagingResponseForCC{
					AppId:  "my-app-guid",
					TaskId: "my-task-guid",
				}
				Eventually(stagingFinished).Should(Receive(&expectedMsg))
			})

			It("does not exit", func() {
				Consistently(runner.Session()).ShouldNot(gexec.Exit())
			})
		})
	})
})

func TestStagerMain(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Integration Suite")
}

var _ = BeforeSuite(func() {
	var err error
	stagerPath, err = gexec.Build("github.com/cloudfoundry-incubator/stager", "-race")
	Ω(err).ShouldNot(HaveOccurred())
})

var _ = AfterSuite(func() {
	gexec.CleanupBuildArtifacts()
	if etcdRunner != nil {
		etcdRunner.Stop()
	}
	if natsRunner != nil {
		natsRunner.Stop()
	}
	if runner != nil {
		runner.Stop()
	}
})
<|endoftext|>


<commit_before>package interfaces

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/Tinker-Ware/gh-service/domain"
	"github.com/gorilla/mux"
	"golang.org/x/oauth2"
)

const htmlIndex = `
Logged in with GitHub
`

const htmlCloseWindow = `
Logged in with GitHub
`

type repoRequest struct {
	Owner   string `json:"owner"`
	Name    string `json:"name"`
	Private bool   `json:"private"`
	Org     string `json:"org"`
}

type fileRequest struct {
	domain.Author
	domain.File
}

type multipleFilesRequest struct {
	Author domain.Author `json:"author"`
	Files  []domain.File `json:"files"`
}

type repositoryResponse struct {
	Repository *domain.Repository `json:"repository"`
}

type repositoriesResponse struct {
	Repositories []domain.Repository `json:"repositories"`
}

// GHInteractor defines all the functions the Github Interactor should have
type GHInteractor interface {
	GHCallback(code, state, incomingState string) (*domain.User, error)
	GHLogin() (string, string)
	ShowUser(username string) (*domain.User, error)
	ShowRepos(username string) ([]domain.Repository, error)
	CreateRepo(username, reponame, org string, private bool) (*domain.Repository, error)
	ShowRepo(username, repo string) (*domain.Repository, error)
	ShowKeys(username string) ([]domain.Key, error)
	CreateKey(username string, key *domain.Key) error
	ShowKey(username string, id int) (*domain.Key, error)
	CreateFile(file domain.File, author domain.Author, username, repo string) error
	AddFiles(files []domain.File, author domain.Author, username, repo string) error
}

// WebServiceHandler has all the necessary fields to run a web-based interface
type WebServiceHandler struct {
	GHInteractor GHInteractor
	APIHost      string
}

// Login is a helper method to test the Github oauth login
func (handler WebServiceHandler) Login(res http.ResponseWriter, req *http.Request) {

	url, state := handler.GHInteractor.GHLogin()

	fmt.Println("State login " + state)

	http.Redirect(res, req, url, http.StatusTemporaryRedirect)

}

type oauthWrapper struct {
	OauthRequest oauthRequest `json:"oauth_request"`
}

type oauthRequest struct {
	UserID int    `json:"user_id"`
	Code   string `json:"code"`
	State  string `json:"state"`
}

type integrationWrapper struct {
	Integration integration `json:"integration"`
}

const integrationURL string = "/api/v1/users/%d/integration"

// Callback manages the Github OAUTH callback
func (handler WebServiceHandler) Callback(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	userToken := req.Header.Get("Authorization")

	decoder := json.NewDecoder(req.Body)

	var oauthwrapper oauthWrapper

	err := decoder.Decode(&oauthwrapper)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	token, err := handler.GHInteractor.GHCallback(oauthwrapper.OauthRequest.Code, "", oauthwrapper.OauthRequest.State)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	wrapper := integrationWrapper{
		Integration: integration{
			UserID:     oauthwrapper.OauthRequest.UserID,
			Token:      token.AccessToken,
			Provider:   "github",
			Username:   token.Username,
			ExpireDate: token.ExpirationDate,
		},
	}

	reqBytes, _ := json.Marshal(&wrapper)

	buf := bytes.NewBuffer(reqBytes)

	path := fmt.Sprintf(integrationURL, oauthwrapper.OauthRequest.UserID)

	request, _ := http.NewRequest(http.MethodPost, handler.APIHost+path, buf)
	request.Header.Add("Authorization", userToken)

	client := &http.Client{}

	resp, _ := client.Do(request)
	if resp.StatusCode == http.StatusCreated {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	// res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusCreated)
	// res.Write(usrB)

}

// Root is a function for the index of the microservice
func (handler WebServiceHandler) Root(res http.ResponseWriter, req *http.Request) {
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(htmlIndex))
}

// ShowUser returns the current logged user
func (handler WebServiceHandler) ShowUser(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	user, err := handler.GHInteractor.ShowUser(username)

	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusNotFound)
		return
	}

	userB, _ := json.Marshal(user)

	res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(userB))
}

// ShowRepos returns a JSON with all the repositories within an user account
func (handler WebServiceHandler) ShowRepos(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	repos, err := handler.GHInteractor.ShowRepos(username)

	if err != nil {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	reposR := repositoriesResponse{
		Repositories: repos,
	}

	reposB, _ := json.Marshal(reposR)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(reposB))
}

// CreateRepo creates a repository in the user account and returns a JSON response
func (handler WebServiceHandler) CreateRepo(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	decoder := json.NewDecoder(req.Body)
	repo := repoRequest{}
	err := decoder.Decode(&repo)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	r, err := handler.GHInteractor.CreateRepo(repo.Owner, repo.Name, repo.Org, repo.Private)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	repoB, _ := json.Marshal(r)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(repoB))

}

// ShowRepo returns a JSON response of a single repository
func (handler WebServiceHandler) ShowRepo(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	repo, err := handler.GHInteractor.ShowRepo(username, repoName)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return

	}

	repoR := repositoryResponse{
		Repository: repo,
	}

	repoB, _ := json.Marshal(repoR)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(repoB))

}

// ShowKeys returns all the keys within github in a JSON response
func (handler WebServiceHandler) ShowKeys(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	keys, err := handler.GHInteractor.ShowKeys(username)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return

	}

	keysB, _ := json.Marshal(keys)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keysB))

}

// CreateKey creates a key in the user repository
func (handler WebServiceHandler) CreateKey(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	decoder := json.NewDecoder(req.Body)
	key := domain.Key{}
	err := decoder.Decode(&key)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	vars := mux.Vars(req)
	username := vars["username"]

	err = handler.GHInteractor.CreateKey(username, &key)

	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	keyB, _ := json.Marshal(key)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keyB))
}

// ShowKey shows a single key whithin the user account on github
func (handler WebServiceHandler) ShowKey(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	idStr := vars["id"]

	id, err := strconv.Atoi(idStr)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	key, err := handler.GHInteractor.ShowKey(username, id)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	keyB, _ := json.Marshal(key)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keyB))

}

// AddFileToRepository adds a single file within an user repository
func (handler WebServiceHandler) AddFileToRepository(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	decoder := json.NewDecoder(req.Body)
	file := fileRequest{}
	err := decoder.Decode(&file)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	err = handler.GHInteractor.CreateFile(file.File, file.Author, username, repoName)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(http.StatusCreated)

}

// AddMultipleFilesToRepository add multiple files into a repository
func (handler WebServiceHandler) AddMultipleFilesToRepository(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	decoder := json.NewDecoder(req.Body)

	request := multipleFilesRequest{}

	err := decoder.Decode(&request)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	err = handler.GHInteractor.AddFiles(request.Files, request.Author, username, repoName)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(http.StatusCreated)

}

func tokenToJSON(token *oauth2.Token) (string, error) {
	d, err := json.Marshal(token)
	if err != nil {
		return "", err
	}
	return string(d), nil

}

func tokenFromJSON(jsonStr string) (*oauth2.Token, error) {
	var token oauth2.Token
	if err := json.Unmarshal([]byte(jsonStr), &token); err != nil {
		return nil, err
	}
	return &token, nil
}
<commit_msg>Fix request to integrations<commit_after>package interfaces

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/Tinker-Ware/gh-service/domain"
	"github.com/gorilla/mux"
	"golang.org/x/oauth2"
)

const htmlIndex = `
Logged in with GitHub
`

const htmlCloseWindow = `
Logged in with GitHub
`

type repoRequest struct {
	Owner   string `json:"owner"`
	Name    string `json:"name"`
	Private bool   `json:"private"`
	Org     string `json:"org"`
}

type fileRequest struct {
	domain.Author
	domain.File
}

type multipleFilesRequest struct {
	Author domain.Author `json:"author"`
	Files  []domain.File `json:"files"`
}

type repositoryResponse struct {
	Repository *domain.Repository `json:"repository"`
}

type repositoriesResponse struct {
	Repositories []domain.Repository `json:"repositories"`
}

// GHInteractor defines all the functions the Github Interactor should have
type GHInteractor interface {
	GHCallback(code, state, incomingState string) (*domain.User, error)
	GHLogin() (string, string)
	ShowUser(username string) (*domain.User, error)
	ShowRepos(username string) ([]domain.Repository, error)
	CreateRepo(username, reponame, org string, private bool) (*domain.Repository, error)
	ShowRepo(username, repo string) (*domain.Repository, error)
	ShowKeys(username string) ([]domain.Key, error)
	CreateKey(username string, key *domain.Key) error
	ShowKey(username string, id int) (*domain.Key, error)
	CreateFile(file domain.File, author domain.Author, username, repo string) error
	AddFiles(files []domain.File, author domain.Author, username, repo string) error
}

// WebServiceHandler has all the necessary fields to run a web-based interface
type WebServiceHandler struct {
	GHInteractor GHInteractor
	APIHost      string
}

// Login is a helper method to test the Github oauth login
func (handler WebServiceHandler) Login(res http.ResponseWriter, req *http.Request) {

	url, state := handler.GHInteractor.GHLogin()

	fmt.Println("State login " + state)

	http.Redirect(res, req, url, http.StatusTemporaryRedirect)

}

type oauthWrapper struct {
	OauthRequest oauthRequest `json:"oauth_request"`
}

type oauthRequest struct {
	UserID int    `json:"user_id"`
	Code   string `json:"code"`
	State  string `json:"state"`
}

type integrationWrapper struct {
	Integration integration `json:"integration"`
}

const integrationURL string = "/api/v1/users/%d/integration"

// Callback manages the Github OAUTH callback
func (handler WebServiceHandler) Callback(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	userToken := req.Header.Get("Authorization")

	decoder := json.NewDecoder(req.Body)

	var oauthwrapper oauthWrapper

	err := decoder.Decode(&oauthwrapper)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	token, err := handler.GHInteractor.GHCallback(oauthwrapper.OauthRequest.Code, "", oauthwrapper.OauthRequest.State)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	wrapper := integrationWrapper{
		Integration: integration{
			UserID:     oauthwrapper.OauthRequest.UserID,
			Token:      token.AccessToken,
			Provider:   "github",
			Username:   token.Username,
			ExpireDate: token.ExpirationDate,
		},
	}

	reqBytes, _ := json.Marshal(&wrapper)

	buf := bytes.NewBuffer(reqBytes)

	path := fmt.Sprintf(integrationURL, oauthwrapper.OauthRequest.UserID)

	request, _ := http.NewRequest(http.MethodPost, handler.APIHost+path, buf)
	request.Header.Add("Authorization", userToken)
	request.Header.Add("Content-Type", "application/json")

	client := &http.Client{}

	resp, _ := client.Do(request)
	if resp.StatusCode != http.StatusCreated {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	// res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusCreated)
	// res.Write(usrB)

}

// Root is a function for the index of the microservice
func (handler WebServiceHandler) Root(res http.ResponseWriter, req *http.Request) {
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(htmlIndex))
}

// ShowUser returns the current logged user
func (handler WebServiceHandler) ShowUser(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	user, err := handler.GHInteractor.ShowUser(username)

	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusNotFound)
		return
	}

	userB, _ := json.Marshal(user)

	res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(userB))
}

// ShowRepos returns a JSON with all the repositories within an user account
func (handler WebServiceHandler) ShowRepos(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	repos, err := handler.GHInteractor.ShowRepos(username)

	if err != nil {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	reposR := repositoriesResponse{
		Repositories: repos,
	}

	reposB, _ := json.Marshal(reposR)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(reposB))
}

// CreateRepo creates a repository in the user account and returns a JSON response
func (handler WebServiceHandler) CreateRepo(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	decoder := json.NewDecoder(req.Body)
	repo := repoRequest{}
	err := decoder.Decode(&repo)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	r, err := handler.GHInteractor.CreateRepo(repo.Owner, repo.Name, repo.Org, repo.Private)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	repoB, _ := json.Marshal(r)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(repoB))

}

// ShowRepo returns a JSON response of a single repository
func (handler WebServiceHandler) ShowRepo(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	repo, err := handler.GHInteractor.ShowRepo(username, repoName)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return

	}

	repoR := repositoryResponse{
		Repository: repo,
	}

	repoB, _ := json.Marshal(repoR)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(repoB))

}

// ShowKeys returns all the keys within github in a JSON response
func (handler WebServiceHandler) ShowKeys(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]

	keys, err := handler.GHInteractor.ShowKeys(username)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return

	}

	keysB, _ := json.Marshal(keys)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keysB))

}

// CreateKey creates a key in the user repository
func (handler WebServiceHandler) CreateKey(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	decoder := json.NewDecoder(req.Body)
	key := domain.Key{}
	err := decoder.Decode(&key)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	vars := mux.Vars(req)
	username := vars["username"]

	err = handler.GHInteractor.CreateKey(username, &key)

	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	keyB, _ := json.Marshal(key)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keyB))
}

// ShowKey shows a single key whithin the user account on github
func (handler WebServiceHandler) ShowKey(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	idStr := vars["id"]

	id, err := strconv.Atoi(idStr)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	key, err := handler.GHInteractor.ShowKey(username, id)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	keyB, _ := json.Marshal(key)

	res.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.WriteHeader(http.StatusOK)
	res.Write([]byte(keyB))

}

// AddFileToRepository adds a single file within an user repository
func (handler WebServiceHandler) AddFileToRepository(res http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	decoder := json.NewDecoder(req.Body)
	file := fileRequest{}
	err := decoder.Decode(&file)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	err = handler.GHInteractor.CreateFile(file.File, file.Author, username, repoName)
	if err != nil {
		fmt.Println(err.Error())
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(http.StatusCreated)

}

// AddMultipleFilesToRepository add multiple files into a repository
func (handler WebServiceHandler) AddMultipleFilesToRepository(res http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	vars := mux.Vars(req)
	username := vars["username"]
	repoName := vars["repo"]

	decoder := json.NewDecoder(req.Body)

	request := multipleFilesRequest{}

	err := decoder.Decode(&request)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	err = handler.GHInteractor.AddFiles(request.Files, request.Author, username, repoName)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(http.StatusCreated)

}

func tokenToJSON(token *oauth2.Token) (string, error) {
	d, err := json.Marshal(token)
	if err != nil {
		return "", err
	}
	return string(d), nil

}

func tokenFromJSON(jsonStr string) (*oauth2.Token, error) {
	var token oauth2.Token
	if err := json.Unmarshal([]byte(jsonStr), &token); err != nil {
		return nil, err
	}
	return &token, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Ceph-CSI 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 cephfs

import (
	"context"
	"errors"
	"fmt"

	"github.com/ceph/ceph-csi/internal/util"
)

const (
	// cephFSCloneFailed indicates that clone is in failed state.
	cephFSCloneFailed = "failed"
	// cephFSCloneCompleted indicates that clone is in in-progress state.
	cephFSCloneInprogress = "in-progress"
	// cephFSCloneComplete indicates that clone is in complete state.
	cephFSCloneComplete = "complete"
	// snapshotIsProtected string indicates that the snapshot is currently protected.
	snapshotIsProtected = "yes"
)

func createCloneFromSubvolume(ctx context.Context, volID, cloneID volumeID, volOpt, parentvolOpt *volumeOptions, cr *util.Credentials) error {
	snapshotID := cloneID
	err := createSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
	if err != nil {
		util.ErrorLog(ctx, "failed to create snapshot %s %v", snapshotID, err)
		return err
	}
	var (
		// if protectErr is not nil we will delete the snapshot as the protect fails
		protectErr error
		// if cloneErr is not nil we will unprotect the snapshot and delete the snapshot
		cloneErr error
	)
	defer func() {
		if protectErr != nil {
			err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
			if err != nil {
				util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			}
		}

		if cloneErr != nil {
			if err = purgeVolume(ctx, cloneID, cr, volOpt, true); err != nil {
				util.ErrorLog(ctx, "failed to delete volume %s: %v", cloneID, err)
			}
			if err = unprotectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
				// Incase the snap is already unprotected we get ErrSnapProtectionExist error code
				// in that case we are safe and we could discard this error and we are good to go
				// ahead with deletion
				if !errors.Is(err, ErrSnapProtectionExist) {
					util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapshotID, err)
				}
			}
			if err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
				util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			}
		}
	}()
	protectErr = protectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
	if protectErr != nil {
		util.ErrorLog(ctx, "failed to protect snapshot %s %v", snapshotID, protectErr)
		return protectErr
	}

	cloneErr = cloneSnapshot(ctx, parentvolOpt, cr, volID, snapshotID, cloneID, volOpt)
	if cloneErr != nil {
		util.ErrorLog(ctx, "failed to clone snapshot %s %s to %s %v", volID, snapshotID, cloneID, cloneErr)
		return cloneErr
	}
	var clone CloneStatus
	clone, cloneErr = getCloneInfo(ctx, volOpt, cr, cloneID)
	if cloneErr != nil {
		return cloneErr
	}

	switch clone.Status.State {
	case cephFSCloneInprogress:
		util.ErrorLog(ctx, "clone is in progress for %v", cloneID)
		return ErrCloneInProgress
	case cephFSCloneFailed:
		util.ErrorLog(ctx, "clone failed for %v", cloneID)
		cloneFailedErr := fmt.Errorf("clone %s is in %s state", cloneID, clone.Status.State)
		return cloneFailedErr
	case cephFSCloneComplete:
		// This is a work around to fix sizing issue for cloned images
		err = resizeVolume(ctx, volOpt, cr, cloneID, volOpt.Size)
		if err != nil {
			util.ErrorLog(ctx, "failed to expand volume %s: %v", cloneID, err)
			return err
		}
		// As we completed clone, remove the intermediate snap
		if err = unprotectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
			// Incase the snap is already unprotected we get ErrSnapProtectionExist error code
			// in that case we are safe and we could discard this error and we are good to go
			// ahead with deletion
			if !errors.Is(err, ErrSnapProtectionExist) {
				util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapshotID, err)
				return err
			}
		}
		if err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
			util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			return err
		}
	}
	return nil
}

func cleanupCloneFromSubvolumeSnapshot(ctx context.Context, volID, cloneID volumeID, parentVolOpt *volumeOptions, cr *util.Credentials) error {
	// snapshot name is same as clone name as we need a name which can be
	// identified during PVC-PVC cloning.
	snapShotID := cloneID
	snapInfo, err := getSnapshotInfo(ctx, parentVolOpt, cr, snapShotID, volID)
	if err != nil {
		if errors.Is(err, ErrSnapNotFound) {
			return nil
		}
		return err
	}

	if snapInfo.Protected == snapshotIsProtected {
		err = unprotectSnapshot(ctx, parentVolOpt, cr, snapShotID, volID)
		if err != nil {
			util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapShotID, err)
			return err
		}
	}
	err = deleteSnapshot(ctx, parentVolOpt, cr, snapShotID, volID)
	if err != nil {
		util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapShotID, err)
		return err
	}
	return nil
}

func createCloneFromSnapshot(ctx context.Context, parentVolOpt, volOptions *volumeOptions, vID *volumeIdentifier, sID *snapshotIdentifier, cr *util.Credentials) error {
	snapID := volumeID(sID.FsSnapshotName)
	err := cloneSnapshot(ctx, parentVolOpt, cr, volumeID(sID.FsSubvolName), snapID, volumeID(vID.FsSubvolName), volOptions)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			if !errors.Is(err, ErrCloneInProgress) {
				if dErr := purgeVolume(ctx, volumeID(vID.FsSubvolName), cr, volOptions, true); dErr != nil {
					util.ErrorLog(ctx, "failed to delete volume %s: %v", vID.FsSubvolName, dErr)
				}
			}
		}
	}()
	var clone = CloneStatus{}
	clone, err = getCloneInfo(ctx, volOptions, cr, volumeID(vID.FsSubvolName))
	if err != nil {
		return err
	}
	switch clone.Status.State {
	case cephFSCloneInprogress:
		return ErrCloneInProgress
	case cephFSCloneFailed:
		return fmt.Errorf("clone %s is in %s state", vID.FsSubvolName, clone.Status.State)
	case cephFSCloneComplete:
		// The clonedvolume currently does not reflect the proper size due to an issue in cephfs
		// however this is getting addressed in cephfs and the parentvolume size will be reflected
		// in the new cloned volume too. Till then we are explicitly making the size set
		err = resizeVolume(ctx, volOptions, cr, volumeID(vID.FsSubvolName), volOptions.Size)
		if err != nil {
			util.ErrorLog(ctx, "failed to expand volume %s with error: %v", vID.FsSubvolName, err)
			return err
		}
	}
	return nil
}

// CloneStatus represents the subvolume clone status.
type CloneStatus struct {
	Status struct {
		State string `json:"state"`
	} `json:"status"`
}

func getCloneInfo(ctx context.Context, volOptions *volumeOptions, cr *util.Credentials, volID volumeID) (CloneStatus, error) {
	clone := CloneStatus{}
	args := []string{
		"fs",
		"clone",
		"status",
		volOptions.FsName,
		string(volID),
		"--group_name",
		volOptions.SubvolumeGroup,
		"-m", volOptions.Monitors,
		"-c", util.CephConfigPath,
		"-n", cephEntityClientPrefix + cr.ID,
		"--keyfile=" + cr.KeyFile,
		"--format=json",
	}
	err := execCommandJSON(
		ctx,
		&clone,
		"ceph",
		args[:]...)
	if err != nil {
		util.ErrorLog(ctx, "failed to get subvolume clone info %s(%s) in fs %s", string(volID), err, volOptions.FsName)
		return clone, err
	}
	return clone, nil
}
<commit_msg>cephfs: correct cephFSCloneInprogress source code comment<commit_after>/*
Copyright 2020 The Ceph-CSI 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 cephfs

import (
	"context"
	"errors"
	"fmt"

	"github.com/ceph/ceph-csi/internal/util"
)

const (
	// cephFSCloneFailed indicates that clone is in failed state.
	cephFSCloneFailed = "failed"
	// cephFSCloneInprogress indicates that clone is in in-progress state.
	cephFSCloneInprogress = "in-progress"
	// cephFSCloneComplete indicates that clone is in complete state.
	cephFSCloneComplete = "complete"
	// snapshotIsProtected string indicates that the snapshot is currently protected.
	snapshotIsProtected = "yes"
)

func createCloneFromSubvolume(ctx context.Context, volID, cloneID volumeID, volOpt, parentvolOpt *volumeOptions, cr *util.Credentials) error {
	snapshotID := cloneID
	err := createSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
	if err != nil {
		util.ErrorLog(ctx, "failed to create snapshot %s %v", snapshotID, err)
		return err
	}
	var (
		// if protectErr is not nil we will delete the snapshot as the protect fails
		protectErr error
		// if cloneErr is not nil we will unprotect the snapshot and delete the snapshot
		cloneErr error
	)
	defer func() {
		if protectErr != nil {
			err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
			if err != nil {
				util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			}
		}

		if cloneErr != nil {
			if err = purgeVolume(ctx, cloneID, cr, volOpt, true); err != nil {
				util.ErrorLog(ctx, "failed to delete volume %s: %v", cloneID, err)
			}
			if err = unprotectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
				// Incase the snap is already unprotected we get ErrSnapProtectionExist error code
				// in that case we are safe and we could discard this error and we are good to go
				// ahead with deletion
				if !errors.Is(err, ErrSnapProtectionExist) {
					util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapshotID, err)
				}
			}
			if err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
				util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			}
		}
	}()
	protectErr = protectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID)
	if protectErr != nil {
		util.ErrorLog(ctx, "failed to protect snapshot %s %v", snapshotID, protectErr)
		return protectErr
	}

	cloneErr = cloneSnapshot(ctx, parentvolOpt, cr, volID, snapshotID, cloneID, volOpt)
	if cloneErr != nil {
		util.ErrorLog(ctx, "failed to clone snapshot %s %s to %s %v", volID, snapshotID, cloneID, cloneErr)
		return cloneErr
	}
	var clone CloneStatus
	clone, cloneErr = getCloneInfo(ctx, volOpt, cr, cloneID)
	if cloneErr != nil {
		return cloneErr
	}

	switch clone.Status.State {
	case cephFSCloneInprogress:
		util.ErrorLog(ctx, "clone is in progress for %v", cloneID)
		return ErrCloneInProgress
	case cephFSCloneFailed:
		util.ErrorLog(ctx, "clone failed for %v", cloneID)
		cloneFailedErr := fmt.Errorf("clone %s is in %s state", cloneID, clone.Status.State)
		return cloneFailedErr
	case cephFSCloneComplete:
		// This is a work around to fix sizing issue for cloned images
		err = resizeVolume(ctx, volOpt, cr, cloneID, volOpt.Size)
		if err != nil {
			util.ErrorLog(ctx, "failed to expand volume %s: %v", cloneID, err)
			return err
		}
		// As we completed clone, remove the intermediate snap
		if err = unprotectSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
			// Incase the snap is already unprotected we get ErrSnapProtectionExist error code
			// in that case we are safe and we could discard this error and we are good to go
			// ahead with deletion
			if !errors.Is(err, ErrSnapProtectionExist) {
				util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapshotID, err)
				return err
			}
		}
		if err = deleteSnapshot(ctx, parentvolOpt, cr, snapshotID, volID); err != nil {
			util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapshotID, err)
			return err
		}
	}
	return nil
}

func cleanupCloneFromSubvolumeSnapshot(ctx context.Context, volID, cloneID volumeID, parentVolOpt *volumeOptions, cr *util.Credentials) error {
	// snapshot name is same as clone name as we need a name which can be
	// identified during PVC-PVC cloning.
	snapShotID := cloneID
	snapInfo, err := getSnapshotInfo(ctx, parentVolOpt, cr, snapShotID, volID)
	if err != nil {
		if errors.Is(err, ErrSnapNotFound) {
			return nil
		}
		return err
	}

	if snapInfo.Protected == snapshotIsProtected {
		err = unprotectSnapshot(ctx, parentVolOpt, cr, snapShotID, volID)
		if err != nil {
			util.ErrorLog(ctx, "failed to unprotect snapshot %s %v", snapShotID, err)
			return err
		}
	}
	err = deleteSnapshot(ctx, parentVolOpt, cr, snapShotID, volID)
	if err != nil {
		util.ErrorLog(ctx, "failed to delete snapshot %s %v", snapShotID, err)
		return err
	}
	return nil
}

func createCloneFromSnapshot(ctx context.Context, parentVolOpt, volOptions *volumeOptions, vID *volumeIdentifier, sID *snapshotIdentifier, cr *util.Credentials) error {
	snapID := volumeID(sID.FsSnapshotName)
	err := cloneSnapshot(ctx, parentVolOpt, cr, volumeID(sID.FsSubvolName), snapID, volumeID(vID.FsSubvolName), volOptions)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			if !errors.Is(err, ErrCloneInProgress) {
				if dErr := purgeVolume(ctx, volumeID(vID.FsSubvolName), cr, volOptions, true); dErr != nil {
					util.ErrorLog(ctx, "failed to delete volume %s: %v", vID.FsSubvolName, dErr)
				}
			}
		}
	}()
	var clone = CloneStatus{}
	clone, err = getCloneInfo(ctx, volOptions, cr, volumeID(vID.FsSubvolName))
	if err != nil {
		return err
	}
	switch clone.Status.State {
	case cephFSCloneInprogress:
		return ErrCloneInProgress
	case cephFSCloneFailed:
		return fmt.Errorf("clone %s is in %s state", vID.FsSubvolName, clone.Status.State)
	case cephFSCloneComplete:
		// The clonedvolume currently does not reflect the proper size due to an issue in cephfs
		// however this is getting addressed in cephfs and the parentvolume size will be reflected
		// in the new cloned volume too. Till then we are explicitly making the size set
		err = resizeVolume(ctx, volOptions, cr, volumeID(vID.FsSubvolName), volOptions.Size)
		if err != nil {
			util.ErrorLog(ctx, "failed to expand volume %s with error: %v", vID.FsSubvolName, err)
			return err
		}
	}
	return nil
}

// CloneStatus represents the subvolume clone status.
type CloneStatus struct {
	Status struct {
		State string `json:"state"`
	} `json:"status"`
}

func getCloneInfo(ctx context.Context, volOptions *volumeOptions, cr *util.Credentials, volID volumeID) (CloneStatus, error) {
	clone := CloneStatus{}
	args := []string{
		"fs",
		"clone",
		"status",
		volOptions.FsName,
		string(volID),
		"--group_name",
		volOptions.SubvolumeGroup,
		"-m", volOptions.Monitors,
		"-c", util.CephConfigPath,
		"-n", cephEntityClientPrefix + cr.ID,
		"--keyfile=" + cr.KeyFile,
		"--format=json",
	}
	err := execCommandJSON(
		ctx,
		&clone,
		"ceph",
		args[:]...)
	if err != nil {
		util.ErrorLog(ctx, "failed to get subvolume clone info %s(%s) in fs %s", string(volID), err, volOptions.FsName)
		return clone, err
	}
	return clone, nil
}
<|endoftext|>