<commit_before>//
// Copyright 2020, Serena Fang
//
// 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 gitlab

import (
	"fmt"
	"time"
)

// InstanceClustersService handles communication with the
// instance clusters related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html
type InstanceClustersService struct {
	client *Client
}

// InstanceCluster represents a GitLab Instance Cluster.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/instance_clusters.html
type InstanceCluster struct {
	ID                 int                 `json:"id"`
	Name               string              `json:"name"`
	Domain             string              `json:"domain"`
	CreatedAt          *time.Time          `json:"created_at"`
	ProviderType       string              `json:"provider_type"`
	PlatformType       string              `json:"platform_type"`
	EnvironmentScope   string              `json:"environment_scope"`
	ClusterType        string              `json:"cluster_type"`
	User               *User               `json:"user"`
	PlatformKubernetes *PlatformKubernetes `json:"platform_kubernetes"`
	ManagementProject  *ManagementProject  `json:"management_project"`
}

func (v InstanceCluster) String() string {
	return Stringify(v)
}

// PlatformKubernetes represents a GitLab Instance Cluster PlatformKubernetes.
type InstanceClusterPlatformKubernetes struct {
	APIURL            string `json:"api_url"`
	Token             string `json:"token"`
	CaCert            string `json:"ca_cert"`
	Namespace         string `json:"namespace"`
	AuthorizationType string `json:"authorization_type"`
}

// ListClusters gets a list of all instance clusters.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#list-instance-clusters
func (s *InstanceClustersService) ListClusters(options ...RequestOptionFunc) ([]*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters")

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var ics []*InstanceCluster
	resp, err := s.client.Do(req, &ics)
	if err != nil {
		return nil, resp, err
	}

	return ics, resp, err
}

// GetCluster gets an instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#get-a-single-instance-cluster
func (s *InstanceClustersService) GetCluster(cluster int, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, &ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// AddInstanceClusterOptions represents the available AddCluster() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#add-existing-cluster-to-instance
type AddInstanceClusterOptions struct {
	Name                *string                               `url:"name,omitempty" json:"name,omitempty"`
	Domain              *string                               `url:"domain,omitempty" json:"domain,omitempty"`
	Enabled             *bool                                 `url:"enabled,omitempty" json:"enabled,omitempty"`
	Managed             *bool                                 `url:"managed,omitempty" json:"managed,omitempty"`
	EnvironmentScope    *string                               `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
	PlatformKubernetes  *AddInstancePlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
	ManagementProjectID *string                               `url:"management_project_id,omitempty" json:"management_project_id,omitempty"`
}

// AddInstancePlatformKubernetesOptions represents the available PlatformKubernetes options for adding.
type AddInstancePlatformKubernetesOptions struct {
	APIURL            *string `url:"api_url,omitempty" json:"api_url,omitempty"`
	Token             *string `url:"token,omitempty" json:"token,omitempty"`
	CaCert            *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
	Namespace         *string `url:"namespace,omitempty" json:"namespace,omitempty"`
	AuthorizationType *string `url:"authorization_type,omitempty" json:"authorization_type,omitempty"`
}

// AddCluster adds an existing cluster to the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#add-existing-instance-cluster
func (s *InstanceClustersService) AddCluster(opt *AddInstanceClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/add")

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// EditInstanceClusterOptions represents the available EditCluster() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#edit-instance-cluster
type EditInstanceClusterOptions struct {
	Name                *string                                `url:"name,omitempty" json:"name,omitempty"`
	Domain              *string                                `url:"domain,omitempty" json:"domain,omitempty"`
	EnvironmentScope    *string                                `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
	ManagementProjectID *string                                `url:"management_project_id,omitempty" json:"management_project_id,omitempty"`
	PlatformKubernetes  *EditInstancePlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
}

// EditInstancePlatformKubernetesOptions represents the available PlatformKubernetes options for editing.
type EditInstancePlatformKubernetesOptions struct {
	APIURL    *string `url:"api_url,omitempty" json:"api_url,omitempty"`
	Token     *string `url:"token,omitempty" json:"token,omitempty"`
	CaCert    *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
	Namespace *string `url:"namespace,omitempty" json:"namespace,omitempty"`
}

// EditCluster updates an existing instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#edit-instance-cluster
func (s *InstanceClustersService) EditCluster(cluster int, opt *EditInstanceClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// DeleteCluster deletes an existing instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#delete-instance-cluster
func (s *InstanceClustersService) DeleteCluster(cluster int, options ...RequestOptionFunc) (*Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>Change Add and Edit to PlatformKubernetesOptions<commit_after>//
// Copyright 2020, Serena Fang
//
// 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 gitlab

import (
	"fmt"
	"time"
)

// InstanceClustersService handles communication with the
// instance clusters related methods of the GitLab API.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html
type InstanceClustersService struct {
	client *Client
}

// InstanceCluster represents a GitLab Instance Cluster.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/instance_clusters.html
type InstanceCluster struct {
	ID                 int                 `json:"id"`
	Name               string              `json:"name"`
	Domain             string              `json:"domain"`
	CreatedAt          *time.Time          `json:"created_at"`
	ProviderType       string              `json:"provider_type"`
	PlatformType       string              `json:"platform_type"`
	EnvironmentScope   string              `json:"environment_scope"`
	ClusterType        string              `json:"cluster_type"`
	User               *User               `json:"user"`
	PlatformKubernetes *PlatformKubernetes `json:"platform_kubernetes"`
	ManagementProject  *ManagementProject  `json:"management_project"`
}

func (v InstanceCluster) String() string {
	return Stringify(v)
}

// PlatformKubernetes represents a GitLab Instance Cluster PlatformKubernetes.
type InstanceClusterPlatformKubernetes struct {
	APIURL            string `json:"api_url"`
	Token             string `json:"token"`
	CaCert            string `json:"ca_cert"`
	Namespace         string `json:"namespace"`
	AuthorizationType string `json:"authorization_type"`
}

// ListClusters gets a list of all instance clusters.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#list-instance-clusters
func (s *InstanceClustersService) ListClusters(options ...RequestOptionFunc) ([]*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters")

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var ics []*InstanceCluster
	resp, err := s.client.Do(req, &ics)
	if err != nil {
		return nil, resp, err
	}

	return ics, resp, err
}

// GetCluster gets an instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#get-a-single-instance-cluster
func (s *InstanceClustersService) GetCluster(cluster int, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, &ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// AddInstanceClusterOptions represents the available AddCluster() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#add-existing-cluster-to-instance
type AddInstanceClusterOptions struct {
	Name                *string                               `url:"name,omitempty" json:"name,omitempty"`
	Domain              *string                               `url:"domain,omitempty" json:"domain,omitempty"`
	Enabled             *bool                                 `url:"enabled,omitempty" json:"enabled,omitempty"`
	Managed             *bool                                 `url:"managed,omitempty" json:"managed,omitempty"`
	EnvironmentScope    *string                               `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
	PlatformKubernetes  *PlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
	ManagementProjectID *string                               `url:"management_project_id,omitempty" json:"management_project_id,omitempty"`
}

// AddInstancePlatformKubernetesOptions represents the available PlatformKubernetes options for adding.
type PlatformKubernetesOptions struct {
	APIURL            *string `url:"api_url,omitempty" json:"api_url,omitempty"`
	Token             *string `url:"token,omitempty" json:"token,omitempty"`
	CaCert            *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
	Namespace         *string `url:"namespace,omitempty" json:"namespace,omitempty"`
	AuthorizationType *string `url:"authorization_type,omitempty" json:"authorization_type,omitempty"`
}

// AddCluster adds an existing cluster to the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#add-existing-instance-cluster
func (s *InstanceClustersService) AddCluster(opt *AddInstanceClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/add")

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// EditInstanceClusterOptions represents the available EditCluster() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#edit-instance-cluster
type EditInstanceClusterOptions struct {
	Name                *string                                `url:"name,omitempty" json:"name,omitempty"`
	Domain              *string                                `url:"domain,omitempty" json:"domain,omitempty"`
	EnvironmentScope    *string                                `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
	ManagementProjectID *string                                `url:"management_project_id,omitempty" json:"management_project_id,omitempty"`
	PlatformKubernetes  *PlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
}

// EditCluster updates an existing instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#edit-instance-cluster
func (s *InstanceClustersService) EditCluster(cluster int, opt *EditInstanceClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	ic := new(InstanceCluster)
	resp, err := s.client.Do(req, ic)
	if err != nil {
		return nil, resp, err
	}

	return ic, resp, err
}

// DeleteCluster deletes an existing instance cluster.
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/instance_clusters.html#delete-instance-cluster
func (s *InstanceClustersService) DeleteCluster(cluster int, options ...RequestOptionFunc) (*Response, error) {
	u := fmt.Sprintf("admin/clusters/%d", cluster)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>package insthugo

import (
	"archive/zip"
	"compress/gzip"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	version = "0.15"
	baseurl = "https://github.com/spf13/hugo/releases/download/v" + version + "/"
)

var (
	usr        user.User
	tempfiles  []string
	filename   = "hugo_" + version + "_" + runtime.GOOS + "_" + runtime.GOARCH
	sha256Hash = map[string]string{
		"hugo_0.15_darwin_386.zip":              "f9b7353f9b64e7aece5f7981e5aa97dc4b31974ce76251edc070e77691bc03e2",
		"hugo_0.15_darwin_amd64.zip":            "aeecd6a12d86ab920f5b04e9486474bbe478dc246cdc2242799849b84c61c6f1",
		"hugo_0.15_dragonfly_amd64.zip":         "e380343789f2b2e0c366c8e1eeb251ccd90eea53dac191ff85d8177b130e53bc",
		"hugo_0.15_freebsd_386.zip":             "98f9210bfa3dcb48bd154879ea1cfe1b0ed8a3d891fdeacbdb4c3fc69b72aac4",
		"hugo_0.15_freebsd_amd64.zip":           "aa6a3028899e76e6920b9b5a64c29e14017ae34120efa67276e614e3a69cb100",
		"hugo_0.15_freebsd_arm.zip":             "de52e1b07caf778bdc3bdb07f39119cd5a1739c8822ebe311cd4f667c43588ac",
		"hugo_0.15_linux_386.tar.gz":            "af28c4cbb16db765535113f361a38b2249c634ce2d3798dcf5b795de6e4b7ecf",
		"hugo_0.15_linux_amd64.tar.gz":          "32a6335bd76f72867efdec9306a8a7eb7b9498a2e0478105efa96c1febadb09b",
		"hugo_0.15_linux_arm.tar.gz":            "886dd1a843c057a46c541011183dd558469250580e81450eedbd1a4d041e9234",
		"hugo_0.15_netbsd_386.zip":              "6245f5db16b33a09466f149d5b7b68a7899d6d624903de9e7e70c4b6ea869a72",
		"hugo_0.15_netbsd_amd64.zip":            "103ea8d81d2a3d707c05e3dd68c98fcf8146ddd36b49bf0e65d9874cee230c88",
		"hugo_0.15_netbsd_arm.zip":              "9c9b5cf4ea3b6169be1b5fc924251a247d9c140dd8a45aa5175031878585ff0a",
		"hugo_0.15_openbsd_386.zip":             "81dfdb3048a27a61b249650241fe4e8da1eda31a3a7311c615eb419f1cdd06b1",
		"hugo_0.15_openbsd_amd64.zip":           "e7447cde0dd7628b05b25b86938018774d8db8156ab1330b364e0e2c6501ad87",
		"hugo_0.15_windows_386_32-bit-only.zip": "0a72f9a1a929f36c0e52fb1c6272b4d37a2bd1a6bd19ce57a6e7b6803b434756",
		"hugo_0.15_windows_amd64.zip":           "9f03602e48ae2199e06431d7436fb3b9464538c0d44aac9a76eb98e1d4d5d727",
	}
)

// Install installs Hugo
func Install() string {
	usr, err := user.Current()
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	caddy := filepath.Clean(usr.HomeDir + "/.caddy/")
	bin := filepath.Clean(caddy + "/bin")
	temp := filepath.Clean(caddy + "/temp")
	hugo := filepath.Clean(bin + "/hugo")

	switch runtime.GOOS {
	case "darwin":
		filename += ".zip"
	case "windows":
		// At least for v0.15 version
		if runtime.GOARCH == "386" {
			filename += "32-bit-only"
		}

		filename += ".zip"
		hugo += ".exe"
	default:
		filename += ".tar.gz"
	}

	// Check if Hugo is already installed
	if _, err := os.Stat(hugo); err == nil {
		return hugo
	}

	fmt.Println("Unable to find Hugo on " + caddy)

	err = os.MkdirAll(caddy, 0774)
	err = os.Mkdir(bin, 0774)
	err = os.Mkdir(temp, 0774)

	if !os.IsExist(err) {
		fmt.Println(err)
		os.Exit(-1)
	}

	tempfile := temp + "/" + filename

	// Create the file
	tempfiles = append(tempfiles, tempfile)
	out, err := os.Create(tempfile)
	out.Chmod(0774)
	if err != nil {
		clean()
		fmt.Println(err)
		os.Exit(-1)
	}
	defer out.Close()

	fmt.Print("Downloading Hugo from GitHub releases... ")

	// Get the data
	resp, err := http.Get(baseurl + filename)
	if err != nil {
		fmt.Println("An error ocurred while downloading. If this error persists, try downloading Hugo from \"https://github.com/spf13/hugo/releases/\" and put the executable in " + bin + " and rename it to 'hugo' or 'hugo.exe' if you're on Windows.")
		fmt.Println(err)
		os.Exit(-1)
	}
	defer resp.Body.Close()

	// Writer the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	fmt.Println("downloaded.")
	fmt.Print("Checking SHA256...")

	hasher := sha256.New()
	f, err := os.Open(tempfile)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	if _, err := io.Copy(hasher, f); err != nil {
		log.Fatal(err)
	}

	if hex.EncodeToString(hasher.Sum(nil)) != sha256Hash[filename] {
		fmt.Println("can't verify SHA256.")
		os.Exit(-1)
	}

	fmt.Println("checked!")
	fmt.Print("Unziping... ")

	// Unzip or Ungzip the file
	switch runtime.GOOS {
	case "darwin", "windows":
		err = unzip(tempfile, bin)
	default:
		err = ungzip(tempfile, bin)
	}

	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	fmt.Println("done.")

	tempfiles = append(tempfiles, bin+"/README.md", bin+"/LICENSE.md")
	clean()

	ftorename := bin + "/" + strings.Replace(filename, ".tar.gz", "", 1)

	if runtime.GOOS == "windows" {
		ftorename = bin + "/" + strings.Replace(filename, ".zip", ".exe", 1)
	}

	os.Rename(ftorename, hugo)
	fmt.Println("Hugo installed at " + hugo)
	return hugo
}

func unzip(archive, target string) error {
	reader, err := zip.OpenReader(archive)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}

	for _, file := range reader.File {
		path := filepath.Join(target, file.Name)
		if file.FileInfo().IsDir() {
			os.MkdirAll(path, file.Mode())
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			return err
		}
		defer fileReader.Close()

		targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}
		defer targetFile.Close()

		if _, err := io.Copy(targetFile, fileReader); err != nil {
			return err
		}
	}

	return nil
}

func ungzip(source, target string) error {
	reader, err := os.Open(source)
	if err != nil {
		return err
	}
	defer reader.Close()

	archive, err := gzip.NewReader(reader)
	if err != nil {
		return err
	}
	defer archive.Close()

	target = filepath.Join(target, archive.Name)
	writer, err := os.Create(target)
	if err != nil {
		return err
	}
	defer writer.Close()

	_, err = io.Copy(writer, archive)
	return err
}

func clean() {
	fmt.Print("Removing temporary files... ")

	for _, file := range tempfiles {
		os.Remove(file)
	}

	fmt.Println("done.")
}
<commit_msg>update<commit_after>package insthugo

import (
	"archive/zip"
	"compress/gzip"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	version = "0.15"
	baseurl = "https://github.com/spf13/hugo/releases/download/v" + version + "/"
)

var (
	usr        user.User
	tempfiles  []string
	filename   = "hugo_" + version + "_" + runtime.GOOS + "_" + runtime.GOARCH
	sha256Hash = map[string]string{
		"hugo_0.15_darwin_386.zip":              "f9b7353f9b64e7aece5f7981e5aa97dc4b31974ce76251edc070e77691bc03e2",
		"hugo_0.15_darwin_amd64.zip":            "aeecd6a12d86ab920f5b04e9486474bbe478dc246cdc2242799849b84c61c6f1",
		"hugo_0.15_dragonfly_amd64.zip":         "e380343789f2b2e0c366c8e1eeb251ccd90eea53dac191ff85d8177b130e53bc",
		"hugo_0.15_freebsd_386.zip":             "98f9210bfa3dcb48bd154879ea1cfe1b0ed8a3d891fdeacbdb4c3fc69b72aac4",
		"hugo_0.15_freebsd_amd64.zip":           "aa6a3028899e76e6920b9b5a64c29e14017ae34120efa67276e614e3a69cb100",
		"hugo_0.15_freebsd_arm.zip":             "de52e1b07caf778bdc3bdb07f39119cd5a1739c8822ebe311cd4f667c43588ac",
		"hugo_0.15_linux_386.tar.gz":            "af28c4cbb16db765535113f361a38b2249c634ce2d3798dcf5b795de6e4b7ecf",
		"hugo_0.15_linux_amd64.tar.gz":          "32a6335bd76f72867efdec9306a8a7eb7b9498a2e0478105efa96c1febadb09b",
		"hugo_0.15_linux_arm.tar.gz":            "886dd1a843c057a46c541011183dd558469250580e81450eedbd1a4d041e9234",
		"hugo_0.15_netbsd_386.zip":              "6245f5db16b33a09466f149d5b7b68a7899d6d624903de9e7e70c4b6ea869a72",
		"hugo_0.15_netbsd_amd64.zip":            "103ea8d81d2a3d707c05e3dd68c98fcf8146ddd36b49bf0e65d9874cee230c88",
		"hugo_0.15_netbsd_arm.zip":              "9c9b5cf4ea3b6169be1b5fc924251a247d9c140dd8a45aa5175031878585ff0a",
		"hugo_0.15_openbsd_386.zip":             "81dfdb3048a27a61b249650241fe4e8da1eda31a3a7311c615eb419f1cdd06b1",
		"hugo_0.15_openbsd_amd64.zip":           "e7447cde0dd7628b05b25b86938018774d8db8156ab1330b364e0e2c6501ad87",
		"hugo_0.15_windows_386_32-bit-only.zip": "0a72f9a1a929f36c0e52fb1c6272b4d37a2bd1a6bd19ce57a6e7b6803b434756",
		"hugo_0.15_windows_amd64.zip":           "9f03602e48ae2199e06431d7436fb3b9464538c0d44aac9a76eb98e1d4d5d727",
	}
)

// Install installs Hugo
func Install() string {
	usr, err := user.Current()
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	caddy := filepath.Clean(usr.HomeDir + "/.caddy/")
	bin := filepath.Clean(caddy + "/bin")
	temp := filepath.Clean(caddy + "/temp")
	hugo := filepath.Clean(bin + "/hugo")

	switch runtime.GOOS {
	case "darwin":
		filename += ".zip"
	case "windows":
		// At least for v0.15 version
		if runtime.GOARCH == "386" {
			filename += "32-bit-only"
		}

		filename += ".zip"
		hugo += ".exe"
	default:
		filename += ".tar.gz"
	}

	// Check if Hugo is already installed
	if _, err := os.Stat(hugo); err == nil {
		return hugo
	}

	fmt.Println("Unable to find Hugo on " + caddy)

	err = os.MkdirAll(caddy, 0774)
	err = os.Mkdir(bin, 0774)
	err = os.Mkdir(temp, 0774)

	tempfile := temp + "/" + filename

	fmt.Print("Downloading Hugo from GitHub releases... ")

	// Create the file
	tempfiles = append(tempfiles, tempfile)
	out, err := os.Create(tempfile)
	out.Chmod(0774)
	if err != nil {
		clean()
		fmt.Println(err)
		os.Exit(-1)
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(baseurl + filename)
	if err != nil {
		fmt.Println("An error ocurred while downloading. If this error persists, try downloading Hugo from \"https://github.com/spf13/hugo/releases/\" and put the executable in " + bin + " and rename it to 'hugo' or 'hugo.exe' if you're on Windows.")
		fmt.Println(err)
		os.Exit(-1)
	}
	defer resp.Body.Close()

	// Writer the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	fmt.Println("downloaded.")
	fmt.Print("Checking SHA256...")

	hasher := sha256.New()
	f, err := os.Open(tempfile)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	if _, err := io.Copy(hasher, f); err != nil {
		log.Fatal(err)
	}

	if hex.EncodeToString(hasher.Sum(nil)) != sha256Hash[filename] {
		fmt.Println("can't verify SHA256.")
		os.Exit(-1)
	}

	fmt.Println("checked!")
	fmt.Print("Unziping... ")

	// Unzip or Ungzip the file
	switch runtime.GOOS {
	case "darwin", "windows":
		err = unzip(tempfile, bin)
	default:
		err = ungzip(tempfile, bin)
	}

	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	fmt.Println("done.")

	tempfiles = append(tempfiles, bin+"/README.md", bin+"/LICENSE.md")
	clean()

	ftorename := bin + "/" + strings.Replace(filename, ".tar.gz", "", 1)

	if runtime.GOOS == "windows" {
		ftorename = bin + "/" + strings.Replace(filename, ".zip", ".exe", 1)
	}

	os.Rename(ftorename, hugo)
	fmt.Println("Hugo installed at " + hugo)
	return hugo
}

func unzip(archive, target string) error {
	reader, err := zip.OpenReader(archive)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}

	for _, file := range reader.File {
		path := filepath.Join(target, file.Name)
		if file.FileInfo().IsDir() {
			os.MkdirAll(path, file.Mode())
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			return err
		}
		defer fileReader.Close()

		targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}
		defer targetFile.Close()

		if _, err := io.Copy(targetFile, fileReader); err != nil {
			return err
		}
	}

	return nil
}

func ungzip(source, target string) error {
	reader, err := os.Open(source)
	if err != nil {
		return err
	}
	defer reader.Close()

	archive, err := gzip.NewReader(reader)
	if err != nil {
		return err
	}
	defer archive.Close()

	target = filepath.Join(target, archive.Name)
	writer, err := os.Create(target)
	if err != nil {
		return err
	}
	defer writer.Close()

	_, err = io.Copy(writer, archive)
	return err
}

func clean() {
	fmt.Print("Removing temporary files... ")

	for _, file := range tempfiles {
		os.Remove(file)
	}

	fmt.Println("done.")
}
<|endoftext|>


<commit_before>package remote

import (
	"errors"
	"fmt"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

var (
	nameLookup   = make(map[string]actor.Props)
	activatorPid *actor.PID
)

func spawnActivatorActor() {
	props := actor.FromProducer(newActivatorActor()).WithGuardian(actor.RestartingSupervisorStrategy())
	activatorPid, _ = actor.SpawnNamed(props, "activator")
}

func stopActivatorActor() {
	activatorPid.GracefulStop()
}

//Register a known actor props by name
func Register(kind string, props *actor.Props) {
	nameLookup[kind] = *props
}

//GetKnownKinds returns a slice of known actor "kinds"
func GetKnownKinds() []string {
	keys := make([]string, 0, len(nameLookup))
	for k := range nameLookup {
		keys = append(keys, k)
	}
	return keys
}

type activator struct {
}

//ErrActivatorUnavailable : this error will not panic the Activator.
//It simply tells Partition this Activator is not available
//Partition will then find next available Activator to spawn
var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}

type ActivatorError struct {
	Code       int32
	DoNotPanic bool
}

func (e *ActivatorError) Error() string {
	return fmt.Sprint(e.Code)
}

//ActivatorForAddress returns a PID for the activator at the given address
func ActivatorForAddress(address string) *actor.PID {
	pid := actor.NewPID(address, "activator")
	return pid
}

//SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
func SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
	activator := ActivatorForAddress(address)
	f := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout)
	return f
}

//Spawn spawns a remote actor of a given type at a given address
func Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	return SpawnNamed(address, "", kind, timeout)
}

//SpawnNamed spawns a named remote actor of a given type at a given address
func SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	activator := ActivatorForAddress(address)
	res, err := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout).Result()
	if err != nil {
		return nil, err
	}
	switch msg := res.(type) {
	case *ActorPidResponse:
		return msg, nil
	default:
		return nil, errors.New("remote: Unknown response when remote activating")
	}
}

func newActivatorActor() actor.Producer {
	return func() actor.Actor {
		return &activator{}
	}
}

func (*activator) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		plog.Debug("Started Activator")
	case *ActorPidRequest:
		props, exist := nameLookup[msg.Kind]

		//if props not exist, return error code UNAVAILABLE
		if !exist {
			response := &ActorPidResponse{
				StatusCode: ResponseStatusCodeUNAVAILABLE.ToInt32(),
			}
			context.Respond(response)
			return
		}

		name := msg.Name

		//unnamed actor, assign auto ID
		if name == "" {
			name = actor.ProcessRegistry.NextId()
		}

		pid, err := actor.SpawnNamed(&props, "Remote$"+name)

		if err == nil {
			response := &ActorPidResponse{Pid: pid}
			context.Respond(response)
		} else if err == actor.ErrNameExists {
			response := &ActorPidResponse{
				Pid:        pid,
				StatusCode: ResponseStatusCodePROCESSNAMEALREADYEXIST.ToInt32(),
			}
			context.Respond(response)
		} else if aErr, ok := err.(*ActivatorError); ok {
			response := &ActorPidResponse{
				StatusCode: aErr.Code,
			}
			context.Respond(response)
			if !aErr.DoNotPanic {
				panic(err)
			}
		} else {
			response := &ActorPidResponse{
				StatusCode: ResponseStatusCodeERROR.ToInt32(),
			}
			context.Respond(response)
			panic(err)
		}
	case actor.SystemMessage, actor.AutoReceiveMessage:
		//ignore
	default:
		plog.Error("Activator received unknown message", log.TypeOf("type", msg), log.Message(msg))
	}
}
<commit_msg>Follow dotnet version.<commit_after>package remote

import (
	"errors"
	"fmt"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

var (
	nameLookup   = make(map[string]actor.Props)
	activatorPid *actor.PID
)

func spawnActivatorActor() {
	props := actor.FromProducer(newActivatorActor()).WithGuardian(actor.RestartingSupervisorStrategy())
	activatorPid, _ = actor.SpawnNamed(props, "activator")
}

func stopActivatorActor() {
	activatorPid.GracefulStop()
}

//Register a known actor props by name
func Register(kind string, props *actor.Props) {
	nameLookup[kind] = *props
}

//GetKnownKinds returns a slice of known actor "kinds"
func GetKnownKinds() []string {
	keys := make([]string, 0, len(nameLookup))
	for k := range nameLookup {
		keys = append(keys, k)
	}
	return keys
}

type activator struct {
}

//ErrActivatorUnavailable : this error will not panic the Activator.
//It simply tells Partition this Activator is not available
//Partition will then find next available Activator to spawn
var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}

type ActivatorError struct {
	Code       int32
	DoNotPanic bool
}

func (e *ActivatorError) Error() string {
	return fmt.Sprint(e.Code)
}

//ActivatorForAddress returns a PID for the activator at the given address
func ActivatorForAddress(address string) *actor.PID {
	pid := actor.NewPID(address, "activator")
	return pid
}

//SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
func SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
	activator := ActivatorForAddress(address)
	f := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout)
	return f
}

//Spawn spawns a remote actor of a given type at a given address
func Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	return SpawnNamed(address, "", kind, timeout)
}

//SpawnNamed spawns a named remote actor of a given type at a given address
func SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	activator := ActivatorForAddress(address)
	res, err := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout).Result()
	if err != nil {
		return nil, err
	}
	switch msg := res.(type) {
	case *ActorPidResponse:
		return msg, nil
	default:
		return nil, errors.New("remote: Unknown response when remote activating")
	}
}

func newActivatorActor() actor.Producer {
	return func() actor.Actor {
		return &activator{}
	}
}

func (*activator) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		plog.Debug("Started Activator")
	case *ActorPidRequest:
		props, exist := nameLookup[msg.Kind]

		//if props not exist, return error and panic
		if !exist {
			response := &ActorPidResponse{
				StatusCode: ResponseStatusCodeERROR.ToInt32(),
			}
			context.Respond(response)
			panic(fmt.Errorf("No Props found for kind %s", msg.Kind))
		}

		name := msg.Name

		//unnamed actor, assign auto ID
		if name == "" {
			name = actor.ProcessRegistry.NextId()
		}

		pid, err := actor.SpawnNamed(&props, "Remote$"+name)

		if err == nil {
			response := &ActorPidResponse{Pid: pid}
			context.Respond(response)
		} else if err == actor.ErrNameExists {
			response := &ActorPidResponse{
				Pid:        pid,
				StatusCode: ResponseStatusCodePROCESSNAMEALREADYEXIST.ToInt32(),
			}
			context.Respond(response)
		} else if aErr, ok := err.(*ActivatorError); ok {
			response := &ActorPidResponse{
				StatusCode: aErr.Code,
			}
			context.Respond(response)
			if !aErr.DoNotPanic {
				panic(err)
			}
		} else {
			response := &ActorPidResponse{
				StatusCode: ResponseStatusCodeERROR.ToInt32(),
			}
			context.Respond(response)
			panic(err)
		}
	case actor.SystemMessage, actor.AutoReceiveMessage:
		//ignore
	default:
		plog.Error("Activator received unknown message", log.TypeOf("type", msg), log.Message(msg))
	}
}
<|endoftext|>


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

package util

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/coreos/ignition/internal/log"
	"github.com/coreos/ignition/internal/version"
)

// HttpClient is a simple wrapper around the Go HTTP client that standardizes
// the process and logging of fetching payloads.
type HttpClient struct {
	client *http.Client
	logger *log.Logger
}

// NewHttpClient creates a new client with the given logger.
func NewHttpClient(logger *log.Logger) HttpClient {
	return HttpClient{
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
		logger: logger,
	}
}

// Get performs an HTTP GET on the provided URL and returns the response body,
// HTTP status code, and error (if any).
func (c HttpClient) Get(url string) ([]byte, int, error) {
	return c.GetWithHeader(url, http.Header{})
}

// GetWithHeader performs an HTTP GET on the provided URL with the provided request header
// and returns the response body, HTTP status code, and error (if any). By
// default, User-Agent and Accept are added to the header but these can be
// overridden.
func (c HttpClient) GetWithHeader(url string, header http.Header) ([]byte, int, error) {
	var body []byte
	var status int

	err := c.logger.LogOp(func() error {
		var bodyReader io.ReadCloser
		var err error

		bodyReader, status, err = c.GetReaderWithHeader(url, header)
		if err != nil {
			return err
		}
		defer bodyReader.Close()

		body, err = ioutil.ReadAll(bodyReader)

		return err
	}, "GET %q", url)

	return body, status, err
}

// GetReader performs an HTTP GET on the provided URL and returns the response body Reader,
// HTTP status code, and error (if any).
func (c HttpClient) GetReader(url string) (io.ReadCloser, int, error) {
	return c.GetReaderWithHeader(url, http.Header{})
}

// GetReaderWithHeader performs an HTTP GET on the provided URL with the provided request header
// and returns the response body Reader, HTTP status code, and error (if any). By
// default, User-Agent and Accept are added to the header but these can be
// overridden.
func (c HttpClient) GetReaderWithHeader(url string, header http.Header) (io.ReadCloser, int, error) {
	var body io.ReadCloser
	var status int

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", "Ignition/"+version.Raw)
	req.Header.Set("Accept", "*")
	for key, values := range header {
		req.Header.Del(key)
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return body, status, err
	}

	status = resp.StatusCode
	c.logger.Debug("GET result: %s", http.StatusText(status))
	body = resp.Body

	return body, status, err
}

// FetchConfig calls FetchConfigWithHeader with an empty set of headers.
func (c HttpClient) FetchConfig(url string, acceptedStatuses ...int) []byte {
	return c.FetchConfigWithHeader(url, http.Header{}, acceptedStatuses...)
}

// FetchConfigWithHeader fetches a raw config from the provided URL and returns
// the response body on success or nil on failure. The caller must also provide
// a list of acceptable HTTP status codes and headers. If the response's status
// code is not in the provided list, it is considered a failure. The HTTP
// response must be OK, otherwise an empty (v.s. nil) config is returned. The
// provided headers are merged with a set of default headers.
func (c HttpClient) FetchConfigWithHeader(url string, header http.Header, acceptedStatuses ...int) []byte {
	var config []byte

	c.logger.LogOp(func() error {
		reqHeader := http.Header{
			"Accept-Encoding": []string{"identity"},
			"Accept":          []string{"application/vnd.coreos.ignition+json; version=2.0.0, application/vnd.coreos.ignition+json; version=1; q=0.5, */*; q=0.1"},
		}
		for key, values := range header {
			reqHeader.Del(key)
			for _, value := range values {
				reqHeader.Add(key, value)
			}
		}

		data, status, err := c.GetWithHeader(url, reqHeader)
		if err != nil {
			return err
		}

		for _, acceptedStatus := range acceptedStatuses {
			if status == acceptedStatus {
				if status == http.StatusOK {
					config = data
				} else {
					config = []byte{}
				}
				return nil
			}
		}

		return fmt.Errorf("%s", http.StatusText(status))
	}, "fetching config from %q", url)

	return config
}
<commit_msg>http: timeout based on time until repsonse headers<commit_after>// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"time"

	"github.com/coreos/ignition/internal/log"
	"github.com/coreos/ignition/internal/version"
)

// HttpClient is a simple wrapper around the Go HTTP client that standardizes
// the process and logging of fetching payloads.
type HttpClient struct {
	client *http.Client
	logger *log.Logger
}

// NewHttpClient creates a new client with the given logger.
func NewHttpClient(logger *log.Logger) HttpClient {
	return HttpClient{
		client: &http.Client{
			Transport: &http.Transport{
				ResponseHeaderTimeout: 10 * time.Second,
				Dial: (&net.Dialer{
					Timeout:   30 * time.Second,
					KeepAlive: 30 * time.Second,
				}).Dial,
				TLSHandshakeTimeout: 10 * time.Second,
			},
		},
		logger: logger,
	}
}

// Get performs an HTTP GET on the provided URL and returns the response body,
// HTTP status code, and error (if any).
func (c HttpClient) Get(url string) ([]byte, int, error) {
	return c.GetWithHeader(url, http.Header{})
}

// GetWithHeader performs an HTTP GET on the provided URL with the provided request header
// and returns the response body, HTTP status code, and error (if any). By
// default, User-Agent and Accept are added to the header but these can be
// overridden.
func (c HttpClient) GetWithHeader(url string, header http.Header) ([]byte, int, error) {
	var body []byte
	var status int

	err := c.logger.LogOp(func() error {
		var bodyReader io.ReadCloser
		var err error

		bodyReader, status, err = c.GetReaderWithHeader(url, header)
		if err != nil {
			return err
		}
		defer bodyReader.Close()

		body, err = ioutil.ReadAll(bodyReader)

		return err
	}, "GET %q", url)

	return body, status, err
}

// GetReader performs an HTTP GET on the provided URL and returns the response body Reader,
// HTTP status code, and error (if any).
func (c HttpClient) GetReader(url string) (io.ReadCloser, int, error) {
	return c.GetReaderWithHeader(url, http.Header{})
}

// GetReaderWithHeader performs an HTTP GET on the provided URL with the provided request header
// and returns the response body Reader, HTTP status code, and error (if any). By
// default, User-Agent and Accept are added to the header but these can be
// overridden.
func (c HttpClient) GetReaderWithHeader(url string, header http.Header) (io.ReadCloser, int, error) {
	var body io.ReadCloser
	var status int

	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", "Ignition/"+version.Raw)
	req.Header.Set("Accept", "*")
	for key, values := range header {
		req.Header.Del(key)
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return body, status, err
	}

	status = resp.StatusCode
	c.logger.Debug("GET result: %s", http.StatusText(status))
	body = resp.Body

	return body, status, err
}

// FetchConfig calls FetchConfigWithHeader with an empty set of headers.
func (c HttpClient) FetchConfig(url string, acceptedStatuses ...int) []byte {
	return c.FetchConfigWithHeader(url, http.Header{}, acceptedStatuses...)
}

// FetchConfigWithHeader fetches a raw config from the provided URL and returns
// the response body on success or nil on failure. The caller must also provide
// a list of acceptable HTTP status codes and headers. If the response's status
// code is not in the provided list, it is considered a failure. The HTTP
// response must be OK, otherwise an empty (v.s. nil) config is returned. The
// provided headers are merged with a set of default headers.
func (c HttpClient) FetchConfigWithHeader(url string, header http.Header, acceptedStatuses ...int) []byte {
	var config []byte

	c.logger.LogOp(func() error {
		reqHeader := http.Header{
			"Accept-Encoding": []string{"identity"},
			"Accept":          []string{"application/vnd.coreos.ignition+json; version=2.0.0, application/vnd.coreos.ignition+json; version=1; q=0.5, */*; q=0.1"},
		}
		for key, values := range header {
			reqHeader.Del(key)
			for _, value := range values {
				reqHeader.Add(key, value)
			}
		}

		data, status, err := c.GetWithHeader(url, reqHeader)
		if err != nil {
			return err
		}

		for _, acceptedStatus := range acceptedStatuses {
			if status == acceptedStatus {
				if status == http.StatusOK {
					config = data
				} else {
					config = []byte{}
				}
				return nil
			}
		}

		return fmt.Errorf("%s", http.StatusText(status))
	}, "fetching config from %q", url)

	return config
}
<|endoftext|>


<commit_before>// Copyright 2016 Hajime Hoshi
//
// 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 loop

import (
	"errors"
	"sync"
	"time"

	"github.com/hajimehoshi/ebiten/internal/ui"
)

func CurrentFPS() float64 {
	return currentRunContext.currentFPS()
}

func IsRunning() bool {
	return currentRunContext.isRunning()
}

func IsRunningSlowly() bool {
	return currentRunContext.isRunningSlowly()
}

type runContext struct {
	running       bool
	fps           float64
	runningSlowly bool
	m             sync.RWMutex
}

var currentRunContext *runContext

func (c *runContext) startRunning() {
	c.m.Lock()
	defer c.m.Unlock()
	c.running = true
}

func (c *runContext) isRunning() bool {
	c.m.Lock()
	defer c.m.Unlock()
	return c.running
}

func (c *runContext) endRunning() {
	c.m.Lock()
	defer c.m.Unlock()
	c.running = false
}

func (c *runContext) currentFPS() float64 {
	c.m.RLock()
	defer c.m.RUnlock()
	if !c.running {
		// TODO: Should panic here?
		return 0
	}
	return c.fps
}

func (c *runContext) updateFPS(fps float64) {
	c.m.Lock()
	defer c.m.Unlock()
	c.fps = fps
}

func (c *runContext) isRunningSlowly() bool {
	c.m.RLock()
	defer c.m.RUnlock()
	if !c.running {
		// TODO: Should panic here?
		return false
	}
	return c.runningSlowly
}

func (c *runContext) setRunningSlowly(isRunningSlowly bool) {
	c.m.Lock()
	defer c.m.Unlock()
	c.runningSlowly = isRunningSlowly
}

type GraphicsContext interface {
	SetSize(width, height, scale int) error
	UpdateAndDraw() error
	Draw() error
}

func Run(g GraphicsContext, width, height, scale int, title string, fps int) error {
	if currentRunContext != nil {
		return errors.New("loop: The game is already running")
	}
	currentRunContext = &runContext{}
	currentRunContext.startRunning()
	defer currentRunContext.endRunning()

	if err := ui.CurrentUI().Start(width, height, scale, title); err != nil {
		return err
	}
	// TODO: Use the error value
	defer ui.CurrentUI().Terminate()

	frames := 0
	n := now()
	beforeForUpdate := n
	beforeForFPS := n
	for {
		e, err := ui.CurrentUI().Update()
		if err != nil {
			return err
		}
		switch e := e.(type) {
		case ui.ScreenSizeEvent:
			if err := g.SetSize(e.Width, e.Height, e.ActualScale); err != nil {
				return err
			}
		case ui.CloseEvent:
			return nil
		case ui.RenderEvent:
			n2 := now()
			// If beforeForUpdate is too old, we assume that screen is not shown.
			if 5*int64(time.Second)/int64(fps) < n2-beforeForUpdate {
				currentRunContext.setRunningSlowly(false)
				beforeForUpdate = n2
			} else {
				// Note that generally t is a little different from 1/60[sec].
				t := n2 - beforeForUpdate
				tt := int(t * int64(fps) / int64(time.Second))
				// As t is not accurate 1/60[sec], errors are accumulated.
				// To make the FPS stable, set tt 1 if t is a little less than 1/60[sec].
				if tt == 0 && (int64(time.Second)/int64(fps)-int64(5*time.Millisecond)) < t {
					tt = 1
				}
				if 1 <= tt {
					for i := 0; i < tt; i++ {
						slow := i < tt-1
						currentRunContext.setRunningSlowly(slow)
						if err := g.UpdateAndDraw(); err != nil {
							return err
						}
					}
				} else {
					if err := g.Draw(); err != nil {
						return err
					}
				}
				if err := ui.CurrentUI().SwapBuffers(); err != nil {
					return err
				}
				beforeForUpdate += int64(tt) * int64(time.Second) / int64(fps)
				frames++
			}
			// Calc the current FPS.
			if time.Second <= time.Duration(n2-beforeForFPS) {
				fps := float64(frames) * float64(time.Second) / float64(n2-beforeForFPS)
				currentRunContext.updateFPS(fps)
				beforeForFPS = n2
				frames = 0
			}
			e.Done <- struct{}{}
		default:
			panic("not reach")
		}
	}
}
<commit_msg>loop: Refactoring<commit_after>// Copyright 2016 Hajime Hoshi
//
// 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 loop

import (
	"errors"
	"sync"
	"time"

	"github.com/hajimehoshi/ebiten/internal/ui"
)

func CurrentFPS() float64 {
	return currentRunContext.getCurrentFPS()
}

func IsRunning() bool {
	return currentRunContext.isRunning()
}

func IsRunningSlowly() bool {
	return currentRunContext.isRunningSlowly()
}

type runContext struct {
	running         bool
	fps             int
	currentFPS      float64
	runningSlowly   bool
	frames          int
	beforeForUpdate int64
	beforeForFPS    int64
	m               sync.RWMutex
}

var currentRunContext *runContext

func (c *runContext) startRunning() {
	c.m.Lock()
	defer c.m.Unlock()
	c.running = true
}

func (c *runContext) isRunning() bool {
	c.m.Lock()
	defer c.m.Unlock()
	return c.running
}

func (c *runContext) endRunning() {
	c.m.Lock()
	defer c.m.Unlock()
	c.running = false
}

func (c *runContext) getCurrentFPS() float64 {
	c.m.RLock()
	defer c.m.RUnlock()
	if !c.running {
		// TODO: Should panic here?
		return 0
	}
	return c.currentFPS
}

func (c *runContext) updateFPS(fps float64) {
	c.m.Lock()
	defer c.m.Unlock()
	c.currentFPS = fps
}

func (c *runContext) isRunningSlowly() bool {
	c.m.RLock()
	defer c.m.RUnlock()
	if !c.running {
		// TODO: Should panic here?
		return false
	}
	return c.runningSlowly
}

func (c *runContext) setRunningSlowly(isRunningSlowly bool) {
	c.m.Lock()
	defer c.m.Unlock()
	c.runningSlowly = isRunningSlowly
}

type GraphicsContext interface {
	SetSize(width, height, scale int) error
	UpdateAndDraw() error
	Draw() error
}

func Run(g GraphicsContext, width, height, scale int, title string, fps int) error {
	if currentRunContext != nil {
		return errors.New("loop: The game is already running")
	}
	currentRunContext = &runContext{
		fps: fps,
	}
	currentRunContext.startRunning()
	defer currentRunContext.endRunning()

	if err := ui.CurrentUI().Start(width, height, scale, title); err != nil {
		return err
	}
	// TODO: Use the error value
	defer ui.CurrentUI().Terminate()

	n := now()
	currentRunContext.beforeForUpdate = n
	currentRunContext.beforeForFPS = n
	for {
		e, err := ui.CurrentUI().Update()
		if err != nil {
			return err
		}
		switch e := e.(type) {
		case ui.ScreenSizeEvent:
			if err := g.SetSize(e.Width, e.Height, e.ActualScale); err != nil {
				return err
			}
		case ui.CloseEvent:
			return nil
		case ui.RenderEvent:
			if err := currentRunContext.render(g); err != nil {
				return err
			}
			e.Done <- struct{}{}
		default:
			panic("not reach")
		}
	}
}

func (c *runContext) render(g GraphicsContext) error {
	fps := c.fps
	n := now()
	// If beforeForUpdate is too old, we assume that screen is not shown.
	if 5*int64(time.Second)/int64(fps) < n-c.beforeForUpdate {
		c.setRunningSlowly(false)
		c.beforeForUpdate = n
	} else {
		// Note that generally t is a little different from 1/60[sec].
		t := n - c.beforeForUpdate
		tt := int(t * int64(fps) / int64(time.Second))
		// As t is not accurate 1/60[sec], errors are accumulated.
		// To make the FPS stable, set tt 1 if t is a little less than 1/60[sec].
		if tt == 0 && (int64(time.Second)/int64(fps)-int64(5*time.Millisecond)) < t {
			tt = 1
		}
		if 1 <= tt {
			for i := 0; i < tt; i++ {
				slow := i < tt-1
				c.setRunningSlowly(slow)
				if err := g.UpdateAndDraw(); err != nil {
					return err
				}
			}
		} else {
			if err := g.Draw(); err != nil {
				return err
			}
		}
		if err := ui.CurrentUI().SwapBuffers(); err != nil {
			return err
		}
		c.beforeForUpdate += int64(tt) * int64(time.Second) / int64(fps)
		c.frames++
	}
	// Calc the current FPS.
	if time.Second <= time.Duration(n-c.beforeForFPS) {
		fps := float64(c.frames) * float64(time.Second) / float64(n-c.beforeForFPS)
		c.updateFPS(fps)
		c.beforeForFPS = n
		c.frames = 0
	}
	return nil
}
<|endoftext|>


<commit_before>package reporting

import (
	"github.com/smartystreets/goconvey/gotest"
)

type StoryReport struct {
	Test gotest.T
	Name string
	File string
	Line int
}

func NewStoryReport(test gotest.T) *StoryReport {
	file, line, name := gotest.ResolveExternalCaller()
	self := &StoryReport{}
	self.Test = test
	self.Name = name
	self.File = file
	self.Line = line
	return self
}
<commit_msg>Just use concise test function name.<commit_after>package reporting

import (
	_ "fmt"
	"github.com/smartystreets/goconvey/gotest"
	"strings"
)

type StoryReport struct {
	Test gotest.T
	Name string
	File string
	Line int
}

func NewStoryReport(test gotest.T) *StoryReport {
	file, line, name := gotest.ResolveExternalCaller()
	name = removePackagePath(name)
	self := &StoryReport{}
	self.Test = test
	self.Name = name
	self.File = file
	self.Line = line
	return self
}

// name comes in looking like "github.com/smartystreets/goconvey/examples.TestName".
// We only want the stuff after the last '.', which is the name of the test function.
func removePackagePath(name string) string {
	parts := strings.Split(name, ".")
	if len(parts) == 1 {
		return name
	}
	return parts[len(parts)-1]
}
<|endoftext|>


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

package span

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"unicode"
)

const fileScheme = "file"

// URI represents the full URI for a file.
type URI string

func (uri URI) IsFile() bool {
	return strings.HasPrefix(string(uri), "file://")
}

// Filename returns the file path for the given URI.
// It is an error to call this on a URI that is not a valid filename.
func (uri URI) Filename() string {
	filename, err := filename(uri)
	if err != nil {
		panic(err)
	}
	return filepath.FromSlash(filename)
}

func filename(uri URI) (string, error) {
	// This function is frequently called and its cost is
	// dominated by the allocation of a net.URL.
	// TODO(adonovan): opt: replace by a bespoke parseFileURI
	// function that doesn't allocate.
	if uri == "" {
		return "", nil
	}
	u, err := url.ParseRequestURI(string(uri))
	if err != nil {
		return "", err
	}
	if u.Scheme != fileScheme {
		return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
	}
	// If the URI is a Windows URI, we trim the leading "/" and uppercase
	// the drive letter, which will never be case sensitive.
	if isWindowsDriveURIPath(u.Path) {
		u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
	}
	return u.Path, nil
}

func URIFromURI(s string) URI {
	if !strings.HasPrefix(s, "file://") {
		return URI(s)
	}

	if !strings.HasPrefix(s, "file:///") {
		// VS Code sends URLs with only two slashes, which are invalid. golang/go#39789.
		s = "file:///" + s[len("file://"):]
	}
	// Even though the input is a URI, it may not be in canonical form. VS Code
	// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
	path, err := url.PathUnescape(s[len("file://"):])
	if err != nil {
		panic(err)
	}

	// File URIs from Windows may have lowercase drive letters.
	// Since drive letters are guaranteed to be case insensitive,
	// we change them to uppercase to remain consistent.
	// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
	if isWindowsDriveURIPath(path) {
		path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
	}
	u := url.URL{Scheme: fileScheme, Path: path}
	return URI(u.String())
}

// CompareURI performs a three-valued comparison of two URIs.
// Lexically unequal URIs may compare equal if they are "file:" URIs
// that share the same base name (ignoring case) and denote the same
// file device/inode, according to stat(2).
func CompareURI(a, b URI) int {
	if equalURI(a, b) {
		return 0
	}
	if a < b {
		return -1
	}
	return 1
}

func equalURI(a, b URI) bool {
	if a == b {
		return true
	}
	// If we have the same URI basename, we may still have the same file URIs.
	if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
		return false
	}
	fa, err := filename(a)
	if err != nil {
		return false
	}
	fb, err := filename(b)
	if err != nil {
		return false
	}
	// Stat the files to check if they are equal.
	infoa, err := os.Stat(filepath.FromSlash(fa))
	if err != nil {
		return false
	}
	infob, err := os.Stat(filepath.FromSlash(fb))
	if err != nil {
		return false
	}
	return os.SameFile(infoa, infob)
}

// URIFromPath returns a span URI for the supplied file path.
// It will always have the file scheme.
func URIFromPath(path string) URI {
	if path == "" {
		return ""
	}
	// Handle standard library paths that contain the literal "$GOROOT".
	// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
	const prefix = "$GOROOT"
	if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
		suffix := path[len(prefix):]
		path = runtime.GOROOT() + suffix
	}
	if !isWindowsDrivePath(path) {
		if abs, err := filepath.Abs(path); err == nil {
			path = abs
		}
	}
	// Check the file path again, in case it became absolute.
	if isWindowsDrivePath(path) {
		path = "/" + strings.ToUpper(string(path[0])) + path[1:]
	}
	path = filepath.ToSlash(path)
	u := url.URL{
		Scheme: fileScheme,
		Path:   path,
	}
	return URI(u.String())
}

// isWindowsDrivePath returns true if the file path is of the form used by
// Windows. We check if the path begins with a drive letter, followed by a ":".
// For example: C:/x/y/z.
func isWindowsDrivePath(path string) bool {
	if len(path) < 3 {
		return false
	}
	return unicode.IsLetter(rune(path[0])) && path[1] == ':'
}

// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
// (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:").
func isWindowsDriveURIPath(uri string) bool {
	if len(uri) < 4 {
		return false
	}
	return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
}
<commit_msg>internal/span: optimise URI.Filename to avoid allocation<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package span

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"unicode"
)

const fileScheme = "file"

// URI represents the full URI for a file.
type URI string

func (uri URI) IsFile() bool {
	return strings.HasPrefix(string(uri), "file://")
}

// Filename returns the file path for the given URI.
// It is an error to call this on a URI that is not a valid filename.
func (uri URI) Filename() string {
	filename, err := filename(uri)
	if err != nil {
		panic(err)
	}
	return filepath.FromSlash(filename)
}

func filename(uri URI) (string, error) {
	if uri == "" {
		return "", nil
	}

	// This conservative check for the common case
	// of a simple non-empty absolute POSIX filename
	// avoids the allocation of a net.URL.
	if strings.HasPrefix(string(uri), "file:///") {
		rest := string(uri)[len("file://"):] // leave one slash
		for i := 0; i < len(rest); i++ {
			b := rest[i]
			// Reject these cases:
			if b < ' ' || b == 0x7f || // control character
				b == '%' || b == '+' || // URI escape
				b == ':' || // Windows drive letter
				b == '@' || b == '&' || b == '?' { // authority or query
				goto slow
			}
		}
		return rest, nil
	}
slow:

	u, err := url.ParseRequestURI(string(uri))
	if err != nil {
		return "", err
	}
	if u.Scheme != fileScheme {
		return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
	}
	// If the URI is a Windows URI, we trim the leading "/" and uppercase
	// the drive letter, which will never be case sensitive.
	if isWindowsDriveURIPath(u.Path) {
		u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
	}

	return u.Path, nil
}

func URIFromURI(s string) URI {
	if !strings.HasPrefix(s, "file://") {
		return URI(s)
	}

	if !strings.HasPrefix(s, "file:///") {
		// VS Code sends URLs with only two slashes, which are invalid. golang/go#39789.
		s = "file:///" + s[len("file://"):]
	}
	// Even though the input is a URI, it may not be in canonical form. VS Code
	// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
	path, err := url.PathUnescape(s[len("file://"):])
	if err != nil {
		panic(err)
	}

	// File URIs from Windows may have lowercase drive letters.
	// Since drive letters are guaranteed to be case insensitive,
	// we change them to uppercase to remain consistent.
	// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
	if isWindowsDriveURIPath(path) {
		path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
	}
	u := url.URL{Scheme: fileScheme, Path: path}
	return URI(u.String())
}

// CompareURI performs a three-valued comparison of two URIs.
// Lexically unequal URIs may compare equal if they are "file:" URIs
// that share the same base name (ignoring case) and denote the same
// file device/inode, according to stat(2).
func CompareURI(a, b URI) int {
	if equalURI(a, b) {
		return 0
	}
	if a < b {
		return -1
	}
	return 1
}

func equalURI(a, b URI) bool {
	if a == b {
		return true
	}
	// If we have the same URI basename, we may still have the same file URIs.
	if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
		return false
	}
	fa, err := filename(a)
	if err != nil {
		return false
	}
	fb, err := filename(b)
	if err != nil {
		return false
	}
	// Stat the files to check if they are equal.
	infoa, err := os.Stat(filepath.FromSlash(fa))
	if err != nil {
		return false
	}
	infob, err := os.Stat(filepath.FromSlash(fb))
	if err != nil {
		return false
	}
	return os.SameFile(infoa, infob)
}

// URIFromPath returns a span URI for the supplied file path.
// It will always have the file scheme.
func URIFromPath(path string) URI {
	if path == "" {
		return ""
	}
	// Handle standard library paths that contain the literal "$GOROOT".
	// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
	const prefix = "$GOROOT"
	if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
		suffix := path[len(prefix):]
		path = runtime.GOROOT() + suffix
	}
	if !isWindowsDrivePath(path) {
		if abs, err := filepath.Abs(path); err == nil {
			path = abs
		}
	}
	// Check the file path again, in case it became absolute.
	if isWindowsDrivePath(path) {
		path = "/" + strings.ToUpper(string(path[0])) + path[1:]
	}
	path = filepath.ToSlash(path)
	u := url.URL{
		Scheme: fileScheme,
		Path:   path,
	}
	return URI(u.String())
}

// isWindowsDrivePath returns true if the file path is of the form used by
// Windows. We check if the path begins with a drive letter, followed by a ":".
// For example: C:/x/y/z.
func isWindowsDrivePath(path string) bool {
	if len(path) < 3 {
		return false
	}
	return unicode.IsLetter(rune(path[0])) && path[1] == ':'
}

// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
// (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:").
func isWindowsDriveURIPath(uri string) bool {
	if len(uri) < 4 {
		return false
	}
	return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
}
<|endoftext|>


<commit_before>package s3

import (
	"compress/gzip"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"github.com/packetzoom/logslammer/buffer"
	"github.com/packetzoom/logslammer/output"
)

const (
	s3FlushInterval = 5
	recvBuffer      = 100
)

func uuid() string {
	b := make([]byte, 16)
	rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

type Config struct {
	AwsKeyId    string `json:"awsKeyId"`
	AwsSecKey   string `json:"awsSecKey"`
	AwsS3Bucket string `json:"awsS3Bucket"`
	AwsS3Region string `json:"awsS3Region"`

	LocalPath       string `json:"localPath"`
	Path            string `json:"path"`
	TimeSliceFormat string `json:"path"`
	AwsS3OutputKey  string `json:"awsS3OutputKey"`
}

type FileSaver struct {
	Config   Config
	Writer   *gzip.Writer
	Filename string
}

func (fileSaver *FileSaver) writeToFile(event *buffer.Event) error {
	if fileSaver.Writer == nil {
		log.Println("Creating new S3 gzip writer")
		file, err := ioutil.TempFile(fileSaver.Config.LocalPath, "s3_output_")

		if err != nil {
			log.Printf("Error creating temporary file:", err)
		}

		fileSaver.Writer = gzip.NewWriter(file)
		fileSaver.Filename = file.Name()
	}

	log.Println("Writing data to file")
	text := *event.Text
	_, err := fileSaver.Writer.Write([]byte(text))

	if err != nil {
		log.Println("Error writing:", err)
		return err
	}

	_, err = fileSaver.Writer.Write([]byte("\n"))

	if err != nil {
		log.Println("Error writing:", err)
		return err
	}

	return nil
}

func (s3Writer *S3Writer) uploadToS3(fileSaver *FileSaver) error {
	if fileSaver.Writer == nil {
		return nil
	}

	log.Println("Upload to S3!")
	writer := fileSaver.Writer
	filename := fileSaver.Filename
	fileSaver.Writer = nil
	writer.Close()

	log.Printf("Opening file %s\n", filename)
	reader, err := os.Open(filename)

	if err != nil {
		log.Printf("Failed to open file:", err)
		return err
	}

	_, s3Error := s3Writer.S3Uploader.Upload(&s3manager.UploadInput{
		Body:            reader,
		Bucket:          aws.String(s3Writer.Config.AwsS3Bucket),
		Key:             aws.String(s3Writer.Config.AwsKeyId),
		ContentEncoding: aws.String("gzip"),
	})

	if s3Error == nil {
		os.Remove(filename)
	} else {
		log.Printf("Error uploading to S3", s3Error)
	}

	return s3Error
}

type S3Writer struct {
	Config     Config
	Sender     buffer.Sender
	S3Uploader *s3manager.Uploader
	term       chan bool
}

func init() {
	output.Register("s3", &S3Writer{
		term: make(chan bool, 1),
	})
}

func (s3Writer *S3Writer) Init(config json.RawMessage, sender buffer.Sender) error {
	var s3Config *Config
	if err := json.Unmarshal(config, &s3Config); err != nil {
		return fmt.Errorf("Error parsing S3 config: %v", err)
	}

	s3Writer.Config = *s3Config
	s3Writer.Sender = sender

	session := session.New(&aws.Config{Region: &s3Writer.Config.AwsS3Region})
	s3Writer.S3Uploader = s3manager.NewUploader(session)

	log.Println("Done instantiating uploader")

	return nil
}

func (s3Writer *S3Writer) Start() error {
	// Create file saver
	fileSaver := new(FileSaver)
	fileSaver.Config = s3Writer.Config

	id := "s3_output"
	// Add the client as a subscriber
	receiveChan := make(chan *buffer.Event, recvBuffer)
	s3Writer.Sender.AddSubscriber(id, receiveChan)
	defer s3Writer.Sender.DelSubscriber(id)

	// Loop events and publish to S3
	tick := time.NewTicker(time.Duration(s3FlushInterval) * time.Second)

	for {
		select {
		case ev := <-receiveChan:
			fileSaver.writeToFile(ev)
		case <-tick.C:
			go s3Writer.uploadToS3(fileSaver)
		case <-s3Writer.term:
			log.Println("S3Writer received term signal")
			return nil
		}
	}

	return nil
}

func (s *S3Writer) Stop() error {
	s.term <- true
	return nil
}
<commit_msg>Support templated S3 keys<commit_after>package s3

import (
	"compress/gzip"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"github.com/packetzoom/logslammer/buffer"
	"github.com/packetzoom/logslammer/output"

	"github.com/jehiah/go-strftime"
)

const (
	s3FlushInterval = 5
	recvBuffer      = 100
)

func uuid() string {
	b := make([]byte, 16)
	rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

type Config struct {
	AwsKeyId    string `json:"awsKeyId"`
	AwsSecKey   string `json:"awsSecKey"`
	AwsS3Bucket string `json:"awsS3Bucket"`
	AwsS3Region string `json:"awsS3Region"`

	LocalPath       string `json:"localPath"`
	Path            string `json:"path"`
	TimeSliceFormat string `json:"timeSliceFormat"`
	AwsS3OutputKey  string `json:"awsS3OutputKey"`
}

type FileSaver struct {
	Config   Config
	Writer   *gzip.Writer
	Filename string
}

func (fileSaver *FileSaver) writeToFile(event *buffer.Event) error {
	if fileSaver.Writer == nil {
		log.Println("Creating new S3 gzip writer")
		file, err := ioutil.TempFile(fileSaver.Config.LocalPath, "s3_output_")

		if err != nil {
			log.Printf("Error creating temporary file:", err)
		}

		fileSaver.Writer = gzip.NewWriter(file)
		fileSaver.Filename = file.Name()
	}

	log.Println("Writing data to file")
	text := *event.Text
	_, err := fileSaver.Writer.Write([]byte(text))

	if err != nil {
		log.Println("Error writing:", err)
		return err
	}

	_, err = fileSaver.Writer.Write([]byte("\n"))

	if err != nil {
		log.Println("Error writing:", err)
		return err
	}

	return nil
}

func (s3Writer *S3Writer) uploadToS3(fileSaver *FileSaver) error {
	if fileSaver.Writer == nil {
		return nil
	}

	log.Println("Upload to S3!")
	writer := fileSaver.Writer
	filename := fileSaver.Filename
	fileSaver.Writer = nil
	writer.Close()

	log.Printf("Opening file %s\n", filename)
	reader, err := os.Open(filename)

	if err != nil {
		log.Printf("Failed to open file:", err)
		return err
	}

	curTime := time.Now()
	hostname, _ := os.Hostname()
	timeKey := strftime.Format(s3Writer.Config.TimeSliceFormat, curTime)

	values_for_s3_object_key := map[string]string{
		"path":      s3Writer.Config.Path,
		"timeSlice": timeKey,
		"hostname":  hostname,
		"uuid":      uuid(),
	}

	destFile := s3Writer.Config.AwsS3OutputKey

	for key, value := range values_for_s3_object_key {
		expr := "%{" + key + "}"
		log.Printf("replace key: %s with %s", expr, value)
		destFile = strings.Replace(destFile, expr, value, -1)
	}

	result, s3Error := s3Writer.S3Uploader.Upload(&s3manager.UploadInput{
		Body:            reader,
		Bucket:          aws.String(s3Writer.Config.AwsS3Bucket),
		Key:             aws.String(destFile),
		ContentEncoding: aws.String("gzip"),
	})

	log.Printf("%s written to S3", result.Location)

	if s3Error == nil {
		os.Remove(filename)
	} else {
		log.Printf("Error uploading to S3", s3Error)
	}

	return s3Error
}

type S3Writer struct {
	Config     Config
	Sender     buffer.Sender
	S3Uploader *s3manager.Uploader
	term       chan bool
}

func init() {
	output.Register("s3", &S3Writer{
		term: make(chan bool, 1),
	})
}

func (s3Writer *S3Writer) Init(config json.RawMessage, sender buffer.Sender) error {
	var s3Config *Config
	if err := json.Unmarshal(config, &s3Config); err != nil {
		return fmt.Errorf("Error parsing S3 config: %v", err)
	}

	s3Writer.Config = *s3Config
	s3Writer.Sender = sender

	aws_access_key_id := s3Writer.Config.AwsKeyId
	aws_secret_access_key := s3Writer.Config.AwsSecKey

	token := ""
	creds := credentials.NewStaticCredentials(aws_access_key_id, aws_secret_access_key, token)
	_, err := creds.Get()

	if err != nil {
		log.Fatalf("Error with AWS credentials:", err)
	}

	session := session.New(&aws.Config{
		Region:      &s3Writer.Config.AwsS3Region,
		Credentials: creds,
	})

	s3Writer.S3Uploader = s3manager.NewUploader(session)
	log.Println("Done instantiating uploader")

	return nil
}

func (s3Writer *S3Writer) Start() error {
	// Create file saver
	fileSaver := new(FileSaver)
	fileSaver.Config = s3Writer.Config

	id := "s3_output"
	// Add the client as a subscriber
	receiveChan := make(chan *buffer.Event, recvBuffer)
	s3Writer.Sender.AddSubscriber(id, receiveChan)
	defer s3Writer.Sender.DelSubscriber(id)

	// Loop events and publish to S3
	tick := time.NewTicker(time.Duration(s3FlushInterval) * time.Second)

	for {
		select {
		case ev := <-receiveChan:
			fileSaver.writeToFile(ev)
		case <-tick.C:
			go s3Writer.uploadToS3(fileSaver)
		case <-s3Writer.term:
			log.Println("S3Writer received term signal")
			return nil
		}
	}

	return nil
}

func (s *S3Writer) Stop() error {
	s.term <- true
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"golang.org/x/crypto/openpgp"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"
)

const jaegerJSONGPGDBExtension = ".jgrdb"
const jaegerDBDescription = "JaegerDB - Jaeger database management program\n\nJaeger is a JSON encoded GPG encrypted key value store. It is useful for separating development with operations and keeping configuration files secure."
const jaegerQuote = "\"Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger!\" - Pacific Rim"
const jaegerDBRecommendedUsage = "RECOMMENDED:\n    jaegerdb -j file.txt.jgrdb -a \"Field1\" -v \"Secret value\"\n\nThis will run JaegerDB with the default options and assume the following:\n    Keyring file: ~/.gnupg/jaeger_pubring.gpg"

var debug debugging = false

type debugging bool

func (d debugging) Printf(format string, args ...interface{}) {
	// From: https://groups.google.com/forum/#!msg/golang-nuts/gU7oQGoCkmg/BNIl-TqB-4wJ
	if d {
		log.Printf(format, args...)
	}
}

type Data struct {
	Properties []Property
}

type Property struct {
	Name           string `json:"Name"`
	EncryptedValue string `json:"EncryptedValue"`
}

func main() {
	// Define flags
	// TODO: View individual property and unencrypted value. 'get'
	var (
		addKey         = flag.String("a", "", "Add property")
		changeKey      = flag.String("c", "", "Change property")
		debugFlag      = flag.Bool("d", false, "Enable Debug")
		deleteKey      = flag.String("delete", "", "Delete property")
		initializeFlag = flag.Bool("init", false, "Create an initial blank JSON GPG database file")
		jsonGPGDB      = flag.String("j", "", "JSON GPG database file. eg. file.txt.jgrdb")
		keyringFile    = flag.String("k", "", "Keyring file. Public key in ASCII armored format. eg. pubring.asc")
		value          = flag.String("v", "", "Value for property to use")
	)

	flag.Usage = func() {
		fmt.Printf("%s\n%s\n\n%s\n\n", jaegerDBDescription, jaegerQuote, jaegerDBRecommendedUsage)
		fmt.Fprintf(os.Stderr, "OPTIONS:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	if *debugFlag {
		debug = true
	}

	if *jsonGPGDB == "" {
		assumedJaegerDB, err := checkExistsJaegerDB()
		if err != nil {
			flag.Usage()
			log.Fatalf("\n\nError: %s", err)
		}
		*jsonGPGDB = assumedJaegerDB
	}

	if *initializeFlag {
		err := initializeJSONGPGDB(jsonGPGDB)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Initialized JSON GPG database and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *deleteKey != "" {
		err := deleteKeyJaegerDB(deleteKey, jsonGPGDB)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Deleted property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	var entitylist openpgp.EntityList

	if *keyringFile == "" {
		_, entitylist = processPublicKeyRing()
	} else {
		_, entitylist = processArmoredKeyRingFile(keyringFile)
	}

	if *addKey != "" {
		if *value == "" {
			flag.Usage()
			log.Fatalf("\n\nError: No value for add key operation specified")
		}
		err := addKeyJaegerDB(addKey, value, jsonGPGDB, entitylist)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Added property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *changeKey != "" {
		if *value == "" {
			flag.Usage()
			log.Fatalf("\n\nError: No value for change key operation specified")
		}
		err := changeKeyJaegerDB(changeKey, value, jsonGPGDB, entitylist)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Changed property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *deleteKey == "" && *addKey == "" && *changeKey == "" {
		log.Fatalf("\n\nError: No JSON GPG database operations specified")
	}

}

func checkExistsJaegerDB() (string, error) {
	// If no JSON GPG database file is explicitly specified, check that one JSON GPG database file is in the
	// current directory and use that file
	files, err := filepath.Glob("*.jgrdb")
	if err != nil {
		return "", err
	}
	if len(files) == 1 {
		return files[0], nil
	}
	return "", fmt.Errorf("Please specify a JSON GPG database file")
}

func initializeJSONGPGDB(jsonGPGDB *string) error {
	if _, err := os.Stat(*jsonGPGDB); err == nil {
		return fmt.Errorf("ERR: File already exists: %v", *jsonGPGDB)
	}

	var newP []Property

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func encodeBase64EncryptedMessage(s string, entitylist openpgp.EntityList) string {
	// Encrypt message using public key and then encode with base64
	debug.Printf("entitylist: #%v", entitylist)
	buf := new(bytes.Buffer)
	w, err := openpgp.Encrypt(buf, entitylist, nil, nil, nil)
	if err != nil {
		log.Fatalln("ERR: Error encrypting message - ", err)
	}

	_, err = w.Write([]byte(s))
	if err != nil {
	}
	err = w.Close()
	if err != nil {
	}

	// Output as base64 encoded string
	bytes, err := ioutil.ReadAll(buf)
	str := base64.StdEncoding.EncodeToString(bytes)

	debug.Printf("Public key encrypted message (base64 encoded): %v", str)

	return str
}

func processPublicKeyRing() (entity *openpgp.Entity, entitylist openpgp.EntityList) {
	// TODO: Handle a specified recipient
	// Get default public keyring location
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}

	jaegerPublicKeyRing := fmt.Sprintf("%v/.gnupg/jaeger_pubring.gpg", usr.HomeDir)
	publicKeyRing := ""

	if _, err := os.Stat(jaegerPublicKeyRing); err == nil {
		publicKeyRing = jaegerPublicKeyRing
	} else {
		publicKeyRing = fmt.Sprintf("%v/.gnupg/pubring.gpg", usr.HomeDir)
	}

	debug.Printf("publicKeyRing file:", publicKeyRing)
	publicKeyRingBuffer, err := os.Open(publicKeyRing)
	if err != nil {
		panic(err)
	}
	entitylist, err = openpgp.ReadKeyRing(publicKeyRingBuffer)
	if err != nil {
		log.Fatal(err)
	}

	entity = entitylist[0]
	debug.Printf("Public key default keyring:", entity.Identities)

	return entity, entitylist
}

func processArmoredKeyRingFile(keyringFile *string) (entity *openpgp.Entity, entitylist openpgp.EntityList) {
	keyringFileBuffer, err := os.Open(*keyringFile)
	if err != nil {
		log.Fatalln("ERROR: Unable to read keyring file")
	}
	entitylist, err = openpgp.ReadArmoredKeyRing(keyringFileBuffer)
	if err != nil {
		log.Fatal(err)
	}
	entity = entitylist[0]
	debug.Printf("Public key from ASCII armored string:", entity.Identities)

	return entity, entitylist
}

func addKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error {
	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	found := false

	var newP []Property

	p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)}

	// Search
	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			j.Properties[i] = p
			found = true
			break
		}
	}

	if found {
		return fmt.Errorf("\n\nError: Property '%s' already exists.", *key)
	}

	newP = append(j.Properties, p)

	debug.Printf("new properties: %v", newP)

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func changeKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error {
	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	found := false

	// New property to replace the old
	p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)}

	// Search and replace
	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			j.Properties[i] = p
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("\n\nError: Property '%s' not found.", *key)
	}

	bytes, err := json.MarshalIndent(j, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func deleteKeyJaegerDB(key *string, jsonGPGDB *string) error {
	debug.Printf("deleteKeyJaegerDB key: %v", *key)

	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	var newP []Property
	found := false

	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			// https://code.google.com/p/go-wiki/wiki/SliceTricks
			newP = j.Properties[:i+copy(j.Properties[i:], j.Properties[i+1:])]
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("\n\nError: Property '%s' not found.", *key)
	}

	debug.Printf("new properties: %v", newP)

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}
<commit_msg>Look for an existing template file when initializing a JSON GPG database file<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"golang.org/x/crypto/openpgp"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"strings"
)

const jaegerTemplateExtension = ".jgrt"
const jaegerDBExtension = ".jgrdb"
const jaegerDBDescription = "JaegerDB - Jaeger database management program\n\nJaeger is a JSON encoded GPG encrypted key value store. It is useful for separating development with operations and keeping configuration files secure."
const jaegerQuote = "\"Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger!\" - Pacific Rim"
const jaegerDBRecommendedUsage = "RECOMMENDED:\n    jaegerdb -j file.txt.jgrdb -a \"Field1\" -v \"Secret value\"\n\nThis will run JaegerDB with the default options and assume the following:\n    Keyring file: ~/.gnupg/jaeger_pubring.gpg"

var debug debugging = false

type debugging bool

func (d debugging) Printf(format string, args ...interface{}) {
	// From: https://groups.google.com/forum/#!msg/golang-nuts/gU7oQGoCkmg/BNIl-TqB-4wJ
	if d {
		log.Printf(format, args...)
	}
}

type Data struct {
	Properties []Property
}

type Property struct {
	Name           string `json:"Name"`
	EncryptedValue string `json:"EncryptedValue"`
}

func main() {
	// Define flags
	// TODO: View individual property and unencrypted value. 'get'
	var (
		addKey         = flag.String("a", "", "Add property")
		changeKey      = flag.String("c", "", "Change property")
		debugFlag      = flag.Bool("d", false, "Enable Debug")
		deleteKey      = flag.String("delete", "", "Delete property")
		initializeFlag = flag.Bool("init", false, "Create an initial blank JSON GPG database file")
		jsonGPGDB      = flag.String("j", "", "JSON GPG database file. eg. file.txt.jgrdb")
		keyringFile    = flag.String("k", "", "Keyring file. Public key in ASCII armored format. eg. pubring.asc")
		value          = flag.String("v", "", "Value for property to use")
	)

	flag.Usage = func() {
		fmt.Printf("%s\n%s\n\n%s\n\n", jaegerDBDescription, jaegerQuote, jaegerDBRecommendedUsage)
		fmt.Fprintf(os.Stderr, "OPTIONS:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	if *debugFlag {
		debug = true
	}

	if *jsonGPGDB == "" {
		assumedJaegerDB, err := checkExistsJaegerDB()
		if err != nil {
			flag.Usage()
			log.Fatalf("\n\nError: %s", err)
		}
		*jsonGPGDB = assumedJaegerDB
	}

	if *initializeFlag {
		err := initializeJSONGPGDB(jsonGPGDB)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Initialized JSON GPG database and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *deleteKey != "" {
		err := deleteKeyJaegerDB(deleteKey, jsonGPGDB)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Deleted property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	var entitylist openpgp.EntityList

	if *keyringFile == "" {
		_, entitylist = processPublicKeyRing()
	} else {
		_, entitylist = processArmoredKeyRingFile(keyringFile)
	}

	if *addKey != "" {
		if *value == "" {
			flag.Usage()
			log.Fatalf("\n\nError: No value for add key operation specified")
		}
		err := addKeyJaegerDB(addKey, value, jsonGPGDB, entitylist)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Added property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *changeKey != "" {
		if *value == "" {
			flag.Usage()
			log.Fatalf("\n\nError: No value for change key operation specified")
		}
		err := changeKeyJaegerDB(changeKey, value, jsonGPGDB, entitylist)
		if err != nil {
			log.Fatal(err)
		} else {
			fmt.Println("Changed property and wrote to file:", *jsonGPGDB)
			os.Exit(0)
		}
	}

	if *deleteKey == "" && *addKey == "" && *changeKey == "" {
		log.Fatalf("\n\nError: No JSON GPG database operations specified")
	}

}

func checkExistsJaegerT() (string, error) {
	// Check that one template file is in the current directory and use that file
	files, err := filepath.Glob("*.jgrt")
	if err != nil {
		return "", err
	}
	if len(files) == 1 {
		return files[0], nil
	}
	return "", fmt.Errorf("No input template file specified")
}

func checkExistsJaegerDB() (string, error) {
	// If no JSON GPG database file is explicitly specified, check that one JSON GPG database file is in the
	// current directory and use that file
	files, err := filepath.Glob("*.jgrdb")
	if err != nil {
		return "", err
	}
	if len(files) == 1 {
		return files[0], nil
	}

	assumedTemplate, err := checkExistsJaegerT()

	if assumedTemplate != "" {
		fmt.Println("assumedTemplate:", assumedTemplate)
		basefilename := strings.TrimSuffix(assumedTemplate, jaegerTemplateExtension)
		jsonGPGDB := fmt.Sprintf("%v%v", basefilename, jaegerDBExtension)
		return jsonGPGDB, nil
	}

	return "", fmt.Errorf("Please specify a JSON GPG database file")
}

func initializeJSONGPGDB(jsonGPGDB *string) error {
	if _, err := os.Stat(*jsonGPGDB); err == nil {
		return fmt.Errorf("ERR: File already exists: %v", *jsonGPGDB)
	}

	var newP []Property

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func encodeBase64EncryptedMessage(s string, entitylist openpgp.EntityList) string {
	// Encrypt message using public key and then encode with base64
	debug.Printf("entitylist: #%v", entitylist)
	buf := new(bytes.Buffer)
	w, err := openpgp.Encrypt(buf, entitylist, nil, nil, nil)
	if err != nil {
		log.Fatalln("ERR: Error encrypting message - ", err)
	}

	_, err = w.Write([]byte(s))
	if err != nil {
	}
	err = w.Close()
	if err != nil {
	}

	// Output as base64 encoded string
	bytes, err := ioutil.ReadAll(buf)
	str := base64.StdEncoding.EncodeToString(bytes)

	debug.Printf("Public key encrypted message (base64 encoded): %v", str)

	return str
}

func processPublicKeyRing() (entity *openpgp.Entity, entitylist openpgp.EntityList) {
	// TODO: Handle a specified recipient
	// Get default public keyring location
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}

	jaegerPublicKeyRing := fmt.Sprintf("%v/.gnupg/jaeger_pubring.gpg", usr.HomeDir)
	publicKeyRing := ""

	if _, err := os.Stat(jaegerPublicKeyRing); err == nil {
		publicKeyRing = jaegerPublicKeyRing
	} else {
		publicKeyRing = fmt.Sprintf("%v/.gnupg/pubring.gpg", usr.HomeDir)
	}

	debug.Printf("publicKeyRing file:", publicKeyRing)
	publicKeyRingBuffer, err := os.Open(publicKeyRing)
	if err != nil {
		panic(err)
	}
	entitylist, err = openpgp.ReadKeyRing(publicKeyRingBuffer)
	if err != nil {
		log.Fatal(err)
	}

	entity = entitylist[0]
	debug.Printf("Public key default keyring:", entity.Identities)

	return entity, entitylist
}

func processArmoredKeyRingFile(keyringFile *string) (entity *openpgp.Entity, entitylist openpgp.EntityList) {
	keyringFileBuffer, err := os.Open(*keyringFile)
	if err != nil {
		log.Fatalln("ERROR: Unable to read keyring file")
	}
	entitylist, err = openpgp.ReadArmoredKeyRing(keyringFileBuffer)
	if err != nil {
		log.Fatal(err)
	}
	entity = entitylist[0]
	debug.Printf("Public key from ASCII armored string:", entity.Identities)

	return entity, entitylist
}

func addKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error {
	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	found := false

	var newP []Property

	p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)}

	// Search
	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			j.Properties[i] = p
			found = true
			break
		}
	}

	if found {
		return fmt.Errorf("\n\nError: Property '%s' already exists.", *key)
	}

	newP = append(j.Properties, p)

	debug.Printf("new properties: %v", newP)

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func changeKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error {
	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	found := false

	// New property to replace the old
	p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)}

	// Search and replace
	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			j.Properties[i] = p
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("\n\nError: Property '%s' not found.", *key)
	}

	bytes, err := json.MarshalIndent(j, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}

func deleteKeyJaegerDB(key *string, jsonGPGDB *string) error {
	debug.Printf("deleteKeyJaegerDB key: %v", *key)

	// json handling
	jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB)
	if err != nil {
		return fmt.Errorf("ERROR: Unable to read JSON GPG DB file")
	}

	var j Data
	if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil {
		return fmt.Errorf("error:", err)
	}
	debug.Printf("json unmarshal: %v", j)

	var newP []Property
	found := false

	for i, _ := range j.Properties {
		property := &j.Properties[i]
		debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue)
		if property.Name == *key {
			// https://code.google.com/p/go-wiki/wiki/SliceTricks
			newP = j.Properties[:i+copy(j.Properties[i:], j.Properties[i+1:])]
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("\n\nError: Property '%s' not found.", *key)
	}

	debug.Printf("new properties: %v", newP)

	newData := Data{newP}

	bytes, err := json.MarshalIndent(newData, "", "    ")
	if err != nil {
		return fmt.Errorf("error:", err)
	}

	debug.Printf("b: %v", string(bytes))

	// Writing file
	// To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745
	if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil {
		return fmt.Errorf("error:", err)
	}

	return nil
}
<|endoftext|>