<commit_before>package asmd

import (
	"errors"
	"os"
	"strconv"
	"time"
)

func (v Variable) ToStdLogic(name string) string {
	str := name + " : std_logic"

	if v.BitWidth > 1 {
		str += "_vector (" + strconv.FormatUint(v.BitWidth-1, 10) + " downto 0)"
	}

	return str
}

func (v Variable) ToStdLogicSignal(name string) string {
	str := "signal " + v.ToStdLogic(name)
	if v.DefaultValue != "" {
		str += " := " + v.DefaultValue
	}
	str += ";"
	return str
}

//func (v Variable) ToGeneric(name string) string {}

func (m *StateMachine) VHDL(filename string) (err error) {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	// Comments
	write(file, "\n")
	write(file, "--------------------------------------------------------------------------------\n")
	write(file, "-- Module Name: ", m.Options.ModuleName, "\n")
	write(file, "-- Author:      ", m.Options.Author, "\n")
	write(file, "-- Date:        ", time.Now().Format("2 Jan 2006"), "\n")
	write(file, "--\n")
	write(file, "--------------------------------------------------------------------------------\n")
	write(file, "\n")
	write(file, "\n")

	// library and use statements
	// TODO infer the minimal set using given types
	write(file, "library IEEE;\n")
	write(file, "use IEEE.STD_LOGIC_1164.ALL;\n")
	write(file, "use IEEE.NUMERIC_STD.ALL;\n")
	write(file, "\n")

	// entity start
	write(file, "entity ", m.Options.trimmedModuleName, " is\n")

	// Entity - Generics
	if len(m.Parameters) > 0 {
		write(file, m.indent(1), "generic (\n")
		isFirst := true
		for name, properties := range m.Parameters {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, name, ": ", properties.Type, " := ", properties.DefaultValue)
			write(file, "\n")
		}
		write(file, m.indent(1), ");\n")
	}

	if len(m.Inputs) > 0 || len(m.Outputs) > 0 {
		write(file, m.indent(1), "port (\n")
		var isFirst bool

		// Entity - Inputs
		isFirst = true
		for name, properties := range m.Inputs {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, properties.ToStdLogic(name))
			write(file, "\n")
		}

		// Entity - Outputs
		// We're merely continuing the same list so don't reset isFirst.
		// TODO make this DRY with Inputs section
		for name, properties := range m.Outputs {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, properties.ToStdLogic(name))
			write(file, "\n")
		}

		write(file, m.indent(1), ");\n")
	}

	// Entity end
	write(file, "end ", m.Options.trimmedModuleName, ";\n")
	write(file, "\n")

	// architecture start
	write(file, "architecture Behavioral of ", m.Options.trimmedModuleName, " is\n")

	// Constants (?)
	// Internal Signals
	write(file, m.indent(1), "-- Register signals\n")
	for sigName, signal := range m.Registers {
		write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg, "+sigName+"_next"), "\n")
	}
	write(file, "\n")

	// Internal signals for functional units
	for unitName, unit := range m.FunctionalUnits {
		write(file, m.indent(1), "-- ", unitName, " connections\n")
		for sigName, signal := range unit.Inputs {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName), "\n")
		}
		for sigName, signal := range unit.Outputs {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName), "\n")
		}
		for sigName, signal := range unit.Registers {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg"), "\n")
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg, "+sigName+"_next"), "\n")
		}
	}
	if len(m.FunctionalUnits) > 0 {
		write(file, "\n")
	}

	write(file, m.indent(1), "-- FSM declarations\n")
	// State machine states
	write(file, m.indent(1), "type state is (")
	isFirst := true
	for stateName, _ := range m.States {
		if isFirst {
			isFirst = false
		} else {
			write(file, ", ")
		}
		write(file, stateName+"_s")
	}
	write(file, "\n")
	// State machine signals
	write(file, m.indent(1), "signal state_reg, state_next : state := ", m.Options.FirstState, ";\n")

	// architecture "begin"
	write(file, "begin\n")

	// Register process
	write(file, m.indent(1), "-- FSM state register\n")
	write(file, m.indent(1), "process(clk, rst)\n")
	write(file, m.indent(1), "begin\n")
	write(file, m.indent(2), "if (rst='1') then\n")
	write(file, m.indent(3), "state_reg <= ", m.Options.FirstState, ";\n")
	if m.Options.ClockType == "posedge" {
		write(file, m.indent(2), "elsif (clk'event and clk='1') then\n")
	} else if m.Options.ClockType == "negedge" {
		write(file, m.indent(2), "elsif (clk'event and clk='0') then\n")
	} else {
		return errors.New("Unrecognized clock type: " + m.Options.ClockType)
	}
	write(file, m.indent(3), "state_reg <= state_next;\n")
	write(file, m.indent(2), "end if;\n")
	write(file, m.indent(1), "end process;\n")
	// Next State process
	// Mealy(?) Output process
	// architecture end
	write(file, "end Behavioral;\n")
	write(file, "\n")

	return nil
}
<commit_msg>State traversal and write-out is done. Still need VHDL value literals, transition subnet cycle detection, and corrected state_reg printouts (sensitivity list, port).<commit_after>package asmd

import (
	"errors"
	"os"
	"strconv"
	"time"
)

func (v Variable) ToStdLogic(name string) string {
	str := name + " : std_logic"

	if v.BitWidth > 1 {
		str += "_vector (" + strconv.FormatUint(v.BitWidth-1, 10) + " downto 0)"
	}

	return str
}

func (v Variable) ToStdLogicSignal(name string) string {
	str := "signal " + v.ToStdLogic(name)
	if v.DefaultValue != "" {
		str += " := " + v.DefaultValue
	}
	str += ";"
	return str
}

//func (v Variable) ToGeneric(name string) string {}

func (m *StateMachine) VHDL(filename string) (err error) {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	// Comments
	write(file, "\n")
	write(file, "--------------------------------------------------------------------------------\n")
	write(file, "-- Module Name: ", m.Options.ModuleName, "\n")
	write(file, "-- Author:      ", m.Options.Author, "\n")
	write(file, "-- Date:        ", time.Now().Format("2 Jan 2006"), "\n")
	write(file, "--\n")
	write(file, "--------------------------------------------------------------------------------\n")
	write(file, "\n")
	write(file, "\n")

	// library and use statements
	// TODO infer the minimal set using given types
	write(file, "library IEEE;\n")
	write(file, "use IEEE.STD_LOGIC_1164.ALL;\n")
	write(file, "use IEEE.NUMERIC_STD.ALL;\n")
	write(file, "\n")

	// entity start
	write(file, "entity ", m.Options.trimmedModuleName, " is\n")

	// Entity - Generics
	if len(m.Parameters) > 0 {
		write(file, m.indent(1), "generic (\n")
		isFirst := true
		for name, properties := range m.Parameters {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, name, ": ", properties.Type, " := ", properties.DefaultValue)
			write(file, "\n")
		}
		write(file, m.indent(1), ");\n")
	}

	if len(m.Inputs) > 0 || len(m.Outputs) > 0 {
		write(file, m.indent(1), "port (\n")
		var isFirst bool

		// Entity - Inputs
		isFirst = true
		for name, properties := range m.Inputs {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, properties.ToStdLogic(name))
			write(file, "\n")
		}

		// Entity - Outputs
		// We're merely continuing the same list so don't reset isFirst.
		// TODO make this DRY with Inputs section
		for name, properties := range m.Outputs {
			write(file, m.indent(2))
			if isFirst {
				write(file, "  ")
				isFirst = false
			} else {
				write(file, "; ")
			}
			write(file, properties.ToStdLogic(name))
			write(file, "\n")
		}

		write(file, m.indent(1), ");\n")
	}

	// Entity end
	write(file, "end ", m.Options.trimmedModuleName, ";\n")
	write(file, "\n")

	// architecture start
	write(file, "architecture Behavioral of ", m.Options.trimmedModuleName, " is\n")

	// Constants (?)
	// Internal Signals
	write(file, m.indent(1), "-- Register signals\n")
	for sigName, signal := range m.Registers {
		write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg, "+sigName+"_next"), "\n")
	}
	write(file, "\n")

	// Internal signals for functional units
	for unitName, unit := range m.FunctionalUnits {
		write(file, m.indent(1), "-- ", unitName, " connections\n")
		for sigName, signal := range unit.Inputs {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName), "\n")
		}
		for sigName, signal := range unit.Outputs {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName), "\n")
		}
		for sigName, signal := range unit.Registers {
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg"), "\n")
			write(file, m.indent(1), signal.ToStdLogicSignal(sigName+"_reg, "+sigName+"_next"), "\n")
		}
	}
	if len(m.FunctionalUnits) > 0 {
		write(file, "\n")
	}

	write(file, m.indent(1), "-- FSM declarations\n")
	// State machine states
	write(file, m.indent(1), "type state is (")
	isFirst := true
	for stateName, _ := range m.States {
		if isFirst {
			isFirst = false
		} else {
			write(file, ", ")
		}
		write(file, stateName)
	}
	write(file, "\n")
	// State machine signals
	write(file, m.indent(1), "signal state_reg, state_next : state := ", m.Options.FirstState, ";\n")

	// architecture "begin"
	write(file, "begin\n")

	// Register process
	write(file, m.indent(1), "-- Register Process\n")
	write(file, m.indent(1), "process(clk")
	if *m.Options.AddAsyncReset {
		write(file, ", rst")
	}
	write(file, ")\n")
	write(file, m.indent(1), "begin\n")
	if *m.Options.AddAsyncReset {
		write(file, m.indent(2), "if (rst='1') then\n")
		write(file, m.indent(3), "-- async reset of registers\n")
		write(file, m.indent(3), "state_reg <= ", m.Options.FirstState, ";\n")
		for name, reg := range m.Registers {
			defVal := reg.DefaultValue
			if defVal == "0" {
				defVal = "(others => '0')"
			}
			write(file, m.indent(3), name+"_reg", " <= ", defVal, ";\n")
		}
	}
	write(file, m.indent(2))
	if *m.Options.AddAsyncReset {
		write(file, "els")
	}
	if m.Options.ClockType == "posedge" {
		write(file, "if (clk'event and clk='1') then\n")
	} else if m.Options.ClockType == "negedge" {
		write(file, "if (clk'event and clk='0') then\n")
	} else {
		return errors.New("Unrecognized clock type: " + m.Options.ClockType)
	}
	write(file, m.indent(3), "-- FSM state register\n")
	write(file, m.indent(3), "state_reg <= state_next;\n")
	write(file, m.indent(3), "-- algorithm registers\n")
	for name, _ := range m.Registers {
		write(file, m.indent(3), name+"_reg", " <= ", name+"_next", ";\n")
	}
	write(file, m.indent(2), "end if;\n")
	write(file, m.indent(1), "end process;\n")
	write(file, "\n")

	// Next State + Output + RTL Operation process
	write(file, m.indent(1), "-- Next State + Output + RTL Operation process\n")
	write(file, m.indent(1), "process(")
	// TODO add in proper sensitivity list inference instead of this "benign sledgehammer" approach
	isFirst = true
	for name, _ := range m.Inputs {
		if name == "clk" || name == "rst" {
			continue
		}
		if !isFirst {
			write(file, ", ")
		} else {
			isFirst = false
		}
		write(file, name)
	}
	for name, _ := range m.Registers {
		if !isFirst {
			write(file, ", ")
		} else {
			isFirst = false
		}
		write(file, name+"_reg", ", ", name+"_next")
	}
	write(file, ")\n")
	write(file, m.indent(1), "begin\n")
	// default register 'next's to previous value
	write(file, m.indent(2), "state_next <= state_reg;\n")
	for regName, _ := range m.Registers {
		write(file, m.indent(2), regName+"_next", " <= ", regName+"_reg", ";\n")
	}
	for outName, out := range m.Outputs {
		write(file, m.indent(2), outName, " <= ", out.DefaultValue, ";\n")
	}
	// state switch statement
	write(file, m.indent(2), "case state_reg is\n")
	for stateName, state := range m.States {
		if state.IsMealy {
			continue
		}
		write(file, m.indent(3), "case ", stateName, " =>\n")
		writeVhdlNextNetwork(file, 4, m, state.Next)
		for regName, operation := range state.Operations {
			write(file, m.indent(4), regName+"_next", " <= ", operation, ";\n")
		}
	}
	write(file, m.indent(2), "end case;\n")
	write(file, m.indent(1), "end process;\n")

	// Functional units
	for unitName, unit := range m.FunctionalUnits {
		write(file, "\n")
		write(file, m.indent(1), "-- FunctionalUnit ", unitName, " with ", strconv.Itoa(len(unit.Registers)), " registers\n")
	}

	// architecture end
	write(file, "end Behavioral;\n")
	write(file, "\n")

	return nil
}

func writeVhdlNextNetwork(file *os.File, indentLevel uint, m *StateMachine, nextThingName string) {
	if nextState, ok := m.States[nextThingName]; ok && !nextState.IsMealy {
		// base case
		// TODO validate this is valid during Parse
		write(file, m.indent(indentLevel), "state_reg <= ", nextThingName, ";\n")
	} else if nextState, ok := m.States[nextThingName]; ok && nextState.IsMealy {
		for out, action := range nextState.Operations {
			varName := out
			if _, ok := m.Registers[out]; ok {
				varName = out + "_next"
			}
			write(file, m.indent(indentLevel), varName, " <= ", action, ";\n")
		}
		writeVhdlNextNetwork(file, indentLevel, m, nextState.Next)
	} else if cond, ok := m.Conditions[nextThingName]; ok {
		// TODO support elsif
		write(file, m.indent(indentLevel), "if ", cond.Expression, " then\n")
		writeVhdlNextNetwork(file, indentLevel+1, m, cond.TrueTarget)
		write(file, m.indent(indentLevel), "else\n")
		writeVhdlNextNetwork(file, indentLevel+1, m, cond.FalseTarget)
		write(file, m.indent(indentLevel), "end if;\n")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"github.com/cyfdecyf/shadowsocks-go/src/shadowsocks"
)

func initShadowSocks() {
	if config.shadowPasswd != "" {
		if config.shadowSocks == "" {
			goto confnotcomplete
		}
		shadowsocks.InitTable(config.shadowPasswd)
	} else if config.shadowSocks != "" {
		goto confnotcomplete
	}
confnotcomplete:
	errl.Println("Missing option, shadowSocks and shadowPasswd should be both given")
}

var noShadowSocksErr = errors.New("No shadowsocks configuration")

func createShadowSocksConnection(hostFull string) (cn conn, err error) {
	if config.shadowSocks == "" {
		return zeroConn, noShadowSocksErr
	}
	c, err := shadowsocks.Dial(hostFull, config.shadowSocks)
	if err != nil {
		debug.Printf("Creating shadowsocks connection: %s %v\n", hostFull, err)
		return zeroConn, err
	}
	debug.Println("shadowsocks connection created to:", hostFull)
	return conn{c, shadowSocksConn}, nil
}
<commit_msg>Refactor shadowsocks initialization.<commit_after>package main

import (
	"errors"
	"github.com/cyfdecyf/shadowsocks-go/src/shadowsocks"
)

var hasShadowSocksServer = false

func initShadowSocks() {
	if config.shadowSocks != "" && config.shadowPasswd != "" {
		shadowsocks.InitTable(config.shadowPasswd)
		hasShadowSocksServer = true
		return
	}
	if (config.shadowSocks != "" && config.shadowPasswd == "") ||
		(config.shadowSocks == "" && config.shadowPasswd != "") {
		errl.Println("Missing option, shadowSocks and shadowPasswd should be both given")
	}
}

var noShadowSocksErr = errors.New("No shadowsocks configuration")

func createShadowSocksConnection(hostFull string) (cn conn, err error) {
	if config.shadowSocks == "" {
		return zeroConn, noShadowSocksErr
	}
	c, err := shadowsocks.Dial(hostFull, config.shadowSocks)
	if err != nil {
		debug.Printf("Creating shadowsocks connection: %s %v\n", hostFull, err)
		return zeroConn, err
	}
	debug.Println("shadowsocks connection created to:", hostFull)
	return conn{c, shadowSocksConn}, nil
}
<|endoftext|>


<commit_before>package builder

// nginx
const (
	NginxVersion           = "1.22.0"
	NginxDownloadURLPrefix = "https://nginx.org/download"
)

// pcre
const (
	PcreVersion           = "10.40"
	PcreDownloadURLPrefix = "https://github.com/PhilipHazel/pcre2/releases/download"
)

// openssl
const (
	OpenSSLVersion           = "1.1.1p"
	OpenSSLDownloadURLPrefix = "https://www.openssl.org/source"
)

// libressl
const (
	LibreSSLVersion           = "3.5.3"
	LibreSSLDownloadURLPrefix = "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL"
)

// zlib
const (
	ZlibVersion           = "1.2.12"
	ZlibDownloadURLPrefix = "https://zlib.net"
)

// openResty
const (
	OpenRestyVersion           = "1.21.4.1"
	OpenRestyDownloadURLPrefix = "https://openresty.org/download"
)

// tengine
const (
	TengineVersion           = "2.3.3"
	TengineDownloadURLPrefix = "https://tengine.taobao.org/download"
)

// component enumerations
const (
	ComponentNginx = iota
	ComponentOpenResty
	ComponentTengine
	ComponentPcre
	ComponentOpenSSL
	ComponentLibreSSL
	ComponentZlib
	ComponentMax
)
<commit_msg>bumped default openssl version to 1.1.1q.<commit_after>package builder

// nginx
const (
	NginxVersion           = "1.22.0"
	NginxDownloadURLPrefix = "https://nginx.org/download"
)

// pcre
const (
	PcreVersion           = "10.40"
	PcreDownloadURLPrefix = "https://github.com/PhilipHazel/pcre2/releases/download"
)

// openssl
const (
	OpenSSLVersion           = "1.1.1q"
	OpenSSLDownloadURLPrefix = "https://www.openssl.org/source"
)

// libressl
const (
	LibreSSLVersion           = "3.5.3"
	LibreSSLDownloadURLPrefix = "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL"
)

// zlib
const (
	ZlibVersion           = "1.2.12"
	ZlibDownloadURLPrefix = "https://zlib.net"
)

// openResty
const (
	OpenRestyVersion           = "1.21.4.1"
	OpenRestyDownloadURLPrefix = "https://openresty.org/download"
)

// tengine
const (
	TengineVersion           = "2.3.3"
	TengineDownloadURLPrefix = "https://tengine.taobao.org/download"
)

// component enumerations
const (
	ComponentNginx = iota
	ComponentOpenResty
	ComponentTengine
	ComponentPcre
	ComponentOpenSSL
	ComponentLibreSSL
	ComponentZlib
	ComponentMax
)
<|endoftext|>


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

package resources

import (
	"context"
	"fmt"
	"strconv"

	"github.com/digitalocean/godo"
	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/cloud"
	"github.com/kris-nova/kubicorn/cutil/compare"
	"github.com/kris-nova/kubicorn/cutil/defaults"
	"github.com/kris-nova/kubicorn/cutil/logger"
)

var _ cloud.Resource = &SSH{}

type SSH struct {
	Shared
	User                 string
	PublicKeyFingerprint string
	PublicKeyData        string
	PublicKeyPath        string
}

func (r *SSH) Actual(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Actual")
	newResource := &SSH{
		Shared: Shared{
			Name:    r.Name,
			CloudID: immutable.SSH.Identifier,
		},
		User: immutable.SSH.User,
	}

	if r.CloudID != "" {

		id, err := strconv.Atoi(r.CloudID)
		if err != nil {
			return nil, nil, err
		}
		ssh, _, err := Sdk.Client.Keys.GetByID(context.TODO(), id)
		if err != nil {
			return nil, nil, err
		}
		strid := strconv.Itoa(ssh.ID)
		newResource.Name = ssh.Name
		newResource.CloudID = strid
		newResource.PublicKeyData = ssh.PublicKey
		newResource.PublicKeyFingerprint = ssh.Fingerprint
	} else {
		found := false
		keys, _, err := Sdk.Client.Keys.List(context.TODO(), &godo.ListOptions{})
		if err != nil {
			return nil, nil, err
		}
		for _, key := range keys {
			if key.Fingerprint == immutable.SSH.PublicKeyFingerprint {
				found = true
				newResource.Name = key.Name
				newResource.CloudID = strconv.Itoa(key.ID)
				newResource.PublicKeyData = key.PublicKey
				newResource.PublicKeyFingerprint = key.Fingerprint
			}
		}
		if !found {
			newResource.PublicKeyPath = immutable.SSH.PublicKeyPath
			newResource.PublicKeyFingerprint = immutable.SSH.PublicKeyFingerprint
			newResource.PublicKeyData = string(immutable.SSH.PublicKeyData)
			newResource.User = immutable.SSH.User
			newResource.CloudID = immutable.SSH.Identifier
		}
	}

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) Expected(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Expected")
	newResource := &SSH{
		Shared: Shared{
			Name:    r.Name,
			CloudID: immutable.SSH.Identifier,
		},
		PublicKeyFingerprint: immutable.SSH.PublicKeyFingerprint,
		PublicKeyData:        string(immutable.SSH.PublicKeyData),
		PublicKeyPath:        immutable.SSH.PublicKeyPath,
		User:                 immutable.SSH.User,
	}
	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) Apply(actual, expected cloud.Resource, immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("droplet.Apply")
	applyResource := expected.(*SSH)
	isEqual, err := compare.IsEqual(actual.(*SSH), expected.(*SSH))
	if err != nil {
		return nil, nil, err
	}
	if isEqual && actual.(*SSH).Shared.CloudID != "" {
		return immutable, applyResource, nil
	}
	request := &godo.KeyCreateRequest{
		Name:      expected.(*SSH).Name,
		PublicKey: expected.(*SSH).PublicKeyData,
	}
	key, _, err := Sdk.Client.Keys.Create(context.TODO(), request)
	if err != nil {
		godoErr := err.(*godo.ErrorResponse)
		if godoErr.Message != "SSH Key is already in use on your account" {
			return nil, nil, err
		}
		key, _, err = Sdk.Client.Keys.GetByFingerprint(context.TODO(), expected.(*SSH).PublicKeyFingerprint)
		if err != nil {
			return nil, nil, err
		}
		logger.Info("Using existing SSH Key [%s]", actual.(*SSH).Name)
	} else {
		logger.Info("Created SSH Key [%d]", key.ID)
	}

	id := strconv.Itoa(key.ID)
	newResource := &SSH{
		Shared: Shared{
			Name:    key.Name,
			CloudID: id,
		},
		PublicKeyFingerprint: key.Fingerprint,
		PublicKeyData:        key.PublicKey,
		PublicKeyPath:        expected.(*SSH).PublicKeyPath,
		User:                 expected.(*SSH).User,
	}
	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}
func (r *SSH) Delete(actual cloud.Resource, immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Delete")
	force := false
	if force {
		deleteResource := actual.(*SSH)
		if deleteResource.CloudID == "" {
			return nil, nil, fmt.Errorf("Unable to delete ssh resource without Id [%s]", deleteResource.Name)
		}
		id, err := strconv.Atoi(immutable.SSH.Identifier)
		if err != nil {
			return nil, nil, err
		}

		_, err = Sdk.Client.Keys.DeleteByID(context.TODO(), id)
		if err != nil {
			return nil, nil, err
		}

		logger.Info("Deleted SSH Key [%d]", id)
	}
	newResource := &SSH{}
	newResource.Name = actual.(*SSH).Name
	newResource.Tags = actual.(*SSH).Tags
	newResource.User = actual.(*SSH).User
	newResource.PublicKeyPath = actual.(*SSH).PublicKeyPath

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) immutableRender(newResource cloud.Resource, inaccurateCluster *cluster.Cluster) *cluster.Cluster {
	logger.Debug("ssh.Render")
	newCluster := defaults.NewClusterDefaults(inaccurateCluster)
	newCluster.SSH.PublicKeyData = []byte(newResource.(*SSH).PublicKeyData)
	newCluster.SSH.PublicKeyFingerprint = newResource.(*SSH).PublicKeyFingerprint
	newCluster.SSH.PublicKeyPath = newResource.(*SSH).PublicKeyPath
	newCluster.SSH.Identifier = newResource.(*SSH).CloudID
	newCluster.SSH.User = newResource.(*SSH).User
	return newCluster
}
<commit_msg>Fixed a issue where the public key path was missing.<commit_after>// Copyright © 2017 The Kubicorn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package resources

import (
	"context"
	"fmt"
	"strconv"

	"github.com/digitalocean/godo"
	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/cloud"
	"github.com/kris-nova/kubicorn/cutil/compare"
	"github.com/kris-nova/kubicorn/cutil/defaults"
	"github.com/kris-nova/kubicorn/cutil/logger"
)

var _ cloud.Resource = &SSH{}

type SSH struct {
	Shared
	User                 string
	PublicKeyFingerprint string
	PublicKeyData        string
	PublicKeyPath        string
}

func (r *SSH) Actual(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Actual")
	newResource := &SSH{
		Shared: Shared{
			Name:    r.Name,
			CloudID: immutable.SSH.Identifier,
		},
		User: immutable.SSH.User,
	}

	if r.CloudID != "" {

		id, err := strconv.Atoi(r.CloudID)
		if err != nil {
			return nil, nil, err
		}
		ssh, _, err := Sdk.Client.Keys.GetByID(context.TODO(), id)
		if err != nil {
			return nil, nil, err
		}
		strid := strconv.Itoa(ssh.ID)
		newResource.Name = ssh.Name
		newResource.CloudID = strid
		newResource.PublicKeyData = ssh.PublicKey
		newResource.PublicKeyFingerprint = ssh.Fingerprint
	} else {
		found := false
		keys, _, err := Sdk.Client.Keys.List(context.TODO(), &godo.ListOptions{})
		if err != nil {
			return nil, nil, err
		}
		for _, key := range keys {
			if key.Fingerprint == immutable.SSH.PublicKeyFingerprint {
				found = true
				newResource.Name = key.Name
				newResource.CloudID = strconv.Itoa(key.ID)
				newResource.PublicKeyData = key.PublicKey
				newResource.PublicKeyFingerprint = key.Fingerprint
				newResource.PublicKeyPath = immutable.SSH.PublicKeyPath
			}
		}
		if !found {
			newResource.PublicKeyPath = immutable.SSH.PublicKeyPath
			newResource.PublicKeyFingerprint = immutable.SSH.PublicKeyFingerprint
			newResource.PublicKeyData = string(immutable.SSH.PublicKeyData)
			newResource.User = immutable.SSH.User
			newResource.CloudID = immutable.SSH.Identifier
		}
	}

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) Expected(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Expected")
	newResource := &SSH{
		Shared: Shared{
			Name:    r.Name,
			CloudID: immutable.SSH.Identifier,
		},
		PublicKeyFingerprint: immutable.SSH.PublicKeyFingerprint,
		PublicKeyData:        string(immutable.SSH.PublicKeyData),
		PublicKeyPath:        immutable.SSH.PublicKeyPath,
		User:                 immutable.SSH.User,
	}
	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) Apply(actual, expected cloud.Resource, immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("droplet.Apply")
	applyResource := expected.(*SSH)
	isEqual, err := compare.IsEqual(actual.(*SSH), expected.(*SSH))
	if err != nil {
		return nil, nil, err
	}
	if isEqual && actual.(*SSH).Shared.CloudID != "" {
		return immutable, applyResource, nil
	}
	request := &godo.KeyCreateRequest{
		Name:      expected.(*SSH).Name,
		PublicKey: expected.(*SSH).PublicKeyData,
	}
	key, _, err := Sdk.Client.Keys.Create(context.TODO(), request)
	if err != nil {
		godoErr := err.(*godo.ErrorResponse)
		if godoErr.Message != "SSH Key is already in use on your account" {
			return nil, nil, err
		}
		key, _, err = Sdk.Client.Keys.GetByFingerprint(context.TODO(), expected.(*SSH).PublicKeyFingerprint)
		if err != nil {
			return nil, nil, err
		}
		logger.Info("Using existing SSH Key [%s]", actual.(*SSH).Name)
	} else {
		logger.Info("Created SSH Key [%d]", key.ID)
	}

	id := strconv.Itoa(key.ID)
	newResource := &SSH{
		Shared: Shared{
			Name:    key.Name,
			CloudID: id,
		},
		PublicKeyFingerprint: key.Fingerprint,
		PublicKeyData:        key.PublicKey,
		PublicKeyPath:        expected.(*SSH).PublicKeyPath,
		User:                 expected.(*SSH).User,
	}
	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}
func (r *SSH) Delete(actual cloud.Resource, immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("ssh.Delete")
	force := false
	if force {
		deleteResource := actual.(*SSH)
		if deleteResource.CloudID == "" {
			return nil, nil, fmt.Errorf("Unable to delete ssh resource without Id [%s]", deleteResource.Name)
		}
		id, err := strconv.Atoi(immutable.SSH.Identifier)
		if err != nil {
			return nil, nil, err
		}

		_, err = Sdk.Client.Keys.DeleteByID(context.TODO(), id)
		if err != nil {
			return nil, nil, err
		}

		logger.Info("Deleted SSH Key [%d]", id)
	}
	newResource := &SSH{}
	newResource.Name = actual.(*SSH).Name
	newResource.Tags = actual.(*SSH).Tags
	newResource.User = actual.(*SSH).User
	newResource.PublicKeyPath = actual.(*SSH).PublicKeyPath

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}

func (r *SSH) immutableRender(newResource cloud.Resource, inaccurateCluster *cluster.Cluster) *cluster.Cluster {
	logger.Debug("ssh.Render")
	newCluster := defaults.NewClusterDefaults(inaccurateCluster)
	newCluster.SSH.PublicKeyData = []byte(newResource.(*SSH).PublicKeyData)
	newCluster.SSH.PublicKeyFingerprint = newResource.(*SSH).PublicKeyFingerprint
	newCluster.SSH.PublicKeyPath = newResource.(*SSH).PublicKeyPath
	newCluster.SSH.Identifier = newResource.(*SSH).CloudID
	newCluster.SSH.User = newResource.(*SSH).User
	return newCluster
}
<|endoftext|>


<commit_before>package gate

import (
	"github.com/golang/protobuf/proto"
	"github.com/name5566/leaf/chanrpc"
	"github.com/name5566/leaf/log"
	"github.com/name5566/leaf/network"
	"github.com/name5566/leaf/network/json"
	"github.com/name5566/leaf/network/protobuf"
	"reflect"
)

type TCPGate struct {
	Addr              string
	MaxConnNum        int
	PendingWriteNum   int
	LenMsgLen         int
	MinMsgLen         uint32
	MaxMsgLen         uint32
	LittleEndian      bool
	JSONProcessor     *json.Processor
	ProtobufProcessor *protobuf.Processor
	AgentChanRPC      *chanrpc.Server
}

func (gate *TCPGate) Run(closeSig chan bool) {
	server := new(network.TCPServer)
	server.Addr = gate.Addr
	server.MaxConnNum = gate.MaxConnNum
	server.PendingWriteNum = gate.PendingWriteNum
	server.LenMsgLen = gate.LenMsgLen
	server.MinMsgLen = gate.MinMsgLen
	server.MaxMsgLen = gate.MaxMsgLen
	server.LittleEndian = gate.LittleEndian
	server.NewAgent = func(conn *network.TCPConn) network.Agent {
		a := new(TCPAgent)
		a.conn = conn
		a.gate = gate

		if gate.AgentChanRPC != nil {
			gate.AgentChanRPC.Go("NewAgent", a)
		}

		return a
	}

	server.Start()
	<-closeSig
	server.Close()
}

func (gate *TCPGate) OnDestroy() {}

type TCPAgent struct {
	conn     *network.TCPConn
	gate     *TCPGate
	userData interface{}
}

func (a *TCPAgent) Run() {
	for {
		data, err := a.conn.ReadMsg()
		if err != nil {
			log.Debug("read message: %v", err)
			break
		}

		if a.gate.JSONProcessor != nil {
			// json
			msg, err := a.gate.JSONProcessor.Unmarshal(data)
			if err != nil {
				log.Debug("unmarshal json error: %v", err)
				break
			}
			err = a.gate.JSONProcessor.Route(msg, Agent(a))
			if err != nil {
				log.Debug("route message error: %v", err)
				break
			}
		} else if a.gate.ProtobufProcessor != nil {
			// protobuf
			msg, err := a.gate.ProtobufProcessor.Unmarshal(data)
			if err != nil {
				log.Debug("unmarshal protobuf error: %v", err)
				break
			}
			err = a.gate.ProtobufProcessor.Route(msg, Agent(a))
			if err != nil {
				log.Debug("route message error: %v", err)
				break
			}
		}
	}
}

func (a *TCPAgent) OnClose() {
	if a.gate.AgentChanRPC != nil {
		err := a.gate.AgentChanRPC.Open(0).Call0("CloseAgent", a)
		if err != nil {
			log.Error("chanrpc error: %v", err)
		}
	}
}

func (a *TCPAgent) WriteMsg(msg interface{}) {
	if a.gate.JSONProcessor != nil {
		// json
		data, err := a.gate.JSONProcessor.Marshal(msg)
		if err != nil {
			log.Error("marshal json %v error: %v", reflect.TypeOf(msg), err)
			return
		}
		a.conn.WriteMsg(data)
	} else if a.gate.ProtobufProcessor != nil {
		// protobuf
		id, data, err := a.gate.ProtobufProcessor.Marshal(msg.(proto.Message))
		if err != nil {
			log.Error("marshal protobuf %v error: %v", reflect.TypeOf(msg), err)
			return
		}
		a.conn.WriteMsg(id, data)
	}
}

func (a *TCPAgent) Close() {
	a.conn.Close()
}

func (a *TCPAgent) UserData() interface{} {
	return a.userData
}

func (a *TCPAgent) SetUserData(data interface{}) {
	a.userData = data
}
<commit_msg>Gate support Websocket<commit_after>package gate

import (
	"github.com/golang/protobuf/proto"
	"github.com/name5566/leaf/chanrpc"
	"github.com/name5566/leaf/log"
	"github.com/name5566/leaf/network"
	"github.com/name5566/leaf/network/json"
	"github.com/name5566/leaf/network/protobuf"
	"reflect"
	"time"
)

type Gate struct {
	MaxConnNum        int
	PendingWriteNum   int
	MaxMsgLen         uint32
	JSONProcessor     *json.Processor
	ProtobufProcessor *protobuf.Processor
	AgentChanRPC      *chanrpc.Server

	// websocket
	WSAddr      string
	HTTPTimeout time.Duration

	// tcp
	TCPAddr      string
	LenMsgLen    int
	LittleEndian bool
}

func (gate *Gate) Run(closeSig chan bool) {
	var wsServer *network.WSServer
	if gate.WSAddr != "" {
		wsServer = new(network.WSServer)
		wsServer.Addr = gate.WSAddr
		wsServer.MaxConnNum = gate.MaxConnNum
		wsServer.PendingWriteNum = gate.PendingWriteNum
		wsServer.MaxMsgLen = gate.MaxMsgLen
		wsServer.HTTPTimeout = gate.HTTPTimeout
		wsServer.NewAgent = func(conn *network.WSConn) network.Agent {
			a := &agent{wsConn: conn, gate: gate}
			if gate.AgentChanRPC != nil {
				gate.AgentChanRPC.Go("NewAgent", a)
			}
			return a
		}
	}

	var tcpServer *network.TCPServer
	if gate.TCPAddr != "" {
		tcpServer = new(network.TCPServer)
		tcpServer.Addr = gate.TCPAddr
		tcpServer.MaxConnNum = gate.MaxConnNum
		tcpServer.PendingWriteNum = gate.PendingWriteNum
		tcpServer.LenMsgLen = gate.LenMsgLen
		tcpServer.MaxMsgLen = gate.MaxMsgLen
		tcpServer.LittleEndian = gate.LittleEndian
		tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent {
			a := &agent{tcpConn: conn, gate: gate}
			if gate.AgentChanRPC != nil {
				gate.AgentChanRPC.Go("NewAgent", a)
			}
			return a
		}
	}

	if wsServer != nil {
		wsServer.Start()
	}
	if tcpServer != nil {
		tcpServer.Start()
	}
	<-closeSig
	if wsServer != nil {
		wsServer.Close()
	}
	if tcpServer != nil {
		tcpServer.Close()
	}
}

func (gate *Gate) OnDestroy() {}

type agent struct {
	wsConn   *network.WSConn
	tcpConn  *network.TCPConn
	gate     *Gate
	userData interface{}
}

func (a *agent) Run() {
	for {
		var (
			data []byte
			err  error
		)
		if a.wsConn != nil {
			data, err = a.wsConn.ReadMsg()
		} else if a.tcpConn != nil {
			data, err = a.tcpConn.ReadMsg()
		}
		if err != nil {
			log.Debug("read message: %v", err)
			break
		}

		if a.gate.JSONProcessor != nil {
			// json
			msg, err := a.gate.JSONProcessor.Unmarshal(data)
			if err != nil {
				log.Debug("unmarshal json error: %v", err)
				break
			}
			err = a.gate.JSONProcessor.Route(msg, a)
			if err != nil {
				log.Debug("route message error: %v", err)
				break
			}
		} else if a.gate.ProtobufProcessor != nil {
			// protobuf
			msg, err := a.gate.ProtobufProcessor.Unmarshal(data)
			if err != nil {
				log.Debug("unmarshal protobuf error: %v", err)
				break
			}
			err = a.gate.ProtobufProcessor.Route(msg, a)
			if err != nil {
				log.Debug("route message error: %v", err)
				break
			}
		}
	}
}

func (a *agent) OnClose() {
	if a.gate.AgentChanRPC != nil {
		err := a.gate.AgentChanRPC.Open(0).Call0("CloseAgent", a)
		if err != nil {
			log.Error("chanrpc error: %v", err)
		}
	}
}

func (a *agent) WriteMsg(msg interface{}) {
	if a.gate.JSONProcessor != nil {
		// json
		data, err := a.gate.JSONProcessor.Marshal(msg)
		if err != nil {
			log.Error("marshal json %v error: %v", reflect.TypeOf(msg), err)
			return
		}
		if a.wsConn != nil {
			a.wsConn.WriteMsg(data)
		} else if a.tcpConn != nil {
			a.tcpConn.WriteMsg(data)
		}
	} else if a.gate.ProtobufProcessor != nil {
		// protobuf
		id, data, err := a.gate.ProtobufProcessor.Marshal(msg.(proto.Message))
		if err != nil {
			log.Error("marshal protobuf %v error: %v", reflect.TypeOf(msg), err)
			return
		}
		if a.wsConn != nil {
			b := make([]byte, len(id)+len(data))
			copy(b, id)
			copy(b[len(id):], data)
			a.wsConn.WriteMsg(b)
		} else if a.tcpConn != nil {
			a.tcpConn.WriteMsg(id, data)
		}
	}
}

func (a *agent) Close() {
	if a.wsConn != nil {
		a.wsConn.Close()
	} else if a.tcpConn != nil {
		a.tcpConn.Close()
	}
}

func (a *agent) UserData() interface{} {
	return a.userData
}

func (a *agent) SetUserData(data interface{}) {
	a.userData = data
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"

	"encoding/json"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/batch"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/structure"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsBatchJobDefinition() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsBatchJobDefinitionCreate,
		Read:   resourceAwsBatchJobDefinitionRead,
		Delete: resourceAwsBatchJobDefinitionDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateBatchName,
			},
			"container_properties": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				StateFunc: func(v interface{}) string {
					json, _ := structure.NormalizeJsonString(v)
					return json
				},
				DiffSuppressFunc: suppressEquivalentJsonDiffs,
				ValidateFunc:     validateAwsBatchJobContainerProperties,
			},
			"parameters": {
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
				Elem:     schema.TypeString,
			},
			"retry_strategy": {
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"attempts": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(1, 10),
						},
					},
				},
			},
			"timeout": {
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"attempt_duration_seconds": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntAtLeast(60),
						},
					},
				},
			},
			"type": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringInSlice([]string{batch.JobDefinitionTypeContainer}, true),
			},
			"revision": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsBatchJobDefinitionCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	name := d.Get("name").(string)

	input := &batch.RegisterJobDefinitionInput{
		JobDefinitionName: aws.String(name),
		Type:              aws.String(d.Get("type").(string)),
	}

	if v, ok := d.GetOk("container_properties"); ok {
		props, err := expandBatchJobContainerProperties(v.(string))
		if err != nil {
			return fmt.Errorf("%s %q", err, name)
		}
		input.ContainerProperties = props
	}

	if v, ok := d.GetOk("parameters"); ok {
		input.Parameters = expandJobDefinitionParameters(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("retry_strategy"); ok {
		input.RetryStrategy = expandJobDefinitionRetryStrategy(v.([]interface{}))
	}

	if v, ok := d.GetOk("timeout"); ok {
		input.Timeout = expandJobDefinitionTimeout(v.([]interface{}))
	}

	out, err := conn.RegisterJobDefinition(input)
	if err != nil {
		return fmt.Errorf("%s %q", err, name)
	}
	d.SetId(*out.JobDefinitionArn)
	d.Set("arn", out.JobDefinitionArn)
	return resourceAwsBatchJobDefinitionRead(d, meta)
}

func resourceAwsBatchJobDefinitionRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	arn := d.Get("arn").(string)
	job, err := getJobDefinition(conn, arn)
	if err != nil {
		return fmt.Errorf("%s %q", err, arn)
	}
	if job == nil {
		d.SetId("")
		return nil
	}
	d.Set("arn", job.JobDefinitionArn)
	d.Set("container_properties", job.ContainerProperties)
	d.Set("parameters", aws.StringValueMap(job.Parameters))

	if err := d.Set("retry_strategy", flattenBatchRetryStrategy(job.RetryStrategy)); err != nil {
		return fmt.Errorf("error setting retry_strategy: %s", err)
	}

	if err := d.Set("timeout", flattenBatchJobTimeout(job.Timeout)); err != nil {
		return fmt.Errorf("error setting timeout: %s", err)
	}

	d.Set("revision", job.Revision)
	d.Set("type", job.Type)
	return nil
}

func resourceAwsBatchJobDefinitionDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	arn := d.Get("arn").(string)
	_, err := conn.DeregisterJobDefinition(&batch.DeregisterJobDefinitionInput{
		JobDefinition: aws.String(arn),
	})
	if err != nil {
		return fmt.Errorf("%s %q", err, arn)
	}
	d.SetId("")
	return nil
}

func getJobDefinition(conn *batch.Batch, arn string) (*batch.JobDefinition, error) {
	describeOpts := &batch.DescribeJobDefinitionsInput{
		JobDefinitions: []*string{aws.String(arn)},
	}
	resp, err := conn.DescribeJobDefinitions(describeOpts)
	if err != nil {
		return nil, err
	}

	numJobDefinitions := len(resp.JobDefinitions)
	switch {
	case numJobDefinitions == 0:
		return nil, nil
	case numJobDefinitions == 1:
		if *resp.JobDefinitions[0].Status == "ACTIVE" {
			return resp.JobDefinitions[0], nil
		}
		return nil, nil
	case numJobDefinitions > 1:
		return nil, fmt.Errorf("Multiple Job Definitions with name %s", arn)
	}
	return nil, nil
}

func validateAwsBatchJobContainerProperties(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)
	_, err := expandBatchJobContainerProperties(value)
	if err != nil {
		errors = append(errors, fmt.Errorf("AWS Batch Job container_properties is invalid: %s", err))
	}
	return
}

func expandBatchJobContainerProperties(rawProps string) (*batch.ContainerProperties, error) {
	var props *batch.ContainerProperties

	err := json.Unmarshal([]byte(rawProps), &props)
	if err != nil {
		return nil, fmt.Errorf("Error decoding JSON: %s", err)
	}

	return props, nil
}

func expandJobDefinitionParameters(params map[string]interface{}) map[string]*string {
	var jobParams = make(map[string]*string)
	for k, v := range params {
		jobParams[k] = aws.String(v.(string))
	}

	return jobParams
}

func expandJobDefinitionRetryStrategy(item []interface{}) *batch.RetryStrategy {
	data := item[0].(map[string]interface{})
	return &batch.RetryStrategy{
		Attempts: aws.Int64(int64(data["attempts"].(int))),
	}
}

func flattenBatchRetryStrategy(item *batch.RetryStrategy) []map[string]interface{} {
	data := []map[string]interface{}{}
	if item != nil {
		data = append(data, map[string]interface{}{
			"attempts": int(aws.Int64Value(item.Attempts)),
		})
	}
	return data
}

func expandJobDefinitionTimeout(item []interface{}) *batch.JobTimeout {
	data := item[0].(map[string]interface{})
	return &batch.JobTimeout{
		AttemptDurationSeconds: aws.Int64(int64(data["attempt_duration_seconds"].(int))),
	}
}

func flattenBatchJobTimeout(item *batch.JobTimeout) []map[string]interface{} {
	data := []map[string]interface{}{}
	if item != nil {
		data = append(data, map[string]interface{}{
			"attempt_duration_seconds": int(aws.Int64Value(item.AttemptDurationSeconds)),
		})
	}
	return data
}
<commit_msg>Add additional error checking to flatten and expand<commit_after>package aws

import (
	"fmt"

	"encoding/json"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/batch"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/structure"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsBatchJobDefinition() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsBatchJobDefinitionCreate,
		Read:   resourceAwsBatchJobDefinitionRead,
		Delete: resourceAwsBatchJobDefinitionDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateBatchName,
			},
			"container_properties": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				StateFunc: func(v interface{}) string {
					json, _ := structure.NormalizeJsonString(v)
					return json
				},
				DiffSuppressFunc: suppressEquivalentJsonDiffs,
				ValidateFunc:     validateAwsBatchJobContainerProperties,
			},
			"parameters": {
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
				Elem:     schema.TypeString,
			},
			"retry_strategy": {
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"attempts": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(1, 10),
						},
					},
				},
			},
			"timeout": {
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"attempt_duration_seconds": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntAtLeast(60),
						},
					},
				},
			},
			"type": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringInSlice([]string{batch.JobDefinitionTypeContainer}, true),
			},
			"revision": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsBatchJobDefinitionCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	name := d.Get("name").(string)

	input := &batch.RegisterJobDefinitionInput{
		JobDefinitionName: aws.String(name),
		Type:              aws.String(d.Get("type").(string)),
	}

	if v, ok := d.GetOk("container_properties"); ok {
		props, err := expandBatchJobContainerProperties(v.(string))
		if err != nil {
			return fmt.Errorf("%s %q", err, name)
		}
		input.ContainerProperties = props
	}

	if v, ok := d.GetOk("parameters"); ok {
		input.Parameters = expandJobDefinitionParameters(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("retry_strategy"); ok {
		input.RetryStrategy = expandJobDefinitionRetryStrategy(v.([]interface{}))
	}

	if v, ok := d.GetOk("timeout"); ok {
		input.Timeout = expandJobDefinitionTimeout(v.([]interface{}))
	}

	out, err := conn.RegisterJobDefinition(input)
	if err != nil {
		return fmt.Errorf("%s %q", err, name)
	}
	d.SetId(*out.JobDefinitionArn)
	d.Set("arn", out.JobDefinitionArn)
	return resourceAwsBatchJobDefinitionRead(d, meta)
}

func resourceAwsBatchJobDefinitionRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	arn := d.Get("arn").(string)
	job, err := getJobDefinition(conn, arn)
	if err != nil {
		return fmt.Errorf("%s %q", err, arn)
	}
	if job == nil {
		d.SetId("")
		return nil
	}
	d.Set("arn", job.JobDefinitionArn)
	d.Set("container_properties", job.ContainerProperties)
	d.Set("parameters", aws.StringValueMap(job.Parameters))

	if err := d.Set("retry_strategy", flattenBatchRetryStrategy(job.RetryStrategy)); err != nil {
		return fmt.Errorf("error setting retry_strategy: %s", err)
	}

	if err := d.Set("timeout", flattenBatchJobTimeout(job.Timeout)); err != nil {
		return fmt.Errorf("error setting timeout: %s", err)
	}

	d.Set("revision", job.Revision)
	d.Set("type", job.Type)
	return nil
}

func resourceAwsBatchJobDefinitionDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).batchconn
	arn := d.Get("arn").(string)
	_, err := conn.DeregisterJobDefinition(&batch.DeregisterJobDefinitionInput{
		JobDefinition: aws.String(arn),
	})
	if err != nil {
		return fmt.Errorf("%s %q", err, arn)
	}
	d.SetId("")
	return nil
}

func getJobDefinition(conn *batch.Batch, arn string) (*batch.JobDefinition, error) {
	describeOpts := &batch.DescribeJobDefinitionsInput{
		JobDefinitions: []*string{aws.String(arn)},
	}
	resp, err := conn.DescribeJobDefinitions(describeOpts)
	if err != nil {
		return nil, err
	}

	numJobDefinitions := len(resp.JobDefinitions)
	switch {
	case numJobDefinitions == 0:
		return nil, nil
	case numJobDefinitions == 1:
		if *resp.JobDefinitions[0].Status == "ACTIVE" {
			return resp.JobDefinitions[0], nil
		}
		return nil, nil
	case numJobDefinitions > 1:
		return nil, fmt.Errorf("Multiple Job Definitions with name %s", arn)
	}
	return nil, nil
}

func validateAwsBatchJobContainerProperties(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)
	_, err := expandBatchJobContainerProperties(value)
	if err != nil {
		errors = append(errors, fmt.Errorf("AWS Batch Job container_properties is invalid: %s", err))
	}
	return
}

func expandBatchJobContainerProperties(rawProps string) (*batch.ContainerProperties, error) {
	var props *batch.ContainerProperties

	err := json.Unmarshal([]byte(rawProps), &props)
	if err != nil {
		return nil, fmt.Errorf("Error decoding JSON: %s", err)
	}

	return props, nil
}

func expandJobDefinitionParameters(params map[string]interface{}) map[string]*string {
	var jobParams = make(map[string]*string)
	for k, v := range params {
		jobParams[k] = aws.String(v.(string))
	}

	return jobParams
}

func expandJobDefinitionRetryStrategy(item []interface{}) *batch.RetryStrategy {
	retryStrategy := &batch.RetryStrategy{}
	data := item[0].(map[string]interface{})

	if v, ok := data["attempts"].(int); ok && v > 0 && v <= 10 {
		retryStrategy.Attempts = aws.Int64(int64(v))
	}

	return retryStrategy
}

func flattenBatchRetryStrategy(item *batch.RetryStrategy) []map[string]interface{} {
	data := []map[string]interface{}{}
	if item != nil && item.Attempts != nil {
		data = append(data, map[string]interface{}{
			"attempts": int(aws.Int64Value(item.Attempts)),
		})
	}
	return data
}

func expandJobDefinitionTimeout(item []interface{}) *batch.JobTimeout {
	timeout := &batch.JobTimeout{}
	data := item[0].(map[string]interface{})

	if v, ok := data["attempt_duration_seconds"].(int); ok && v >= 60 {
		timeout.AttemptDurationSeconds = aws.Int64(int64(v))
	}

	return timeout
}

func flattenBatchJobTimeout(item *batch.JobTimeout) []map[string]interface{} {
	data := []map[string]interface{}{}
	if item != nil && item.AttemptDurationSeconds != nil {
		data = append(data, map[string]interface{}{
			"attempt_duration_seconds": int(aws.Int64Value(item.AttemptDurationSeconds)),
		})
	}
	return data
}
<|endoftext|>


<commit_before>package carTime

import (
	"fmt"
	"os"
	"time"

	"github.com/bullettrain-sh/bullettrain-go-core/ansi"
)

const (
	carPaint    = "black:white"
	symbolIcon  = ""
	symbolPaint = "black:white"
)

// Time Car
type Car struct {
	paint string
}

// GetPaint returns the calculated end paint string for the car.
func (c *Car) GetPaint() string {
	if c.paint = os.Getenv("BULLETTRAIN_CAR_TIME_PAINT"); c.paint == "" {
		c.paint = carPaint
	}

	return c.paint
}

func paintedSymbol() string {
	var timeSymbol string
	if timeSymbol = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_ICON"); timeSymbol == "" {
		timeSymbol = symbolIcon
	}

	var timeSymbolPaint string
	if timeSymbolPaint = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_PAINT"); timeSymbolPaint == "" {
		timeSymbolPaint = symbolPaint
	}

	return ansi.Color(timeSymbol, timeSymbolPaint)
}

// CanShow decides if this car needs to be displayed.
func (c *Car) CanShow() bool {
	s := false
	if e := os.Getenv("BULLETTRAIN_CAR_TIME_SHOW"); e == "true" {
		s = true
	}

	return s
}

// Render builds and passes the end product of a completely composed car onto
// the channel.
func (c *Car) Render(out chan<- string) {
	defer close(out)
	carPaint := ansi.ColorFunc(c.GetPaint())
	n := time.Now()

	t := n.Format("15:04:05")
	if h := os.Getenv("BULLETTRAIN_CAR_TIME_12HR"); h == "true" {
		t = n.Format("3:04:05")
	}

	out <- fmt.Sprintf("%s%s",
		paintedSymbol(),
		carPaint(fmt.Sprintf("%s", t)))
}

// GetSeparatorPaint overrides the Fg/Bg colours of the right hand side
// separator through ENV variables.
func (c *Car) GetSeparatorPaint() string {
	return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_PAINT")
}

// GetSeparatorSymbol overrides the symbol of the right hand side
// separator through ENV variables.
func (c *Car) GetSeparatorSymbol() string {
	return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_SYMBOL")
}
<commit_msg>Fix up time code from code review<commit_after>package carTime

import (
	"fmt"
	"os"
	"time"

	"github.com/bullettrain-sh/bullettrain-go-core/ansi"
)

const (
	carPaint    = "black:white"
	symbolIcon  = ""
	symbolPaint = "black:white"
)

// Time Car
type Car struct {
	paint string
}

// GetPaint returns the calculated end paint string for the car.
func (c *Car) GetPaint() string {
	if c.paint = os.Getenv("BULLETTRAIN_CAR_TIME_PAINT"); c.paint == "" {
		c.paint = carPaint
	}

	return c.paint
}

func paintedSymbol() string {
	var timeSymbol string
	if timeSymbol = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_ICON"); timeSymbol == "" {
		timeSymbol = symbolIcon
	}

	var timeSymbolPaint string
	if timeSymbolPaint = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_PAINT"); timeSymbolPaint == "" {
		timeSymbolPaint = symbolPaint
	}

	return ansi.Color(timeSymbol, timeSymbolPaint)
}

// CanShow decides if this car needs to be displayed.
func (c *Car) CanShow() bool {
	s := false
	if e := os.Getenv("BULLETTRAIN_CAR_TIME_SHOW"); e == "true" {
		s = true
	}

	return s
}

// Render builds and passes the end product of a completely composed car onto
// the channel.
func (c *Car) Render(out chan<- string) {
	defer close(out)
	carPaint := ansi.ColorFunc(c.GetPaint())
	n := time.Now()

	t := n.Format("15:04:05")
	if h := os.Getenv("BULLETTRAIN_CAR_TIME_12HR"); h == "true" {
		t = n.Format("3:04:05")
	}

	out <- fmt.Sprintf("%s%s", paintedSymbol(), carPaint(t))
}

// GetSeparatorPaint overrides the Fg/Bg colours of the right hand side
// separator through ENV variables.
func (c *Car) GetSeparatorPaint() string {
	return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_PAINT")
}

// GetSeparatorSymbol overrides the symbol of the right hand side
// separator through ENV variables.
func (c *Car) GetSeparatorSymbol() string {
	return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_SYMBOL")
}
<|endoftext|>


<commit_before>/*
RESTful API for the car share system
*/
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage/mongodb"
	"github.com/benbjohnson/clock"
	"github.com/manyminds/api2go"
	"github.com/manyminds/api2go-adapter/gingonic"

	"gopkg.in/LewisWatson/firebase-jwt-auth.v1"
	"gopkg.in/alecthomas/kingpin.v2"
	"gopkg.in/gin-gonic/gin.v1"
	"gopkg.in/mgo.v2"
)

var (
	port              = kingpin.Flag("port", "Set port to bind to").Default("31415").Envar("CARSHARE_PORT").Int()
	mgoURL            = kingpin.Flag("mgoURL", "URL to MongoDB server or seed server(s) for clusters").Default("localhost").Envar("CARSHARE_MGO_URL").URL()
	firebaseProjectID = kingpin.Flag("firebase", "Firebase project to use for authentication").Default("ridesharelogger").Envar("CARSHARE_FIREBASE_PROJECT").String()
	acao              = kingpin.Flag("cors", "Enable HTTP Access Control (CORS) for the specified URI").PlaceHolder("URI").Envar("CARSHARE_CORS_URI").String()
)

func main() {

	kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("0.3.3").Author("Lewis Watson")
	kingpin.CommandLine.Help = "API for tracking car shares"
	kingpin.Parse()

	log.Printf("connecting to mongodb server %s%s", (*mgoURL).Host, (*mgoURL).Path)
	db, err := mgo.Dial((*mgoURL).String())
	if err != nil {
		log.Fatalf("error connecting to mongodb server: %s", err)
	}

	userStorage := &mongodb.UserStorage{}
	carShareStorage := &mongodb.CarShareStorage{}
	tripStorage := &mongodb.TripStorage{}

	log.Printf("using firebase project \"%s\" for authentication", *firebaseProjectID)
	tokenVerifier, err := fireauth.New(*firebaseProjectID)
	if err != nil {
		log.Fatal(err)
	}

	r := gin.Default()
	api := api2go.NewAPIWithRouting(
		"v0",
		api2go.NewStaticResolver("/"),
		gingonic.New(r),
	)

	if *acao != "" {
		log.Printf("enabling CORS access for %s", *acao)
		api.UseMiddleware(
			func(c api2go.APIContexter, w http.ResponseWriter, r *http.Request) {
				c.Set("db", db)
				w.Header().Set("Access-Control-Allow-Origin", *acao)
				w.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")
				w.Header().Set("Access-Control-Allow-Methods", "GET,PATCH,DELETE,OPTIONS")
			},
		)
	}

	api.AddResource(
		model.User{},
		resource.UserResource{
			UserStorage: userStorage,
		},
	)
	api.AddResource(
		model.Trip{},
		resource.TripResource{
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			CarShareStorage: carShareStorage,
			Clock:           clock.New(),
		},
	)
	api.AddResource(
		model.CarShare{},
		resource.CarShareResource{
			CarShareStorage: carShareStorage,
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			TokenVerifier:   tokenVerifier,
		},
	)

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	log.Printf("listening on :%d", *port)
	log.Fatal(r.Run(fmt.Sprintf(":%d", *port)))
}
<commit_msg>Tweak logging<commit_after>/*
RESTful API for the car share system
*/
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage/mongodb"
	"github.com/benbjohnson/clock"
	"github.com/manyminds/api2go"
	"github.com/manyminds/api2go-adapter/gingonic"
	"github.com/op/go-logging"

	"gopkg.in/LewisWatson/firebase-jwt-auth.v1"
	"gopkg.in/alecthomas/kingpin.v2"
	"gopkg.in/gin-gonic/gin.v1"
	"gopkg.in/mgo.v2"
)

var (
	port              = kingpin.Flag("port", "Set port to bind to").Default("31415").Envar("CARSHARE_PORT").Int()
	mgoURL            = kingpin.Flag("mgoURL", "URL to MongoDB server or seed server(s) for clusters").Default("localhost").Envar("CARSHARE_MGO_URL").URL()
	firebaseProjectID = kingpin.Flag("firebase", "Firebase project to use for authentication").Default("ridesharelogger").Envar("CARSHARE_FIREBASE_PROJECT").String()
	acao              = kingpin.Flag("cors", "Enable HTTP Access Control (CORS) for the specified URI").PlaceHolder("URI").Envar("CARSHARE_CORS_URI").String()

	log    = logging.MustGetLogger("example")
	format = logging.MustStringFormatter(
		`%{color}%{time:2006-01-02T15:04:05.999} %{shortfunc} %{level:.4s} %{id:03x}%{color:reset} %{message}`,
	)
)

func main() {

	logging.SetBackend(logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))

	kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("0.3.3").Author("Lewis Watson")
	kingpin.CommandLine.Help = "API for tracking car shares"
	kingpin.Parse()

	log.Infof("connecting to mongodb server %s%s", (*mgoURL).Host, (*mgoURL).Path)
	db, err := mgo.Dial((*mgoURL).String())
	if err != nil {
		log.Fatalf("error connecting to mongodb server: %s", err)
	}

	userStorage := &mongodb.UserStorage{}
	carShareStorage := &mongodb.CarShareStorage{}
	tripStorage := &mongodb.TripStorage{}

	log.Infof("using firebase project \"%s\" for authentication", *firebaseProjectID)
	tokenVerifier, err := fireauth.New(*firebaseProjectID)
	if err != nil {
		log.Fatal(err)
	}

	r := gin.Default()
	api := api2go.NewAPIWithRouting(
		"v0",
		api2go.NewStaticResolver("/"),
		gingonic.New(r),
	)

	if *acao != "" {
		log.Infof("enabling CORS access for %s", *acao)
		api.UseMiddleware(
			func(c api2go.APIContexter, w http.ResponseWriter, r *http.Request) {
				c.Set("db", db)
				w.Header().Set("Access-Control-Allow-Origin", *acao)
				w.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")
				w.Header().Set("Access-Control-Allow-Methods", "GET,PATCH,DELETE,OPTIONS")
			},
		)
	}

	api.AddResource(
		model.User{},
		resource.UserResource{
			UserStorage: userStorage,
		},
	)
	api.AddResource(
		model.Trip{},
		resource.TripResource{
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			CarShareStorage: carShareStorage,
			Clock:           clock.New(),
		},
	)
	api.AddResource(
		model.CarShare{},
		resource.CarShareResource{
			CarShareStorage: carShareStorage,
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			TokenVerifier:   tokenVerifier,
		},
	)

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	log.Infof("Listening and serving HTTP on :%d", *port)
	log.Fatal(r.Run(fmt.Sprintf(":%d", *port)))
}
<|endoftext|>


<commit_before>/*
RESTful API for the car share system
*/
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage/mongodb"
	"github.com/benbjohnson/clock"
	"github.com/manyminds/api2go"
	"github.com/manyminds/api2go-adapter/gingonic"
	"github.com/op/go-logging"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	"gopkg.in/LewisWatson/firebase-jwt-auth.v1"
	"gopkg.in/alecthomas/kingpin.v2"
	"gopkg.in/gin-gonic/gin.v1"
	"gopkg.in/mgo.v2"
)

var (
	port              = kingpin.Flag("port", "Set port to bind to").Default("31415").Envar("CARSHARE_PORT").Int()
	mgoURL            = kingpin.Flag("mgoURL", "URL to MongoDB server or seed server(s) for clusters").Default("localhost").Envar("CARSHARE_MGO_URL").URL()
	firebaseProjectID = kingpin.Flag("firebase", "Firebase project to use for authentication").Default("ridesharelogger").Envar("CARSHARE_FIREBASE_PROJECT").String()
	acao              = kingpin.Flag("cors", "Enable HTTP Access Control (CORS) for the specified URI").PlaceHolder("URI").Envar("CARSHARE_CORS_URI").String()

	log    = logging.MustGetLogger("main")
	format = logging.MustStringFormatter(
		`%{color}%{time:2006-01-02T15:04:05.999} %{shortpkg} %{longfunc} %{level:.4s} %{id:03x}%{color:reset} %{message}`,
	)

	userStorage     = &mongodb.UserStorage{}
	carShareStorage = &mongodb.CarShareStorage{}
	tripStorage     = &mongodb.TripStorage{}
)

func init() {

	logging.SetBackend(logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))

	kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("0.4.0").Author("Lewis Watson")
	kingpin.CommandLine.Help = "API for tracking car shares"
	kingpin.Parse()
}

func main() {

	log.Infof("connecting to mongodb server %s%s", (*mgoURL).Host, (*mgoURL).Path)
	db, err := mgo.Dial((*mgoURL).String())
	if err != nil {
		log.Fatalf("error connecting to mongodb server: %s", err)
	}

	log.Infof("using firebase project \"%s\" for authentication", *firebaseProjectID)
	tokenVerifier, err := fireauth.New(*firebaseProjectID)
	if err != nil {
		log.Fatal(err)
	}

	r := gin.Default()
	api := api2go.NewAPIWithRouting(
		"v0",
		api2go.NewStaticResolver("/"),
		gingonic.New(r),
	)

	if *acao != "" {
		log.Infof("enabling CORS access for %s", *acao)
		api.UseMiddleware(
			func(c api2go.APIContexter, w http.ResponseWriter, r *http.Request) {
				c.Set("db", db)
				w.Header().Set("Access-Control-Allow-Origin", *acao)
				w.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")
				w.Header().Set("Access-Control-Allow-Methods", "GET,PATCH,DELETE,OPTIONS")
			},
		)
	}

	api.AddResource(
		model.User{},
		resource.UserResource{
			UserStorage: userStorage,
		},
	)
	api.AddResource(
		model.Trip{},
		resource.TripResource{
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			CarShareStorage: carShareStorage,
			Clock:           clock.New(),
		},
	)
	api.AddResource(
		model.CarShare{},
		resource.CarShareResource{
			CarShareStorage: carShareStorage,
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			TokenVerifier:   tokenVerifier,
		},
	)

	// handler for metrics
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))

	log.Infof("Listening and serving HTTP on :%d", *port)
	log.Fatal(r.Run(fmt.Sprintf(":%d", *port)))
}
<commit_msg>Tweak logging<commit_after>/*
RESTful API for the car share system
*/
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage/mongodb"
	"github.com/benbjohnson/clock"
	"github.com/manyminds/api2go"
	"github.com/manyminds/api2go-adapter/gingonic"
	"github.com/op/go-logging"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	"gopkg.in/LewisWatson/firebase-jwt-auth.v1"
	"gopkg.in/alecthomas/kingpin.v2"
	"gopkg.in/gin-gonic/gin.v1"
	"gopkg.in/mgo.v2"
)

var (
	port              = kingpin.Flag("port", "Set port to bind to").Default("31415").Envar("CARSHARE_PORT").Int()
	mgoURL            = kingpin.Flag("mgoURL", "URL to MongoDB server or seed server(s) for clusters").Default("localhost").Envar("CARSHARE_MGO_URL").URL()
	firebaseProjectID = kingpin.Flag("firebase", "Firebase project to use for authentication").Default("ridesharelogger").Envar("CARSHARE_FIREBASE_PROJECT").String()
	acao              = kingpin.Flag("cors", "Enable HTTP Access Control (CORS) for the specified URI").PlaceHolder("URI").Envar("CARSHARE_CORS_URI").String()

	log    = logging.MustGetLogger("main")
	format = logging.MustStringFormatter(
		`%{color}%{time:2006-01-02T15:04:05.999} %{level:.4s} %{id:03x}%{color:reset} %{message}`,
	)

	userStorage     = &mongodb.UserStorage{}
	carShareStorage = &mongodb.CarShareStorage{}
	tripStorage     = &mongodb.TripStorage{}
)

func init() {

	logging.SetBackend(logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))

	kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("0.4.0").Author("Lewis Watson")
	kingpin.CommandLine.Help = "API for tracking car shares"
	kingpin.Parse()
}

func main() {

	log.Infof("connecting to mongodb server %s%s", (*mgoURL).Host, (*mgoURL).Path)
	db, err := mgo.Dial((*mgoURL).String())
	if err != nil {
		log.Fatalf("error connecting to mongodb server: %s", err)
	}

	log.Infof("using firebase project \"%s\" for authentication", *firebaseProjectID)
	tokenVerifier, err := fireauth.New(*firebaseProjectID)
	if err != nil {
		log.Fatal(err)
	}

	r := gin.Default()
	api := api2go.NewAPIWithRouting(
		"v0",
		api2go.NewStaticResolver("/"),
		gingonic.New(r),
	)

	if *acao != "" {
		log.Infof("enabling CORS access for %s", *acao)
		api.UseMiddleware(
			func(c api2go.APIContexter, w http.ResponseWriter, r *http.Request) {
				c.Set("db", db)
				w.Header().Set("Access-Control-Allow-Origin", *acao)
				w.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")
				w.Header().Set("Access-Control-Allow-Methods", "GET,PATCH,DELETE,OPTIONS")
			},
		)
	}

	api.AddResource(
		model.User{},
		resource.UserResource{
			UserStorage: userStorage,
		},
	)
	api.AddResource(
		model.Trip{},
		resource.TripResource{
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			CarShareStorage: carShareStorage,
			Clock:           clock.New(),
		},
	)
	api.AddResource(
		model.CarShare{},
		resource.CarShareResource{
			CarShareStorage: carShareStorage,
			TripStorage:     tripStorage,
			UserStorage:     userStorage,
			TokenVerifier:   tokenVerifier,
		},
	)

	// handler for metrics
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))

	log.Infof("Listening and serving HTTP on :%d", *port)
	log.Fatal(r.Run(fmt.Sprintf(":%d", *port)))
}
<|endoftext|>


<commit_before>package certs

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	cache "github.com/pmylund/go-cache"
)

// StorageHandler is a standard interface to a storage backend,
// used by AuthorisationManager to read and write key values to the backend
type StorageHandler interface {
	GetKey(string) (string, error)
	SetKey(string, string, int64) error
	GetKeys(string) []string
	DeleteKey(string) bool
	DeleteScanMatch(string) bool
}

type CertificateManager struct {
	storage StorageHandler
	logger  *logrus.Entry
	cache   *cache.Cache
	secret  string
}

func NewCertificateManager(storage StorageHandler, secret string, logger *logrus.Logger) *CertificateManager {
	if logger == nil {
		logger = logrus.New()
	}

	return &CertificateManager{
		storage: storage,
		logger:  logger.WithFields(logrus.Fields{"prefix": "cert_storage"}),
		cache:   cache.New(5*time.Minute, 10*time.Minute),
		secret:  secret,
	}
}

// Extracted from: https://golang.org/src/crypto/tls/tls.go
//
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
		return key, nil
	}
	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
		switch key := key.(type) {
		case *rsa.PrivateKey, *ecdsa.PrivateKey:
			return key, nil
		default:
			return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
		}
	}

	if key, err := x509.ParseECPrivateKey(der); err == nil {
		return key, nil
	}

	return nil, errors.New("tls: failed to parse private key")
}

func isSHA256(value string) bool {
	// check if hex encoded
	if _, err := hex.DecodeString(value); err != nil {
		return false
	}

	return true
}

func HexSHA256(cert []byte) string {
	certSHA := sha256.Sum256(cert)
	return hex.EncodeToString(certSHA[:])
}

func ParsePEM(data []byte, secret string) ([]*pem.Block, error) {
	var pemBlocks []*pem.Block

	for {
		var block *pem.Block
		block, data = pem.Decode(data)

		if block == nil {
			break
		}

		if x509.IsEncryptedPEMBlock(block) {
			var err error
			block.Bytes, err = x509.DecryptPEMBlock(block, []byte(secret))

			if err != nil {
				return nil, err
			}
		}

		pemBlocks = append(pemBlocks, block)
	}

	return pemBlocks, nil
}

func publicKey(priv interface{}) interface{} {
	switch k := priv.(type) {
	case *rsa.PrivateKey:
		return &k.PublicKey
	case *ecdsa.PrivateKey:
		return &k.PublicKey
	default:
		return nil
	}
}

func ParsePEMCertificate(data []byte, secret string) (*tls.Certificate, error) {
	var cert tls.Certificate

	blocks, err := ParsePEM(data, secret)
	if err != nil {
		return nil, err
	}

	var certID string

	for _, block := range blocks {
		if block.Type == "CERTIFICATE" {
			certID = HexSHA256(block.Bytes)
			cert.Certificate = append(cert.Certificate, block.Bytes)
			continue
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			cert.PrivateKey, err = parsePrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
			continue
		}

		if block.Type == "PUBLIC KEY" {
			// Create a dummny cert just for listing purpose
			cert.Certificate = append(cert.Certificate, block.Bytes)
			cert.Leaf = &x509.Certificate{Subject: pkix.Name{CommonName: "Public Key: " + HexSHA256(block.Bytes)}}
		}
	}

	if len(cert.Certificate) == 0 {
		return nil, errors.New("Can't find CERTIFICATE block")
	}

	if cert.Leaf == nil {
		cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])

		if err != nil {
			return nil, err
		}
	}

	// Cache certificate fingerprint
	cert.Leaf.Extensions = append([]pkix.Extension{{
		Value: []byte(certID),
	}}, cert.Leaf.Extensions...)

	return &cert, nil
}

type CertificateType int

const (
	CertificatePrivate CertificateType = iota
	CertificatePublic
	CertificateAny
)

func isPrivateKeyEmpty(cert *tls.Certificate) bool {
	switch priv := cert.PrivateKey.(type) {
	default:
		if priv == nil {
			return true
		}
	}

	return false
}

func isCertCanBeListed(cert *tls.Certificate, mode CertificateType) bool {
	switch mode {
	case CertificatePrivate:
		return !isPrivateKeyEmpty(cert)
	case CertificatePublic:
		return isPrivateKeyEmpty(cert)
	case CertificateAny:
		return true
	}

	return true
}

type CertificateMeta struct {
	ID            string    `json:"id"`
	Fingerprint   string    `json:"fingerprint"`
	HasPrivateKey bool      `json:"has_private"`
	Issuer        pkix.Name `json:"issuer,omitempty"`
	Subject       pkix.Name `json:"subject,omitempty"`
	NotBefore     time.Time `json:"not_before,omitempty"`
	NotAfter      time.Time `json:"not_after,omitempty"`
	DNSNames      []string  `json:"dns_names,omitempty"`
}

func ExtractCertificateMeta(cert *tls.Certificate, certID string) *CertificateMeta {
	return &CertificateMeta{
		ID:            certID,
		Fingerprint:   string(cert.Leaf.Extensions[0].Value),
		HasPrivateKey: !isPrivateKeyEmpty(cert),
		Issuer:        cert.Leaf.Issuer,
		Subject:       cert.Leaf.Subject,
		NotBefore:     cert.Leaf.NotBefore,
		NotAfter:      cert.Leaf.NotAfter,
		DNSNames:      cert.Leaf.DNSNames,
	}
}

func (c *CertificateManager) List(certIDs []string, mode CertificateType) (out []*tls.Certificate) {
	var cert *tls.Certificate
	var rawCert []byte
	var err error

	for _, id := range certIDs {
		if cert, found := c.cache.Get(id); found {
			if isCertCanBeListed(cert.(*tls.Certificate), mode) {
				out = append(out, cert.(*tls.Certificate))
			}
			continue
		}

		if isSHA256(id) {
			var val string
			val, err = c.storage.GetKey("raw-" + id)
			if err != nil {
				c.logger.Warn("Can't retrieve certificate from Redis:", id, err)
				out = append(out, nil)
				continue
			}
			rawCert = []byte(val)
		} else {
			rawCert, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Error("Error while reading certificate from file:", id, err)
				out = append(out, nil)
				continue
			}
		}

		cert, err = ParsePEMCertificate(rawCert, c.secret)
		if err != nil {
			c.logger.Error("Error while parsing certificate: ", id, " ", err)
			out = append(out, nil)
			continue
		}

		c.cache.Set(id, cert, cache.DefaultExpiration)

		if isCertCanBeListed(cert, mode) {
			out = append(out, cert)
		}
	}

	return out
}

// Returns list of fingerprints
func (c *CertificateManager) ListPublicKeys(keyIDs []string) (out []string) {
	var rawKey []byte
	var err error

	for _, id := range keyIDs {
		if fingerprint, found := c.cache.Get("pub-" + id); found {
			out = append(out, fingerprint.(string))
			continue
		}

		if isSHA256(id) {
			var val string
			val, err = c.storage.GetKey("raw-" + id)
			if err != nil {
				c.logger.Warn("Can't retrieve public key from Redis:", id, err)
				out = append(out, "")
				continue
			}
			rawKey = []byte(val)
		} else {
			rawKey, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Error("Error while reading public key from file:", id, err)
				out = append(out, "")
				continue
			}
		}

		block, _ := pem.Decode(rawKey)
		if block == nil {
			c.logger.Error("Can't parse public key:", id)
			out = append(out, "")
			continue
		}

		fingerprint := HexSHA256(block.Bytes)
		c.cache.Set("pub-"+id, fingerprint, cache.DefaultExpiration)
		out = append(out, fingerprint)
	}

	return out
}

func (c *CertificateManager) ListAllIds(prefix string) (out []string) {
	keys := c.storage.GetKeys("raw-" + prefix + "*")

	for _, key := range keys {
		out = append(out, strings.TrimPrefix(key, "raw-"))
	}

	return out
}

func (c *CertificateManager) GetRaw(certID string) (string, error) {
	return c.storage.GetKey("raw-" + certID)
}

func (c *CertificateManager) Add(certData []byte, orgID string) (string, error) {
	var certBlocks [][]byte
	var keyPEM, keyRaw []byte
	var publicKeyPem []byte

	rest := certData

	for {
		var block *pem.Block

		block, rest = pem.Decode(rest)
		if block == nil {
			break
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			if len(keyRaw) > 0 {
				err := errors.New("Found multiple private keys")
				c.logger.Error(err)
				return "", err
			}

			keyRaw = block.Bytes
			keyPEM = pem.EncodeToMemory(block)
		} else if block.Type == "CERTIFICATE" {
			certBlocks = append(certBlocks, pem.EncodeToMemory(block))
		} else if block.Type == "PUBLIC KEY" {
			publicKeyPem = pem.EncodeToMemory(block)
		} else {
			c.logger.Info("Ingnoring PEM block with type:", block.Type)
		}
	}

	certChainPEM := bytes.Join(certBlocks, []byte("\n"))

	if len(certChainPEM) == 0 {
		if len(publicKeyPem) == 0 {
			err := errors.New("Failed to decode certificate. It should be PEM encoded.")
			c.logger.Error(err)
			return "", err
		} else {
			certChainPEM = publicKeyPem
		}
	} else if len(publicKeyPem) > 0 {
		err := errors.New("Public keys can't be combined with certificates")
		c.logger.Error(err)
		return "", err
	}

	var certID string

	// Found private key, check if it match the certificate
	if len(keyPEM) > 0 {
		cert, err := tls.X509KeyPair(certChainPEM, keyPEM)
		if err != nil {
			c.logger.Error(err)
			return "", err
		}

		// Encrypt private key and append it to the chain
		encryptedKeyPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, "ENCRYPTED PRIVATE KEY", keyRaw, []byte(c.secret), x509.PEMCipherAES256)
		if err != nil {
			c.logger.Error("Failed to encode private key", err)
			return "", err
		}

		certChainPEM = append(certChainPEM, []byte("\n")...)
		certChainPEM = append(certChainPEM, pem.EncodeToMemory(encryptedKeyPEMBlock)...)

		certID = orgID + HexSHA256(cert.Certificate[0])
	} else if len(publicKeyPem) > 0 {
		publicKey, _ := pem.Decode(publicKeyPem)
		certID = orgID + HexSHA256(publicKey.Bytes)
	} else {
		// Get first cert
		certRaw, _ := pem.Decode(certChainPEM)
		cert, err := x509.ParseCertificate(certRaw.Bytes)
		if err != nil {
			err := errors.New("Error while parsing certificate: " + err.Error())
			c.logger.Error(err)
			return "", err
		}

		certID = orgID + HexSHA256(cert.Raw)
	}

	if cert, err := c.storage.GetKey("raw-" + certID); err == nil && cert != "" {
		return "", errors.New("Certificate with " + certID + " id already exists")
	}

	if err := c.storage.SetKey("raw-"+certID, string(certChainPEM), 0); err != nil {
		c.logger.Error(err)
		return "", err
	}

	return certID, nil
}

func (c *CertificateManager) Delete(certID string) {
	c.storage.DeleteKey("raw-" + certID)
	c.cache.Delete(certID)
}

func (c *CertificateManager) CertPool(certIDs []string) *x509.CertPool {
	pool := x509.NewCertPool()

	for _, cert := range c.List(certIDs, CertificatePublic) {
		if cert != nil {
			pool.AddCert(cert.Leaf)
		}
	}

	return pool
}

func (c *CertificateManager) ValidateRequestCertificate(certIDs []string, r *http.Request) error {
	if r.TLS == nil {
		return errors.New("TLS not enabled")
	}

	if len(r.TLS.PeerCertificates) == 0 {
		return errors.New("Client TLS certificate is required")
	}

	leaf := r.TLS.PeerCertificates[0]

	certID := HexSHA256(leaf.Raw)
	for _, cert := range c.List(certIDs, CertificatePublic) {
		// Extensions[0] contains cache of certificate SHA256
		if cert == nil || string(cert.Leaf.Extensions[0].Value) == certID {
			return nil
		}
	}

	return errors.New("Certificate with SHA256 " + certID + " not allowed")
}

func (c *CertificateManager) FlushCache() {
	c.cache.Flush()
}

func (c *CertificateManager) flushStorage() {
	c.storage.DeleteScanMatch("*")
}
<commit_msg>Fix retrieval of private certificates from Hybrid env (#2391)<commit_after>package certs

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	cache "github.com/pmylund/go-cache"
)

// StorageHandler is a standard interface to a storage backend,
// used by AuthorisationManager to read and write key values to the backend
type StorageHandler interface {
	GetKey(string) (string, error)
	SetKey(string, string, int64) error
	GetKeys(string) []string
	DeleteKey(string) bool
	DeleteScanMatch(string) bool
}

type CertificateManager struct {
	storage StorageHandler
	logger  *logrus.Entry
	cache   *cache.Cache
	secret  string
}

func NewCertificateManager(storage StorageHandler, secret string, logger *logrus.Logger) *CertificateManager {
	if logger == nil {
		logger = logrus.New()
	}

	return &CertificateManager{
		storage: storage,
		logger:  logger.WithFields(logrus.Fields{"prefix": "cert_storage"}),
		cache:   cache.New(5*time.Minute, 10*time.Minute),
		secret:  secret,
	}
}

// Extracted from: https://golang.org/src/crypto/tls/tls.go
//
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
		return key, nil
	}
	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
		switch key := key.(type) {
		case *rsa.PrivateKey, *ecdsa.PrivateKey:
			return key, nil
		default:
			return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
		}
	}

	if key, err := x509.ParseECPrivateKey(der); err == nil {
		return key, nil
	}

	return nil, errors.New("tls: failed to parse private key")
}

func isSHA256(value string) bool {
	// check if hex encoded
	if _, err := hex.DecodeString(value); err != nil {
		return false
	}

	return true
}

func HexSHA256(cert []byte) string {
	certSHA := sha256.Sum256(cert)
	return hex.EncodeToString(certSHA[:])
}

func ParsePEM(data []byte, secret string) ([]*pem.Block, error) {
	var pemBlocks []*pem.Block

	for {
		var block *pem.Block
		block, data = pem.Decode(data)

		if block == nil {
			break
		}

		if x509.IsEncryptedPEMBlock(block) {
			var err error
			block.Bytes, err = x509.DecryptPEMBlock(block, []byte(secret))
			block.Headers = nil
			block.Type = strings.Replace(block.Type, "ENCRYPTED ", "", 1)

			if err != nil {
				return nil, err
			}
		}

		pemBlocks = append(pemBlocks, block)
	}

	return pemBlocks, nil
}

func publicKey(priv interface{}) interface{} {
	switch k := priv.(type) {
	case *rsa.PrivateKey:
		return &k.PublicKey
	case *ecdsa.PrivateKey:
		return &k.PublicKey
	default:
		return nil
	}
}

func ParsePEMCertificate(data []byte, secret string) (*tls.Certificate, error) {
	var cert tls.Certificate

	blocks, err := ParsePEM(data, secret)
	if err != nil {
		return nil, err
	}

	var certID string

	for _, block := range blocks {
		if block.Type == "CERTIFICATE" {
			certID = HexSHA256(block.Bytes)
			cert.Certificate = append(cert.Certificate, block.Bytes)
			continue
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			cert.PrivateKey, err = parsePrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
			continue
		}

		if block.Type == "PUBLIC KEY" {
			// Create a dummny cert just for listing purpose
			cert.Certificate = append(cert.Certificate, block.Bytes)
			cert.Leaf = &x509.Certificate{Subject: pkix.Name{CommonName: "Public Key: " + HexSHA256(block.Bytes)}}
		}
	}

	if len(cert.Certificate) == 0 {
		return nil, errors.New("Can't find CERTIFICATE block")
	}

	if cert.Leaf == nil {
		cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])

		if err != nil {
			return nil, err
		}
	}

	// Cache certificate fingerprint
	cert.Leaf.Extensions = append([]pkix.Extension{{
		Value: []byte(certID),
	}}, cert.Leaf.Extensions...)

	return &cert, nil
}

type CertificateType int

const (
	CertificatePrivate CertificateType = iota
	CertificatePublic
	CertificateAny
)

func isPrivateKeyEmpty(cert *tls.Certificate) bool {
	switch priv := cert.PrivateKey.(type) {
	default:
		if priv == nil {
			return true
		}
	}

	return false
}

func isCertCanBeListed(cert *tls.Certificate, mode CertificateType) bool {
	switch mode {
	case CertificatePrivate:
		return !isPrivateKeyEmpty(cert)
	case CertificatePublic:
		return isPrivateKeyEmpty(cert)
	case CertificateAny:
		return true
	}

	return true
}

type CertificateMeta struct {
	ID            string    `json:"id"`
	Fingerprint   string    `json:"fingerprint"`
	HasPrivateKey bool      `json:"has_private"`
	Issuer        pkix.Name `json:"issuer,omitempty"`
	Subject       pkix.Name `json:"subject,omitempty"`
	NotBefore     time.Time `json:"not_before,omitempty"`
	NotAfter      time.Time `json:"not_after,omitempty"`
	DNSNames      []string  `json:"dns_names,omitempty"`
}

func ExtractCertificateMeta(cert *tls.Certificate, certID string) *CertificateMeta {
	return &CertificateMeta{
		ID:            certID,
		Fingerprint:   string(cert.Leaf.Extensions[0].Value),
		HasPrivateKey: !isPrivateKeyEmpty(cert),
		Issuer:        cert.Leaf.Issuer,
		Subject:       cert.Leaf.Subject,
		NotBefore:     cert.Leaf.NotBefore,
		NotAfter:      cert.Leaf.NotAfter,
		DNSNames:      cert.Leaf.DNSNames,
	}
}

func (c *CertificateManager) List(certIDs []string, mode CertificateType) (out []*tls.Certificate) {
	var cert *tls.Certificate
	var rawCert []byte
	var err error

	for _, id := range certIDs {
		if cert, found := c.cache.Get(id); found {
			if isCertCanBeListed(cert.(*tls.Certificate), mode) {
				out = append(out, cert.(*tls.Certificate))
			}
			continue
		}

		if isSHA256(id) {
			var val string
			val, err = c.storage.GetKey("raw-" + id)
			if err != nil {
				c.logger.Warn("Can't retrieve certificate from Redis:", id, err)
				out = append(out, nil)
				continue
			}
			rawCert = []byte(val)
		} else {
			rawCert, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Error("Error while reading certificate from file:", id, err)
				out = append(out, nil)
				continue
			}
		}

		cert, err = ParsePEMCertificate(rawCert, c.secret)
		if err != nil {
			c.logger.Error("Error while parsing certificate: ", id, " ", err)
			c.logger.Debug("Failed certificate: ", string(rawCert))
			out = append(out, nil)
			continue
		}

		c.cache.Set(id, cert, cache.DefaultExpiration)

		if isCertCanBeListed(cert, mode) {
			out = append(out, cert)
		}
	}

	return out
}

// Returns list of fingerprints
func (c *CertificateManager) ListPublicKeys(keyIDs []string) (out []string) {
	var rawKey []byte
	var err error

	for _, id := range keyIDs {
		if fingerprint, found := c.cache.Get("pub-" + id); found {
			out = append(out, fingerprint.(string))
			continue
		}

		if isSHA256(id) {
			var val string
			val, err = c.storage.GetKey("raw-" + id)
			if err != nil {
				c.logger.Warn("Can't retrieve public key from Redis:", id, err)
				out = append(out, "")
				continue
			}
			rawKey = []byte(val)
		} else {
			rawKey, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Error("Error while reading public key from file:", id, err)
				out = append(out, "")
				continue
			}
		}

		block, _ := pem.Decode(rawKey)
		if block == nil {
			c.logger.Error("Can't parse public key:", id)
			out = append(out, "")
			continue
		}

		fingerprint := HexSHA256(block.Bytes)
		c.cache.Set("pub-"+id, fingerprint, cache.DefaultExpiration)
		out = append(out, fingerprint)
	}

	return out
}

func (c *CertificateManager) ListAllIds(prefix string) (out []string) {
	keys := c.storage.GetKeys("raw-" + prefix + "*")

	for _, key := range keys {
		out = append(out, strings.TrimPrefix(key, "raw-"))
	}

	return out
}

func (c *CertificateManager) GetRaw(certID string) (string, error) {
	return c.storage.GetKey("raw-" + certID)
}

func (c *CertificateManager) Add(certData []byte, orgID string) (string, error) {
	var certBlocks [][]byte
	var keyPEM, keyRaw []byte
	var publicKeyPem []byte

	rest := certData

	for {
		var block *pem.Block

		block, rest = pem.Decode(rest)
		if block == nil {
			break
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			if len(keyRaw) > 0 {
				err := errors.New("Found multiple private keys")
				c.logger.Error(err)
				return "", err
			}

			keyRaw = block.Bytes
			keyPEM = pem.EncodeToMemory(block)
		} else if block.Type == "CERTIFICATE" {
			certBlocks = append(certBlocks, pem.EncodeToMemory(block))
		} else if block.Type == "PUBLIC KEY" {
			publicKeyPem = pem.EncodeToMemory(block)
		} else {
			c.logger.Info("Ingnoring PEM block with type:", block.Type)
		}
	}

	certChainPEM := bytes.Join(certBlocks, []byte("\n"))

	if len(certChainPEM) == 0 {
		if len(publicKeyPem) == 0 {
			err := errors.New("Failed to decode certificate. It should be PEM encoded.")
			c.logger.Error(err)
			return "", err
		} else {
			certChainPEM = publicKeyPem
		}
	} else if len(publicKeyPem) > 0 {
		err := errors.New("Public keys can't be combined with certificates")
		c.logger.Error(err)
		return "", err
	}

	var certID string

	// Found private key, check if it match the certificate
	if len(keyPEM) > 0 {
		cert, err := tls.X509KeyPair(certChainPEM, keyPEM)
		if err != nil {
			c.logger.Error(err)
			return "", err
		}

		// Encrypt private key and append it to the chain
		encryptedKeyPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, "ENCRYPTED PRIVATE KEY", keyRaw, []byte(c.secret), x509.PEMCipherAES256)
		if err != nil {
			c.logger.Error("Failed to encode private key", err)
			return "", err
		}

		certChainPEM = append(certChainPEM, []byte("\n")...)
		certChainPEM = append(certChainPEM, pem.EncodeToMemory(encryptedKeyPEMBlock)...)

		certID = orgID + HexSHA256(cert.Certificate[0])
	} else if len(publicKeyPem) > 0 {
		publicKey, _ := pem.Decode(publicKeyPem)
		certID = orgID + HexSHA256(publicKey.Bytes)
	} else {
		// Get first cert
		certRaw, _ := pem.Decode(certChainPEM)
		cert, err := x509.ParseCertificate(certRaw.Bytes)
		if err != nil {
			err := errors.New("Error while parsing certificate: " + err.Error())
			c.logger.Error(err)
			return "", err
		}

		certID = orgID + HexSHA256(cert.Raw)
	}

	if cert, err := c.storage.GetKey("raw-" + certID); err == nil && cert != "" {
		return "", errors.New("Certificate with " + certID + " id already exists")
	}

	if err := c.storage.SetKey("raw-"+certID, string(certChainPEM), 0); err != nil {
		c.logger.Error(err)
		return "", err
	}

	return certID, nil
}

func (c *CertificateManager) Delete(certID string) {
	c.storage.DeleteKey("raw-" + certID)
	c.cache.Delete(certID)
}

func (c *CertificateManager) CertPool(certIDs []string) *x509.CertPool {
	pool := x509.NewCertPool()

	for _, cert := range c.List(certIDs, CertificatePublic) {
		if cert != nil {
			pool.AddCert(cert.Leaf)
		}
	}

	return pool
}

func (c *CertificateManager) ValidateRequestCertificate(certIDs []string, r *http.Request) error {
	if r.TLS == nil {
		return errors.New("TLS not enabled")
	}

	if len(r.TLS.PeerCertificates) == 0 {
		return errors.New("Client TLS certificate is required")
	}

	leaf := r.TLS.PeerCertificates[0]

	certID := HexSHA256(leaf.Raw)
	for _, cert := range c.List(certIDs, CertificatePublic) {
		// Extensions[0] contains cache of certificate SHA256
		if cert == nil || string(cert.Leaf.Extensions[0].Value) == certID {
			return nil
		}
	}

	return errors.New("Certificate with SHA256 " + certID + " not allowed")
}

func (c *CertificateManager) FlushCache() {
	c.cache.Flush()
}

func (c *CertificateManager) flushStorage() {
	c.storage.DeleteScanMatch("*")
}
<|endoftext|>


<commit_before>package chunk

import (
	"errors"
	"sync"

	. "github.com/claudetech/loggo/default"
)

// ErrTimeout is a timeout error
var ErrTimeout = errors.New("timeout")

// Storage is a chunk storage
type Storage struct {
	ChunkSize int64
	MaxChunks int
	chunks    map[string][]byte
	stack     *Stack
	lock      sync.Mutex
}

// Item represents a chunk in RAM
type Item struct {
	id    string
	bytes []byte
}

// NewStorage creates a new storage
func NewStorage(chunkSize int64, maxChunks int) *Storage {
	storage := Storage{
		ChunkSize: chunkSize,
		MaxChunks: maxChunks,
		chunks:    make(map[string][]byte),
		stack:     NewStack(maxChunks),
	}

	return &storage
}

// Clear removes all old chunks on disk (will be called on each program start)
func (s *Storage) Clear() error {
	return nil
}

// Load a chunk from ram or creates it
func (s *Storage) Load(id string) []byte {
	s.lock.Lock()
	if chunk, exists := s.chunks[id]; exists {
		s.stack.Touch(id)
		s.lock.Unlock()
		return chunk
	}
	s.lock.Unlock()
	return nil
}

// Store stores a chunk in the RAM and adds it to the disk storage queue
func (s *Storage) Store(id string, bytes []byte) error {
	s.lock.Lock()

	if _, exists := s.chunks[id]; exists {
		s.stack.Touch(id)
		s.lock.Unlock()
		return nil
	}

	deleteID := s.stack.Pop()
	if "" != deleteID {
		delete(s.chunks, deleteID)

		Log.Debugf("Deleted chunk %v", deleteID)
	}

	s.chunks[id] = bytes
	s.stack.Push(id)
	s.lock.Unlock()

	return nil
}
<commit_msg>Allow parrallel reads from storage using rw lock<commit_after>package chunk

import (
	"errors"
	"sync"

	. "github.com/claudetech/loggo/default"
)

// ErrTimeout is a timeout error
var ErrTimeout = errors.New("timeout")

// Storage is a chunk storage
type Storage struct {
	ChunkSize int64
	MaxChunks int
	chunks    map[string][]byte
	stack     *Stack
	lock      sync.RWMutex
}

// Item represents a chunk in RAM
type Item struct {
	id    string
	bytes []byte
}

// NewStorage creates a new storage
func NewStorage(chunkSize int64, maxChunks int) *Storage {
	storage := Storage{
		ChunkSize: chunkSize,
		MaxChunks: maxChunks,
		chunks:    make(map[string][]byte),
		stack:     NewStack(maxChunks),
	}

	return &storage
}

// Clear removes all old chunks on disk (will be called on each program start)
func (s *Storage) Clear() error {
	return nil
}

// Load a chunk from ram or creates it
func (s *Storage) Load(id string) []byte {
	s.lock.RLock()
	if chunk, exists := s.chunks[id]; exists {
		s.stack.Touch(id)
		s.lock.RUnlock()
		return chunk
	}
	s.lock.RUnlock()
	return nil
}

// Store stores a chunk in the RAM and adds it to the disk storage queue
func (s *Storage) Store(id string, bytes []byte) error {
	s.lock.RLock()

	if _, exists := s.chunks[id]; exists {
		s.stack.Touch(id)
		s.lock.RUnlock()
		return nil
	}

	s.lock.RUnlock()
	s.lock.Lock()

	deleteID := s.stack.Pop()
	if "" != deleteID {
		delete(s.chunks, deleteID)

		Log.Debugf("Deleted chunk %v", deleteID)
	}

	s.chunks[id] = bytes
	s.stack.Push(id)
	s.lock.Unlock()

	return nil
}
<|endoftext|>