<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package plugins

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"sigs.k8s.io/kustomize/v3/pkg/ifc"
	"sigs.k8s.io/kustomize/v3/pkg/resid"
	"sigs.k8s.io/kustomize/v3/pkg/resmap"
	"sigs.k8s.io/yaml"
)

const (
	idAnnotation = "kustomize.config.k8s.io/id"
)

// ExecPlugin record the name and args of an executable
// It triggers the executable generator and transformer
type ExecPlugin struct {
	// absolute path of the executable
	path string

	// Optional command line arguments to the executable
	// pulled from specially named fields in cfg.
	// This is for executables that don't want to parse YAML.
	args []string

	// Plugin configuration data.
	cfg []byte

	// resmap Factory to make resources
	rf *resmap.Factory

	// loader to load files
	ldr ifc.Loader
}

func NewExecPlugin(p string) *ExecPlugin {
	return &ExecPlugin{path: p}
}

// isAvailable checks to see if the plugin is available
func (p *ExecPlugin) isAvailable() bool {
	f, err := os.Stat(p.path)
	if os.IsNotExist(err) {
		return false
	}
	return f.Mode()&0111 != 0000
}

func (p *ExecPlugin) Config(
	ldr ifc.Loader, rf *resmap.Factory, config []byte) error {
	p.rf = rf
	p.ldr = ldr
	p.cfg = config
	return p.processOptionalArgsFields()
}

type argsConfig struct {
	ArgsOneLiner string `json:"argsOneLiner,omitempty" yaml:"argsOneLiner,omitempty"`
	ArgsFromFile string `json:"argsFromFile,omitempty" yaml:"argsFromFile,omitempty"`
}

func (p *ExecPlugin) processOptionalArgsFields() error {
	var c argsConfig
	yaml.Unmarshal(p.cfg, &c)
	if c.ArgsOneLiner != "" {
		p.args = strings.Split(c.ArgsOneLiner, " ")
	}
	if c.ArgsFromFile != "" {
		content, err := p.ldr.Load(c.ArgsFromFile)
		if err != nil {
			return err
		}
		for _, x := range strings.Split(string(content), "\n") {
			x := strings.TrimLeft(x, " ")
			if x != "" {
				p.args = append(p.args, x)
			}
		}
	}
	return nil
}

func (p *ExecPlugin) writeConfig() (string, error) {
	tmpFile, err := ioutil.TempFile("", "kust-pipe")
	if err != nil {
		return "", err
	}

	stdout, err := os.OpenFile(tmpFile.Name(), os.O_RDWR, 0600)
	if err != nil {
		return "", err
	}
	_, err = stdout.Write(p.cfg)
	if err != nil {
		return "", err
	}
	err = stdout.Close()
	if err != nil {
		return "", err
	}
	return tmpFile.Name(), nil
}

func (p *ExecPlugin) Generate() (resmap.ResMap, error) {
	output, err := p.invokePlugin(nil)
	if err != nil {
		return nil, err
	}
	return p.rf.NewResMapFromBytes(output)
}

func (p *ExecPlugin) Transform(rm resmap.ResMap) error {
	// add ResIds as annotations to all objects so that we can add them back
	inputRM, err := p.getResMapWithIdAnnotation(rm)
	if err != nil {
		return err
	}

	// encode the ResMap so it can be fed to the plugin
	resources, err := inputRM.AsYaml()
	if err != nil {
		return err
	}

	// invoke the plugin with resources as the input
	output, err := p.invokePlugin(resources)
	if err != nil {
		return fmt.Errorf("%v %s", err, string(output))
	}

	// update the original ResMap based on the output
	return p.updateResMapValues(output, rm)
}

// invokePlugin invokes the plugin
func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) {
	args, err := p.getArgs()
	if err != nil {
		return nil, err
	}
	cmd := exec.Command(p.path, args...)
	cmd.Env = p.getEnv()
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stderr = os.Stderr
	if _, err := os.Stat(p.ldr.Root()); err == nil {
		cmd.Dir = p.ldr.Root()
	}
	return cmd.Output()
}

// The first arg is always the absolute path to a temporary file
// holding the YAML form of the plugin config.
func (p *ExecPlugin) getArgs() ([]string, error) {
	configFileName, err := p.writeConfig()
	if err != nil {
		return nil, err
	}
	return append([]string{configFileName}, p.args...), nil
}

func (p *ExecPlugin) getEnv() []string {
	env := os.Environ()
	env = append(env,
		"KUSTOMIZE_PLUGIN_CONFIG_STRING="+string(p.cfg),
		"KUSTOMIZE_PLUGIN_CONFIG_ROOT="+p.ldr.Root())
	return env
}

// Returns a new copy of the given ResMap with the ResIds annotated in each Resource
func (p *ExecPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) {
	inputRM := rm.DeepCopy()
	for _, r := range inputRM.Resources() {
		idString, err := yaml.Marshal(r.CurId())
		if err != nil {
			return nil, err
		}
		annotations := r.GetAnnotations()
		if annotations == nil {
			annotations = make(map[string]string)
		}
		annotations[idAnnotation] = string(idString)
		r.SetAnnotations(annotations)
	}
	return inputRM, nil
}

/*
updateResMapValues updates the Resource value in the given ResMap
with the emitted Resource values in output.
*/
func (p *ExecPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error {
	outputRM, err := p.rf.NewResMapFromBytes(output)
	if err != nil {
		return err
	}
	for _, r := range outputRM.Resources() {
		// for each emitted Resource, find the matching Resource in the original ResMap
		// using its id
		annotations := r.GetAnnotations()
		idString, ok := annotations[idAnnotation]
		if !ok {
			return fmt.Errorf("the transformer %s should not remove annotation %s",
				p.path, idAnnotation)
		}
		id := resid.ResId{}
		err := yaml.Unmarshal([]byte(idString), &id)
		if err != nil {
			return err
		}
		res, err := rm.GetByCurrentId(id)
		if err != nil {
			return fmt.Errorf("unable to find unique match to %s", id.String())
		}
		// remove the annotation set by Kustomize to track the resource
		delete(annotations, idAnnotation)
		if len(annotations) == 0 {
			annotations = nil
		}
		r.SetAnnotations(annotations)

		// update the ResMap resource value with the transformed object
		res.Kunstructured = r.Kunstructured
	}
	return nil
}
<commit_msg>fix: tempfile(?)<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package plugins

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"sigs.k8s.io/kustomize/v3/pkg/ifc"
	"sigs.k8s.io/kustomize/v3/pkg/resid"
	"sigs.k8s.io/kustomize/v3/pkg/resmap"
	"sigs.k8s.io/yaml"
)

const (
	idAnnotation = "kustomize.config.k8s.io/id"
)

// ExecPlugin record the name and args of an executable
// It triggers the executable generator and transformer
type ExecPlugin struct {
	// absolute path of the executable
	path string

	// Optional command line arguments to the executable
	// pulled from specially named fields in cfg.
	// This is for executables that don't want to parse YAML.
	args []string

	// Plugin configuration data.
	cfg []byte

	// resmap Factory to make resources
	rf *resmap.Factory

	// loader to load files
	ldr ifc.Loader
}

func NewExecPlugin(p string) *ExecPlugin {
	return &ExecPlugin{path: p}
}

// isAvailable checks to see if the plugin is available
func (p *ExecPlugin) isAvailable() bool {
	f, err := os.Stat(p.path)
	if os.IsNotExist(err) {
		return false
	}
	return f.Mode()&0111 != 0000
}

func (p *ExecPlugin) Config(
	ldr ifc.Loader, rf *resmap.Factory, config []byte) error {
	p.rf = rf
	p.ldr = ldr
	p.cfg = config
	return p.processOptionalArgsFields()
}

type argsConfig struct {
	ArgsOneLiner string `json:"argsOneLiner,omitempty" yaml:"argsOneLiner,omitempty"`
	ArgsFromFile string `json:"argsFromFile,omitempty" yaml:"argsFromFile,omitempty"`
}

func (p *ExecPlugin) processOptionalArgsFields() error {
	var c argsConfig
	yaml.Unmarshal(p.cfg, &c)
	if c.ArgsOneLiner != "" {
		p.args = strings.Split(c.ArgsOneLiner, " ")
	}
	if c.ArgsFromFile != "" {
		content, err := p.ldr.Load(c.ArgsFromFile)
		if err != nil {
			return err
		}
		for _, x := range strings.Split(string(content), "\n") {
			x := strings.TrimLeft(x, " ")
			if x != "" {
				p.args = append(p.args, x)
			}
		}
	}
	return nil
}

func (p *ExecPlugin) writeConfig() (string, error) {
	tmpFile, err := ioutil.TempFile("", "kust-pipe")
	if err != nil {
		return "", err
	}

	stdout, err := os.OpenFile(tmpFile.Name(), os.O_RDWR, 0600)
	if err != nil {
		return "", err
	}
	_, err = stdout.Write(p.cfg)
	if err != nil {
		return "", err
	}
	err = stdout.Close()
	if err != nil {
		return "", err
	}
	return tmpFile.Name(), nil
}

func (p *ExecPlugin) Generate() (resmap.ResMap, error) {
	output, err := p.invokePlugin(nil)
	if err != nil {
		return nil, err
	}
	return p.rf.NewResMapFromBytes(output)
}

func (p *ExecPlugin) Transform(rm resmap.ResMap) error {
	// add ResIds as annotations to all objects so that we can add them back
	inputRM, err := p.getResMapWithIdAnnotation(rm)
	if err != nil {
		return err
	}

	// encode the ResMap so it can be fed to the plugin
	resources, err := inputRM.AsYaml()
	if err != nil {
		return err
	}

	// invoke the plugin with resources as the input
	output, err := p.invokePlugin(resources)
	if err != nil {
		return fmt.Errorf("%v %s", err, string(output))
	}

	// update the original ResMap based on the output
	return p.updateResMapValues(output, rm)
}

// invokePlugin invokes the plugin
func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) {
	args, err := p.getArgs()
	if err != nil {
		return nil, err
	}
	// Cleanup plugin config file after execution
	defer os.Remove(args[0])

	cmd := exec.Command(p.path, args...)
	cmd.Env = p.getEnv()
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stderr = os.Stderr
	if _, err := os.Stat(p.ldr.Root()); err == nil {
		cmd.Dir = p.ldr.Root()
	}
	return cmd.Output()
}

// The first arg is always the absolute path to a temporary file
// holding the YAML form of the plugin config.
func (p *ExecPlugin) getArgs() ([]string, error) {
	configFileName, err := p.writeConfig()
	if err != nil {
		return nil, err
	}
	return append([]string{configFileName}, p.args...), nil
}

func (p *ExecPlugin) getEnv() []string {
	env := os.Environ()
	env = append(env,
		"KUSTOMIZE_PLUGIN_CONFIG_STRING="+string(p.cfg),
		"KUSTOMIZE_PLUGIN_CONFIG_ROOT="+p.ldr.Root())
	return env
}

// Returns a new copy of the given ResMap with the ResIds annotated in each Resource
func (p *ExecPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) {
	inputRM := rm.DeepCopy()
	for _, r := range inputRM.Resources() {
		idString, err := yaml.Marshal(r.CurId())
		if err != nil {
			return nil, err
		}
		annotations := r.GetAnnotations()
		if annotations == nil {
			annotations = make(map[string]string)
		}
		annotations[idAnnotation] = string(idString)
		r.SetAnnotations(annotations)
	}
	return inputRM, nil
}

/*
updateResMapValues updates the Resource value in the given ResMap
with the emitted Resource values in output.
*/
func (p *ExecPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error {
	outputRM, err := p.rf.NewResMapFromBytes(output)
	if err != nil {
		return err
	}
	for _, r := range outputRM.Resources() {
		// for each emitted Resource, find the matching Resource in the original ResMap
		// using its id
		annotations := r.GetAnnotations()
		idString, ok := annotations[idAnnotation]
		if !ok {
			return fmt.Errorf("the transformer %s should not remove annotation %s",
				p.path, idAnnotation)
		}
		id := resid.ResId{}
		err := yaml.Unmarshal([]byte(idString), &id)
		if err != nil {
			return err
		}
		res, err := rm.GetByCurrentId(id)
		if err != nil {
			return fmt.Errorf("unable to find unique match to %s", id.String())
		}
		// remove the annotation set by Kustomize to track the resource
		delete(annotations, idAnnotation)
		if len(annotations) == 0 {
			annotations = nil
		}
		r.SetAnnotations(annotations)

		// update the ResMap resource value with the transformed object
		res.Kunstructured = r.Kunstructured
	}
	return nil
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"time"

	"github.com/kelseyhightower/envconfig"
)

type TPR struct {
	ReadyWaitInterval time.Duration `split_words:"true",default:"3s"`
	ReadyWaitTimeout  time.Duration `split_words:"true",default:"30s"`
	ResyncPeriod      time.Duration `split_words:"true",default:"5m"`
}

type Resources struct {
	ResyncPeriodPod        time.Duration `split_words:"true",default:"5m"`
	ResourceCheckInterval  time.Duration `split_words:"true",default:"3s"`
	ResourceCheckTimeout   time.Duration `split_words:"true",default:"10m"`
	PodLabelWaitTimeout    time.Duration `split_words:"true",default:"10m"`
	PodDeletionWaitTimeout time.Duration `split_words:"true",default:"10m"`
}

type Auth struct {
	PamRoleName          string `split_words:"true",default:"zalandos"`
	PamConfiguration     string `split_words:"true",default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
	TeamsAPIUrl          string `envconfig:"teams_api_url",default:"https://teams.example.com/api/"`
	OAuthTokenSecretName string `envconfig:"oauth_token_secret_name",default:"postgresql-operator"`
	SuperUsername        string `split_words:"true",default:"postgres"`
	ReplicationUsername  string `split_words:"true",default:"replication"`
}

type Config struct {
	TPR
	Resources
	Auth
	EtcdHost           string `split_words:"true",default:"etcd-client.default.svc.cluster.local:2379"`
	DockerImage        string `split_words:"true",default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
	ServiceAccountName string `split_words:"true",default:"operator"`
	DbHostedZone       string `split_words:"true",default:"db.example.com"`
	DebugLogging       bool   `split_words:"true",default:"false"`
}

func LoadFromEnv() *Config {
	var cfg Config
	err := envconfig.Process("PGOP", &cfg)
	if err != nil {
		panic(fmt.Errorf("Can't read config: %v", err))
	}

	return &cfg
}
<commit_msg>fix struct tag delimiter<commit_after>package config

import (
	"fmt"
	"time"

	"github.com/kelseyhightower/envconfig"
)

type TPR struct {
	ReadyWaitInterval time.Duration `split_words:"true" default:"3s"`
	ReadyWaitTimeout  time.Duration `split_words:"true" default:"30s"`
	ResyncPeriod      time.Duration `split_words:"true" default:"5m"`
}

type Resources struct {
	ResyncPeriodPod        time.Duration `split_words:"true" default:"5m"`
	ResourceCheckInterval  time.Duration `split_words:"true" default:"3s"`
	ResourceCheckTimeout   time.Duration `split_words:"true" default:"10m"`
	PodLabelWaitTimeout    time.Duration `split_words:"true" default:"10m"`
	PodDeletionWaitTimeout time.Duration `split_words:"true" default:"10m"`
}

type Auth struct {
	PamRoleName          string `split_words:"true" default:"zalandos"`
	PamConfiguration     string `split_words:"true" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
	TeamsAPIUrl          string `envconfig:"teams_api_url" default:"https://teams.example.com/api/"`
	OAuthTokenSecretName string `envconfig:"oauth_token_secret_name" default:"postgresql-operator"`
	SuperUsername        string `split_words:"true" default:"postgres"`
	ReplicationUsername  string `split_words:"true" default:"replication"`
}

type Config struct {
	TPR
	Resources
	Auth
	EtcdHost           string `split_words:"true" default:"etcd-client.default.svc.cluster.local:2379"`
	DockerImage        string `split_words:"true" default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
	ServiceAccountName string `split_words:"true" default:"operator"`
	DbHostedZone       string `split_words:"true" default:"db.example.com"`
}

func LoadFromEnv() *Config {
	var cfg Config
	err := envconfig.Process("PGOP", &cfg)
	if err != nil {
		panic(fmt.Errorf("Can't read config: %v", err))
	}

	return &cfg
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package procfs

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"unicode"

	"github.com/golang/glog"
)

type ProcFS struct{}

func NewProcFS() ProcFSInterface {
	return &ProcFS{}
}

func containerNameFromProcCgroup(content string) (string, error) {
	lines := strings.Split(content, "\n")
	for _, line := range lines {
		entries := strings.SplitN(line, ":", 3)
		if len(entries) == 3 && entries[1] == "devices" {
			return strings.TrimSpace(entries[2]), nil
		}
	}
	return "", fmt.Errorf("could not find devices cgroup location")
}

// getFullContainerName gets the container name given the root process id of the container.
// Eg. If the devices cgroup for the container is stored in /sys/fs/cgroup/devices/docker/nginx,
// return docker/nginx. Assumes that the process is part of exactly one cgroup hierarchy.
func (pfs *ProcFS) GetFullContainerName(pid int) (string, error) {
	filePath := path.Join("/proc", strconv.Itoa(pid), "cgroup")
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	return containerNameFromProcCgroup(string(content))
}

func PidOf(name string) []int {
	pids := []int{}
	filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		base := filepath.Base(path)
		// Traverse only the directories we are interested in
		if info.IsDir() && path != "/proc" {
			// If the directory is not a number (i.e. not a PID), skip it
			if _, err := strconv.Atoi(base); err != nil {
				return filepath.SkipDir
			}
		}
		if base != "cmdline" {
			return nil
		}
		cmdline, err := ioutil.ReadFile(path)
		if err != nil {
			glog.V(4).Infof("Error reading file %s: %+v", path, err)
			return nil
		}
		// The bytes we read have '\0' as a separator for the command line
		parts := bytes.SplitN(cmdline, []byte{0}, 2)
		if len(parts) == 0 {
			return nil
		}
		// Split the command line itself we are interested in just the first part
		exe := strings.FieldsFunc(string(parts[0]), func(c rune) bool {
			return unicode.IsSpace(c) || c == ':'
		})
		if len(exe) == 0 {
			return nil
		}
		// Check if the name of the executable is what we are looking for
		if filepath.Base(exe[0]) == name {
			dirname := filepath.Base(filepath.Dir(path))
			// Grab the PID from the directory path
			pid, _ := strconv.Atoi(dirname)
			pids = append(pids, pid)
		}
		return nil
	})
	return pids
}
<commit_msg>Fix TestPidOf {procfs} - Take #2<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package procfs

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"unicode"

	"github.com/golang/glog"
)

type ProcFS struct{}

func NewProcFS() ProcFSInterface {
	return &ProcFS{}
}

func containerNameFromProcCgroup(content string) (string, error) {
	lines := strings.Split(content, "\n")
	for _, line := range lines {
		entries := strings.SplitN(line, ":", 3)
		if len(entries) == 3 && entries[1] == "devices" {
			return strings.TrimSpace(entries[2]), nil
		}
	}
	return "", fmt.Errorf("could not find devices cgroup location")
}

// getFullContainerName gets the container name given the root process id of the container.
// Eg. If the devices cgroup for the container is stored in /sys/fs/cgroup/devices/docker/nginx,
// return docker/nginx. Assumes that the process is part of exactly one cgroup hierarchy.
func (pfs *ProcFS) GetFullContainerName(pid int) (string, error) {
	filePath := path.Join("/proc", strconv.Itoa(pid), "cgroup")
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	return containerNameFromProcCgroup(string(content))
}

func PidOf(name string) []int {
	pids := []int{}
	filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			// We should continue processing other directories/files
			return nil
		}
		base := filepath.Base(path)
		// Traverse only the directories we are interested in
		if info.IsDir() && path != "/proc" {
			// If the directory is not a number (i.e. not a PID), skip it
			if _, err := strconv.Atoi(base); err != nil {
				return filepath.SkipDir
			}
		}
		if base != "cmdline" {
			return nil
		}
		cmdline, err := ioutil.ReadFile(path)
		if err != nil {
			glog.V(4).Infof("Error reading file %s: %+v", path, err)
			return nil
		}
		// The bytes we read have '\0' as a separator for the command line
		parts := bytes.SplitN(cmdline, []byte{0}, 2)
		if len(parts) == 0 {
			return nil
		}
		// Split the command line itself we are interested in just the first part
		exe := strings.FieldsFunc(string(parts[0]), func(c rune) bool {
			return unicode.IsSpace(c) || c == ':'
		})
		if len(exe) == 0 {
			return nil
		}
		// Check if the name of the executable is what we are looking for
		if filepath.Base(exe[0]) == name {
			dirname := filepath.Base(filepath.Dir(path))
			// Grab the PID from the directory path
			pid, _ := strconv.Atoi(dirname)
			pids = append(pids, pid)
		}
		return nil
	})
	return pids
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved

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

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

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

package docker

import (
	"bytes"
	dockerlib "github.com/fsouza/go-dockerclient"
	"github.com/kubernetes/kompose/pkg/utils/archive"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// Build will provide methods for interaction with API regarding building images
type Build struct {
	Client dockerlib.Client
}

/*
BuildImage builds a Docker image via the Docker API. Takes the source directory
and image name and then builds the appropriate image. Tarball is utilized
in order to make building easier.
*/
func (c *Build) BuildImage(source string, image string, dockerfile string) error {

	log.Infof("Building image '%s' from directory '%s'", image, path.Base(source))

	// Create a temporary file for tarball image packaging
	tmpFile, err := ioutil.TempFile(os.TempDir(), "kompose-image-build-")
	if err != nil {
		return err
	}
	log.Debugf("Created temporary file %v for Docker image tarballing", tmpFile.Name())

	// Create a tarball of the source directory in order to build the resulting image
	err = archive.CreateTarball(strings.Join([]string{source, ""}, "/"), tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to create a tarball")
	}

	// Load the file into memory
	tarballSource, err := os.Open(tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to load tarball into memory")
	}

	// Let's create all the options for the image building.
	outputBuffer := bytes.NewBuffer(nil)
	opts := dockerlib.BuildImageOptions{
		Name:         image,
		InputStream:  tarballSource,
		OutputStream: outputBuffer,
		Dockerfile:   dockerfile,
	}

	// Build it!
	if err := c.Client.BuildImage(opts); err != nil {
		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
	}

	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))
	log.Debugf("Image %s build output:\n%s", image, outputBuffer)

	return nil
}
<commit_msg>Fix printing docker build output if -v is given (#1255)<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved

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

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

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

package docker

import (
	"bytes"
	dockerlib "github.com/fsouza/go-dockerclient"
	"github.com/kubernetes/kompose/pkg/utils/archive"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// Build will provide methods for interaction with API regarding building images
type Build struct {
	Client dockerlib.Client
}

/*
BuildImage builds a Docker image via the Docker API. Takes the source directory
and image name and then builds the appropriate image. Tarball is utilized
in order to make building easier.
*/
func (c *Build) BuildImage(source string, image string, dockerfile string) error {

	log.Infof("Building image '%s' from directory '%s'", image, path.Base(source))

	// Create a temporary file for tarball image packaging
	tmpFile, err := ioutil.TempFile(os.TempDir(), "kompose-image-build-")
	if err != nil {
		return err
	}
	log.Debugf("Created temporary file %v for Docker image tarballing", tmpFile.Name())

	// Create a tarball of the source directory in order to build the resulting image
	err = archive.CreateTarball(strings.Join([]string{source, ""}, "/"), tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to create a tarball")
	}

	// Load the file into memory
	tarballSource, err := os.Open(tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to load tarball into memory")
	}

	// Let's create all the options for the image building.
	outputBuffer := bytes.NewBuffer(nil)
	opts := dockerlib.BuildImageOptions{
		Name:         image,
		InputStream:  tarballSource,
		OutputStream: outputBuffer,
		Dockerfile:   dockerfile,
	}

	// Build it!
	err = c.Client.BuildImage(opts)
	log.Debugf("Image %s build output:\n%s", image, outputBuffer)

	if err != nil {
		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
	}

	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved

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

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

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

package docker

import (
	"bytes"
	dockerlib "github.com/fsouza/go-dockerclient"
	"github.com/kubernetes/kompose/pkg/utils/archive"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// Build will provide methods for interaction with API regarding building images
type Build struct {
	Client dockerlib.Client
}

/*
BuildImage builds a Docker image via the Docker API. Takes the source directory
and image name and then builds the appropriate image. Tarball is utilized
in order to make building easier.
*/
func (c *Build) BuildImage(source string, image string, dockerfile string) error {

	log.Infof("Building image '%s' from directory '%s'", image, path.Base(source))

	// Create a temporary file for tarball image packaging
	tmpFile, err := ioutil.TempFile("/tmp", "kompose-image-build-")
	if err != nil {
		return err
	}
	log.Debugf("Created temporary file %v for Docker image tarballing", tmpFile.Name())

	// Create a tarball of the source directory in order to build the resulting image
	err = archive.CreateTarball(strings.Join([]string{source, ""}, "/"), tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to create a tarball")
	}

	// Load the file into memory
	tarballSource, err := os.Open(tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to load tarball into memory")
	}

	// Let's create all the options for the image building.
	outputBuffer := bytes.NewBuffer(nil)
	opts := dockerlib.BuildImageOptions{
		Name:         image,
		InputStream:  tarballSource,
		OutputStream: outputBuffer,
		Dockerfile:   dockerfile,
	}

	// Build it!
	if err := c.Client.BuildImage(opts); err != nil {
		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
	}

	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))
	log.Debugf("Image %s build output:\n%s", image, outputBuffer)

	return nil
}
<commit_msg>Use OS provided temporary directory<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved

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

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

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

package docker

import (
	"bytes"
	dockerlib "github.com/fsouza/go-dockerclient"
	"github.com/kubernetes/kompose/pkg/utils/archive"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// Build will provide methods for interaction with API regarding building images
type Build struct {
	Client dockerlib.Client
}

/*
BuildImage builds a Docker image via the Docker API. Takes the source directory
and image name and then builds the appropriate image. Tarball is utilized
in order to make building easier.
*/
func (c *Build) BuildImage(source string, image string, dockerfile string) error {

	log.Infof("Building image '%s' from directory '%s'", image, path.Base(source))

	// Create a temporary file for tarball image packaging
	tmpFile, err := ioutil.TempFile(os.TempDir(), "kompose-image-build-")
	if err != nil {
		return err
	}
	log.Debugf("Created temporary file %v for Docker image tarballing", tmpFile.Name())

	// Create a tarball of the source directory in order to build the resulting image
	err = archive.CreateTarball(strings.Join([]string{source, ""}, "/"), tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to create a tarball")
	}

	// Load the file into memory
	tarballSource, err := os.Open(tmpFile.Name())
	if err != nil {
		return errors.Wrap(err, "Unable to load tarball into memory")
	}

	// Let's create all the options for the image building.
	outputBuffer := bytes.NewBuffer(nil)
	opts := dockerlib.BuildImageOptions{
		Name:         image,
		InputStream:  tarballSource,
		OutputStream: outputBuffer,
		Dockerfile:   dockerfile,
	}

	// Build it!
	if err := c.Client.BuildImage(opts); err != nil {
		return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.")
	}

	log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source))
	log.Debugf("Image %s build output:\n%s", image, outputBuffer)

	return nil
}
<|endoftext|>


<commit_before>package class

import (
    . "jvmgo/any"
    cf "jvmgo/classfile"
)

type ConstantMethodref struct {
    className       string
    name            string
    descriptor      string
    cp              *ConstantPool
    method          *Method
    nativeMethod    Any // cannot use package 'native' because of cycle import!
}

type ConstantInterfaceMethodref struct {
    ConstantMethodref
}

func (self *ConstantMethodref) NativeMethod() (Any) {
    return self.nativeMethod
}

// method resolution
func (self *ConstantMethodref) StaticMethod() (*Method) {
    if self.method == nil {
        method := self.findMethod(self.className)
        if method != nil && method.IsStatic() {
            if method.IsNative() && !method.IsRegisterNatives() {
                self.nativeMethod = findNativeMethod(method)
            }
            self.method = method
        } else {
            // todo
            panic("method not found!")
        }
    }
    return self.method
}
func (self *ConstantMethodref) SpecialMethod() (*Method) {
    if self.method == nil {
        method := self.findMethod(self.className)
        if method != nil && !method.IsStatic() {
            if method.IsNative(){
                self.nativeMethod = findNativeMethod(method)
            }
            self.method = method
        } else {
            // todo
            panic("method not found!")
        }
    }
    return self.method
}
func (self *ConstantMethodref) findMethod(className string) (*Method) {
    class := self.cp.class.classLoader.LoadClass(className)
    return class.getMethod(self.name, self.descriptor)
}
func (self *ConstantMethodref) VirtualMethod() (*Method) {
    if self.method == nil {
        classLoader := self.cp.class.classLoader
        className := self.className
        for {
            if className != "" {
                class := classLoader.LoadClass(className)
                method := class.getMethod(self.name, self.descriptor)
                if method != nil && !method.IsStatic() {
                    self.method = method
                    return method
                } else {
                    className = class.superClassName
                }
            } else {
                break
            }
        }
        // todo
        panic("method not found!")
    }
    return self.method
}

func newConstantMethodref(cp *ConstantPool, methodrefInfo *cf.ConstantMethodrefInfo) (*ConstantMethodref) {
    methodref := &ConstantMethodref{}
    methodref.className = methodrefInfo.ClassName()
    methodref.name = methodrefInfo.Name()
    methodref.descriptor = methodrefInfo.Descriptor()
    methodref.cp = cp
    return methodref
}

func newConstantInterfaceMethodref(cp *ConstantPool, methodrefInfo *cf.ConstantInterfaceMethodrefInfo) (*ConstantInterfaceMethodref) {
    methodref := &ConstantInterfaceMethodref{}
    methodref.className = methodrefInfo.ClassName()
    methodref.name = methodrefInfo.Name()
    methodref.descriptor = methodrefInfo.Descriptor()
    methodref.cp = cp
    return methodref
}
<commit_msg>code refactor<commit_after>package class

import (
    . "jvmgo/any"
    cf "jvmgo/classfile"
)

type ConstantMethodref struct {
    className       string
    name            string
    descriptor      string
    cp              *ConstantPool
    method          *Method
    nativeMethod    Any // cannot use package 'native' because of cycle import!
}

type ConstantInterfaceMethodref struct {
    ConstantMethodref
}

func (self *ConstantMethodref) NativeMethod() (Any) {
    return self.nativeMethod
}

// method resolution
func (self *ConstantMethodref) StaticMethod() (*Method) {
    if self.method == nil {
        method := self.findMethod(self.className)
        if method != nil && method.IsStatic() {
            if method.IsNative() && !method.IsRegisterNatives() {
                self.nativeMethod = findNativeMethod(method)
            }
            self.method = method
        } else {
            // todo
            panic("method not found!")
        }
    }
    return self.method
}
func (self *ConstantMethodref) SpecialMethod() (*Method) {
    if self.method == nil {
        method := self.findMethod(self.className)
        if method != nil && !method.IsStatic() {
            if method.IsNative() {
                self.nativeMethod = findNativeMethod(method)
            }
            self.method = method
        } else {
            // todo
            panic("method not found!")
        }
    }
    return self.method
}
func (self *ConstantMethodref) VirtualMethod() (*Method) {
    if self.method == nil {
        classLoader := self.cp.class.classLoader
        className := self.className
        for {
            if className != "" {
                class := classLoader.LoadClass(className)
                method := class.getMethod(self.name, self.descriptor)
                if method != nil && !method.IsStatic() {
                    if method.IsNative() {
                        self.nativeMethod = findNativeMethod(method)
                    }
                    self.method = method
                    return method
                } else {
                    className = class.superClassName
                }
            } else {
                break
            }
        }
        // todo
        panic("method not found!")
    }
    return self.method
}
func (self *ConstantMethodref) findMethod(className string) (*Method) {
    class := self.cp.class.classLoader.LoadClass(className)
    return class.getMethod(self.name, self.descriptor)
}

func newConstantMethodref(cp *ConstantPool, methodrefInfo *cf.ConstantMethodrefInfo) (*ConstantMethodref) {
    methodref := &ConstantMethodref{}
    methodref.className = methodrefInfo.ClassName()
    methodref.name = methodrefInfo.Name()
    methodref.descriptor = methodrefInfo.Descriptor()
    methodref.cp = cp
    return methodref
}

func newConstantInterfaceMethodref(cp *ConstantPool, methodrefInfo *cf.ConstantInterfaceMethodrefInfo) (*ConstantInterfaceMethodref) {
    methodref := &ConstantInterfaceMethodref{}
    methodref.className = methodrefInfo.ClassName()
    methodref.name = methodrefInfo.Name()
    methodref.descriptor = methodrefInfo.Descriptor()
    methodref.cp = cp
    return methodref
}
<|endoftext|>


<commit_before>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * io/writer_test.go                                      *
 *                                                        *
 * hprose writer test for Go.                             *
 *                                                        *
 * LastModified: Aug 17, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package io

import (
	"bytes"
	"math"
	"math/rand"
	"strconv"
	"testing"
)

func testSerializeNil(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(nil)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "n" {
		t.Error(b.String())
	}
}
func testSerializeTrue(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(true)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "t" {
		t.Error(b.String())
	}
}
func testSerializeFalse(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(false)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "f" {
		t.Error(b.String())
	}
}
func testSerializeDigit(t *testing.T, writer *Writer, b *bytes.Buffer) {
	for i := 0; i <= 9; i++ {
		b.Truncate(0)
		err := writer.Serialize(i)
		if err != nil {
			t.Error(err.Error())
		}
		if b.String() != strconv.Itoa(i) {
			t.Error(b.String())
		}
	}
}
func testSerializeInt(t *testing.T, writer *Writer, b *bytes.Buffer) {
	for i := 0; i <= 100; i++ {
		b.Truncate(0)
		x := rand.Intn(math.MaxInt32-10) + 10
		err := writer.Serialize(x)
		if err != nil {
			t.Error(err.Error())
		}
		if b.String() != "i"+strconv.Itoa(x)+";" {
			t.Error(b.String())
		}
	}
}

func TestSerialize(t *testing.T) {
	b := new(bytes.Buffer)
	writer := NewWriter(b, false)
	testSerializeNil(t, writer, b)
	testSerializeTrue(t, writer, b)
	testSerializeFalse(t, writer, b)
	testSerializeDigit(t, writer, b)
	testSerializeInt(t, writer, b)
}
<commit_msg>update writer_test<commit_after>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * io/writer_test.go                                      *
 *                                                        *
 * hprose writer test for Go.                             *
 *                                                        *
 * LastModified: Aug 17, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package io

import (
	"bytes"
	"math"
	"math/rand"
	"strconv"
	"testing"
)

func testSerializeNil(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(nil)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "n" {
		t.Error(b.String())
	}
}
func testSerializeTrue(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(true)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "t" {
		t.Error(b.String())
	}
}
func testSerializeFalse(t *testing.T, writer *Writer, b *bytes.Buffer) {
	b.Truncate(0)
	err := writer.Serialize(false)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "f" {
		t.Error(b.String())
	}
}
func testSerializeDigit(t *testing.T, writer *Writer, b *bytes.Buffer) {
	for i := 0; i <= 9; i++ {
		b.Truncate(0)
		err := writer.Serialize(i)
		if err != nil {
			t.Error(err.Error())
		}
		if b.String() != strconv.Itoa(i) {
			t.Error(b.String())
		}
	}
}
func testSerializeInt(t *testing.T, writer *Writer, b *bytes.Buffer) {
	for i := 0; i <= 100; i++ {
		b.Truncate(0)
		x := rand.Intn(math.MaxInt32-10) + 10
		err := writer.Serialize(x)
		if err != nil {
			t.Error(err.Error())
		}
		if b.String() != "i"+strconv.Itoa(x)+";" {
			t.Error(b.String())
		}
	}
}

func testSerializeLong(t *testing.T, writer *Writer, b *bytes.Buffer) {
	for i := 0; i <= 100; i++ {
		b.Truncate(0)
		x := rand.Intn(math.MaxInt64-math.MaxInt32-1) + math.MaxInt32 + 1
		err := writer.Serialize(x)
		if err != nil {
			t.Error(err.Error())
		}
		if b.String() != "l"+strconv.Itoa(x)+";" {
			t.Error(b.String())
		}
	}
}

func TestSerialize(t *testing.T) {
	b := new(bytes.Buffer)
	writer := NewWriter(b, false)
	testSerializeNil(t, writer, b)
	testSerializeTrue(t, writer, b)
	testSerializeFalse(t, writer, b)
	testSerializeDigit(t, writer, b)
	testSerializeInt(t, writer, b)
	testSerializeLong(t, writer, b)
}
<|endoftext|>


<commit_before>// [ "$GOOS" = windows ] ||
// $G $D/$F.go && $L $F.$A && ./$A.out

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

package main

import "syscall"

func main() {
	syscall.Kill(syscall.Getpid(), syscall.SIGCHLD)
	println("survived SIGCHLD")
}
<commit_msg>test: fix windows build<commit_after>// ([ "$GOOS" = windows ] && echo survived SIGCHLD) ||
// ($G $D/$F.go && $L $F.$A && ./$A.out)

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

package main

import "syscall"

func main() {
	syscall.Kill(syscall.Getpid(), syscall.SIGCHLD)
	println("survived SIGCHLD")
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 probes

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
	"github.com/gophercloud/gophercloud/pagination"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology"
	"github.com/skydive-project/skydive/topology/graph"
)

// NeutronProbe describes a topology porbe that map neutron attribues in the graph
type NeutronProbe struct {
	graph.DefaultGraphListener
	graph           *graph.Graph
	client          *gophercloud.ServiceClient
	portMetadata    map[graph.Identifier]portMetadata
	nodeUpdaterChan chan graph.Identifier
	intfRegexp      *regexp.Regexp
	nsRegexp        *regexp.Regexp
	opts            gophercloud.AuthOptions
	regionName      string
	availability    gophercloud.Availability
}

// attributes neutron attributes
type attributes struct {
	PortID      string
	NetworkID   string
	NetworkName string
	TenantID    string
	IPs         string
	VNI         string
}

// portMetadata neutron metadata
type portMetadata struct {
	mac    string
	portID string
}

// NeutronPortNotFound error
type NeutronPortNotFound struct {
	MAC string
}

func (e NeutronPortNotFound) Error() string {
	return "Unable to find port for MAC address: " + e.MAC
}

func retrieveportMetadata(node *graph.Node) portMetadata {
	md := portMetadata{}

	// We prefer to use the 'ExtID/attached-mac' metadata to get
	// the port, and we fallback to the 'mac' metadata.
	if attachedMAC, _ := node.GetFieldString("ExtID.attached-mac"); attachedMAC != "" {
		md.mac = attachedMAC
	} else if mac, _ := node.GetFieldString("MAC"); mac != "" {
		md.mac = mac
	}

	if ifaceID, _ := node.GetFieldString("ExtID.iface-id"); ifaceID != "" {
		md.portID = ifaceID
	}
	return md
}

func (mapper *NeutronProbe) retrievePort(portMd portMetadata) (port ports.Port, err error) {
	var opts ports.ListOpts

	logging.GetLogger().Debugf("Retrieving attributes from Neutron for MAC: %s", portMd.mac)

	/* Determine the best way to search for the Neutron port.
	 * We prefer the Neutron port UUID if we have it, but will fall back
	 * to using the MAC address otherwise. */
	if portMd.portID != "" {
		opts.ID = portMd.portID
	} else {
		opts.MACAddress = portMd.mac
	}
	pager := ports.List(mapper.client, opts)

	err = pager.EachPage(func(page pagination.Page) (bool, error) {
		portList, err := ports.ExtractPorts(page)
		if err != nil {
			return false, err
		}

		for _, p := range portList {
			if p.MACAddress == portMd.mac {
				port = p
				return false, nil
			}
		}

		return true, nil
	})

	if err != nil {
		return port, err
	}

	if len(port.NetworkID) == 0 {
		return port, NeutronPortNotFound{portMd.mac}
	}

	return port, err
}

func (mapper *NeutronProbe) retrieveAttributes(portMd portMetadata) (*attributes, error) {
	port, err := mapper.retrievePort(portMd)
	if err != nil {
		return nil, err
	}

	type netWithProvider struct {
		networks.Network
		provider.NetworkProviderExt
	}

	var network netWithProvider
	result := networks.Get(mapper.client, port.NetworkID)
	err = result.ExtractInto(&network)

	if err != nil {
		return nil, err
	}

	var IPs []string
	for _, element := range port.FixedIPs {
		IPs = append(IPs, element.IPAddress)
	}

	a := &attributes{
		PortID:      port.ID,
		NetworkID:   port.NetworkID,
		NetworkName: network.Name,
		TenantID:    port.TenantID,
		IPs:         strings.Join(IPs[:], ","),
		VNI:         network.SegmentationID,
	}

	return a, nil
}

func (mapper *NeutronProbe) nodeUpdater() {
	logging.GetLogger().Debugf("Starting Neutron updater")

	for nodeID := range mapper.nodeUpdaterChan {
		node := mapper.graph.GetNode(nodeID)
		if node == nil {
			continue
		}

		if mac, _ := node.GetFieldString("MAC"); mac == "" {
			continue
		}

		portMd := retrieveportMetadata(node)

		attrs, err := mapper.retrieveAttributes(portMd)
		if err != nil {
			if nerr, ok := err.(NeutronPortNotFound); ok {
				logging.GetLogger().Debugf("Setting in cache not found MAC %s", nerr.MAC)
			} else {
				logging.GetLogger().Errorf("Failed to retrieve attributes for port %s/%s : %v",
					portMd.portID, portMd.mac, err)
			}
		} else {
			mapper.updateNode(node, attrs)
		}
	}
	logging.GetLogger().Debugf("Stopping Neutron updater")
}

func (mapper *NeutronProbe) updateNode(node *graph.Node, attrs *attributes) {
	mapper.graph.Lock()
	defer mapper.graph.Unlock()

	metadata := make(map[string]interface{})

	if attrs.PortID != "" {
		metadata["Neutron.PortID"] = attrs.PortID
	}

	if attrs.TenantID != "" {
		metadata["Neutron.TenantID"] = attrs.TenantID
	}

	if attrs.NetworkID != "" {
		metadata["Neutron.NetworkID"] = attrs.NetworkID
	}

	if attrs.NetworkName != "" {
		metadata["Neutron.NetworkName"] = attrs.NetworkName
	}

	if attrs.IPs != "" {
		metadata["Neutron.IPs"] = attrs.IPs
	}

	if segID, err := strconv.Atoi(attrs.VNI); err != nil && segID > 0 {
		metadata["Neutron.VNI"] = int64(segID)
	}

	tr := mapper.graph.StartMetadataTransaction(node)

	metadata["Manager"] = "neutron"
	for k, v := range metadata {
		tr.AddMetadata(k, v)
	}

	tr.Commit()

	name, _ := node.GetFieldString("Name")
	if name == "" {
		return
	}

	if !strings.HasPrefix(name, "qvo") {
		return
	}

	// tap to qvo path
	tap := strings.Replace(name, "qvo", "tap", 1)

	if uuid, _ := node.GetFieldString("ExtID.vm-uuid"); uuid != "" {
		if attachedMac, _ := node.GetFieldString("ExtID.attached-mac"); attachedMac != "" {
			retryFnc := func() error {
				mapper.graph.Lock()
				defer mapper.graph.Unlock()

				if path := mapper.graph.LookupShortestPath(node, graph.Metadata{"Name": tap}, topology.Layer2Metadata); len(path) > 0 {
					for i, n := range path {
						tr := mapper.graph.StartMetadataTransaction(n)
						tr.AddMetadata("ExtID.vm-uuid", uuid)
						tr.AddMetadata("ExtID.attached-mac", attachedMac)
						for k, v := range metadata {
							tr.AddMetadata(k, v)
						}

						// add vm peering info, going to be used by peering probe
						if i == len(path)-1 {
							tr.AddMetadata("PeerIntfMAC", attachedMac)
						}
						tr.Commit()
					}

					return nil
				}
				return errors.New("Path not found")
			}
			go common.Retry(retryFnc, 60, 1*time.Second)
		}
	}
}

// enhanceNode enhance the graph node with neutron metadata (Name, MAC, Manager ...)
func (mapper *NeutronProbe) enhanceNode(node *graph.Node) {
	name, _ := node.GetFieldString("Name")
	if name == "" {
		return
	}

	if mapper.nsRegexp.MatchString(name) {
		mapper.graph.AddMetadata(node, "Manager", "neutron")
		return
	}

	if !mapper.intfRegexp.MatchString(name) {
		return
	}

	mac, _ := node.GetFieldString("MAC")
	if mac == "" {
		return
	}

	prevPortMd, f := mapper.portMetadata[node.ID]
	currPortMd := retrieveportMetadata(node)

	// If port metadata have not changed, we return
	if f && (prevPortMd == currPortMd) {
		return
	}
	// We only try to get Neutron metadata one time per port
	// metadata values
	mapper.portMetadata[node.ID] = currPortMd

	mapper.nodeUpdaterChan <- node.ID
}

// OnNodeUpdated event
func (mapper *NeutronProbe) OnNodeUpdated(n *graph.Node) {
	mapper.enhanceNode(n)
}

// OnNodeAdded event
func (mapper *NeutronProbe) OnNodeAdded(n *graph.Node) {
	mapper.enhanceNode(n)
}

// OnNodeDeleted event
func (mapper *NeutronProbe) OnNodeDeleted(n *graph.Node) {
	delete(mapper.portMetadata, n.ID)
}

// Start the probe
func (mapper *NeutronProbe) Start() {
	go func() {
		for mapper.client == nil {
			provider, err := openstack.AuthenticatedClient(mapper.opts)
			if err != nil {
				logging.GetLogger().Errorf("keystone authentication error: %s", err)
				time.Sleep(time.Second)
				continue
			}

			client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
				Name:         "neutron",
				Region:       mapper.regionName,
				Availability: mapper.availability,
			})
			if err != nil {
				logging.GetLogger().Errorf("keystone authentication error: %s", err)
				time.Sleep(time.Second)
				continue
			}
			mapper.client = client
		}
		mapper.graph.RLock()
		for _, n := range mapper.graph.GetNodes(nil) {
			mapper.enhanceNode(n)
		}
		mapper.graph.RUnlock()

		mapper.nodeUpdater()
	}()
}

// Stop the probe
func (mapper *NeutronProbe) Stop() {
	mapper.graph.RemoveEventListener(mapper)
	close(mapper.nodeUpdaterChan)
}

// NewNeutronProbe creates a neutron probe that will enhance the graph
func NewNeutronProbe(g *graph.Graph, authURL, username, password, tenantName, regionName, domainName string, availability gophercloud.Availability) (*NeutronProbe, error) {
	// only looking for interfaces matching the following regex as nova, neutron interfaces match this pattern
	intfRegexp := regexp.MustCompile(`(tap|qr-|qg-|qvo)[a-fA-F0-9]{8}-[a-fA-F0-9]{2}`)
	nsRegexp := regexp.MustCompile(`(qrouter|qdhcp)-[a-fA-F0-9]{8}`)

	opts := gophercloud.AuthOptions{
		IdentityEndpoint: authURL,
		Username:         username,
		Password:         password,
		TenantName:       tenantName,
		DomainName:       domainName,
		AllowReauth:      true,
	}

	mapper := &NeutronProbe{
		graph:           g,
		intfRegexp:      intfRegexp,
		nsRegexp:        nsRegexp,
		regionName:      regionName,
		availability:    availability,
		opts:            opts,
		nodeUpdaterChan: make(chan graph.Identifier, 500),
		portMetadata:    make(map[graph.Identifier]portMetadata),
	}

	g.AddEventListener(mapper)

	return mapper, nil
}

// NewNeutronProbeFromConfig creates a new neutron probe based on configuration
func NewNeutronProbeFromConfig(g *graph.Graph) (*NeutronProbe, error) {
	authURL := config.GetConfig().GetString("openstack.auth_url")
	username := config.GetConfig().GetString("openstack.username")
	password := config.GetConfig().GetString("openstack.password")
	tenantName := config.GetConfig().GetString("openstack.tenant_name")
	regionName := config.GetConfig().GetString("openstack.region_name")
	endpointType := config.GetConfig().GetString("openstack.endpoint_type")
	domainName := config.GetConfig().GetString("openstack.domain_name")

	endpointTypes := map[string]gophercloud.Availability{
		"public":   gophercloud.AvailabilityPublic,
		"admin":    gophercloud.AvailabilityAdmin,
		"internal": gophercloud.AvailabilityInternal,
	}

	if a, ok := endpointTypes[endpointType]; ok {
		return NewNeutronProbe(g, authURL, username, password, tenantName, regionName, domainName, a)
	}

	return nil, fmt.Errorf("Endpoint type '%s' is not valid (must be 'public', 'admin' or 'internal')", endpointType)
}
<commit_msg>Neutron: add subnet cidr to neutron ips (#504)<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 probes

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/provider"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
	"github.com/gophercloud/gophercloud/pagination"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology"
	"github.com/skydive-project/skydive/topology/graph"
)

// NeutronProbe describes a topology porbe that map neutron attribues in the graph
type NeutronProbe struct {
	graph.DefaultGraphListener
	graph           *graph.Graph
	client          *gophercloud.ServiceClient
	portMetadata    map[graph.Identifier]portMetadata
	nodeUpdaterChan chan graph.Identifier
	intfRegexp      *regexp.Regexp
	nsRegexp        *regexp.Regexp
	opts            gophercloud.AuthOptions
	regionName      string
	availability    gophercloud.Availability
}

// attributes neutron attributes
type attributes struct {
	PortID      string
	NetworkID   string
	NetworkName string
	TenantID    string
	IPs         string
	VNI         string
}

// portMetadata neutron metadata
type portMetadata struct {
	mac    string
	portID string
}

// NeutronPortNotFound error
type NeutronPortNotFound struct {
	MAC string
}

func (e NeutronPortNotFound) Error() string {
	return "Unable to find port for MAC address: " + e.MAC
}

func retrieveportMetadata(node *graph.Node) portMetadata {
	md := portMetadata{}

	// We prefer to use the 'ExtID/attached-mac' metadata to get
	// the port, and we fallback to the 'mac' metadata.
	if attachedMAC, _ := node.GetFieldString("ExtID.attached-mac"); attachedMAC != "" {
		md.mac = attachedMAC
	} else if mac, _ := node.GetFieldString("MAC"); mac != "" {
		md.mac = mac
	}

	if ifaceID, _ := node.GetFieldString("ExtID.iface-id"); ifaceID != "" {
		md.portID = ifaceID
	}
	return md
}

func (mapper *NeutronProbe) retrievePort(portMd portMetadata) (port ports.Port, err error) {
	var opts ports.ListOpts

	logging.GetLogger().Debugf("Retrieving attributes from Neutron for MAC: %s", portMd.mac)

	/* Determine the best way to search for the Neutron port.
	 * We prefer the Neutron port UUID if we have it, but will fall back
	 * to using the MAC address otherwise. */
	if portMd.portID != "" {
		opts.ID = portMd.portID
	} else {
		opts.MACAddress = portMd.mac
	}
	pager := ports.List(mapper.client, opts)

	err = pager.EachPage(func(page pagination.Page) (bool, error) {
		portList, err := ports.ExtractPorts(page)
		if err != nil {
			return false, err
		}

		for _, p := range portList {
			if p.MACAddress == portMd.mac {
				port = p
				return false, nil
			}
		}

		return true, nil
	})

	if err != nil {
		return port, err
	}

	if len(port.NetworkID) == 0 {
		return port, NeutronPortNotFound{portMd.mac}
	}

	return port, err
}

func (mapper *NeutronProbe) retrieveAttributes(portMd portMetadata) (*attributes, error) {
	port, err := mapper.retrievePort(portMd)
	if err != nil {
		return nil, err
	}

	type netWithProvider struct {
		networks.Network
		provider.NetworkProviderExt
	}

	var network netWithProvider
	result := networks.Get(mapper.client, port.NetworkID)
	err = result.ExtractInto(&network)

	if err != nil {
		return nil, err
	}

	var IPs []string
	for _, element := range port.FixedIPs {
		subnet, err := subnets.Get(mapper.client, element.SubnetID).Extract()
		if err != nil {
			return nil, err
		}
		neutronIPs := element.IPAddress + "/" + strings.Split(subnet.CIDR, "/")[1]
		IPs = append(IPs, neutronIPs)
	}

	a := &attributes{
		PortID:      port.ID,
		NetworkID:   port.NetworkID,
		NetworkName: network.Name,
		TenantID:    port.TenantID,
		IPs:         strings.Join(IPs[:], ","),
		VNI:         network.SegmentationID,
	}

	return a, nil
}

func (mapper *NeutronProbe) nodeUpdater() {
	logging.GetLogger().Debugf("Starting Neutron updater")

	for nodeID := range mapper.nodeUpdaterChan {
		node := mapper.graph.GetNode(nodeID)
		if node == nil {
			continue
		}

		if mac, _ := node.GetFieldString("MAC"); mac == "" {
			continue
		}

		portMd := retrieveportMetadata(node)

		attrs, err := mapper.retrieveAttributes(portMd)
		if err != nil {
			if nerr, ok := err.(NeutronPortNotFound); ok {
				logging.GetLogger().Debugf("Setting in cache not found MAC %s", nerr.MAC)
			} else {
				logging.GetLogger().Errorf("Failed to retrieve attributes for port %s/%s : %v",
					portMd.portID, portMd.mac, err)
			}
		} else {
			mapper.updateNode(node, attrs)
		}
	}
	logging.GetLogger().Debugf("Stopping Neutron updater")
}

func (mapper *NeutronProbe) updateNode(node *graph.Node, attrs *attributes) {
	mapper.graph.Lock()
	defer mapper.graph.Unlock()

	metadata := make(map[string]interface{})

	if attrs.PortID != "" {
		metadata["Neutron.PortID"] = attrs.PortID
	}

	if attrs.TenantID != "" {
		metadata["Neutron.TenantID"] = attrs.TenantID
	}

	if attrs.NetworkID != "" {
		metadata["Neutron.NetworkID"] = attrs.NetworkID
	}

	if attrs.NetworkName != "" {
		metadata["Neutron.NetworkName"] = attrs.NetworkName
	}

	if attrs.IPs != "" {
		metadata["Neutron.IPs"] = attrs.IPs
	}

	if segID, err := strconv.Atoi(attrs.VNI); err != nil && segID > 0 {
		metadata["Neutron.VNI"] = int64(segID)
	}

	tr := mapper.graph.StartMetadataTransaction(node)

	metadata["Manager"] = "neutron"
	for k, v := range metadata {
		tr.AddMetadata(k, v)
	}

	tr.Commit()

	name, _ := node.GetFieldString("Name")
	if name == "" {
		return
	}

	if !strings.HasPrefix(name, "qvo") {
		return
	}

	// tap to qvo path
	tap := strings.Replace(name, "qvo", "tap", 1)

	if uuid, _ := node.GetFieldString("ExtID.vm-uuid"); uuid != "" {
		if attachedMac, _ := node.GetFieldString("ExtID.attached-mac"); attachedMac != "" {
			retryFnc := func() error {
				mapper.graph.Lock()
				defer mapper.graph.Unlock()

				if path := mapper.graph.LookupShortestPath(node, graph.Metadata{"Name": tap}, topology.Layer2Metadata); len(path) > 0 {
					for i, n := range path {
						tr := mapper.graph.StartMetadataTransaction(n)
						tr.AddMetadata("ExtID.vm-uuid", uuid)
						tr.AddMetadata("ExtID.attached-mac", attachedMac)
						for k, v := range metadata {
							tr.AddMetadata(k, v)
						}

						// add vm peering info, going to be used by peering probe
						if i == len(path)-1 {
							tr.AddMetadata("PeerIntfMAC", attachedMac)
						}
						tr.Commit()
					}

					return nil
				}
				return errors.New("Path not found")
			}
			go common.Retry(retryFnc, 60, 1*time.Second)
		}
	}
}

// enhanceNode enhance the graph node with neutron metadata (Name, MAC, Manager ...)
func (mapper *NeutronProbe) enhanceNode(node *graph.Node) {
	name, _ := node.GetFieldString("Name")
	if name == "" {
		return
	}

	if mapper.nsRegexp.MatchString(name) {
		mapper.graph.AddMetadata(node, "Manager", "neutron")
		return
	}

	if !mapper.intfRegexp.MatchString(name) {
		return
	}

	mac, _ := node.GetFieldString("MAC")
	if mac == "" {
		return
	}

	prevPortMd, f := mapper.portMetadata[node.ID]
	currPortMd := retrieveportMetadata(node)

	// If port metadata have not changed, we return
	if f && (prevPortMd == currPortMd) {
		return
	}
	// We only try to get Neutron metadata one time per port
	// metadata values
	mapper.portMetadata[node.ID] = currPortMd

	mapper.nodeUpdaterChan <- node.ID
}

// OnNodeUpdated event
func (mapper *NeutronProbe) OnNodeUpdated(n *graph.Node) {
	mapper.enhanceNode(n)
}

// OnNodeAdded event
func (mapper *NeutronProbe) OnNodeAdded(n *graph.Node) {
	mapper.enhanceNode(n)
}

// OnNodeDeleted event
func (mapper *NeutronProbe) OnNodeDeleted(n *graph.Node) {
	delete(mapper.portMetadata, n.ID)
}

// Start the probe
func (mapper *NeutronProbe) Start() {
	go func() {
		for mapper.client == nil {
			provider, err := openstack.AuthenticatedClient(mapper.opts)
			if err != nil {
				logging.GetLogger().Errorf("keystone authentication error: %s", err)
				time.Sleep(time.Second)
				continue
			}

			client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
				Name:         "neutron",
				Region:       mapper.regionName,
				Availability: mapper.availability,
			})
			if err != nil {
				logging.GetLogger().Errorf("keystone authentication error: %s", err)
				time.Sleep(time.Second)
				continue
			}
			mapper.client = client
		}
		mapper.graph.RLock()
		for _, n := range mapper.graph.GetNodes(nil) {
			mapper.enhanceNode(n)
		}
		mapper.graph.RUnlock()

		mapper.nodeUpdater()
	}()
}

// Stop the probe
func (mapper *NeutronProbe) Stop() {
	mapper.graph.RemoveEventListener(mapper)
	close(mapper.nodeUpdaterChan)
}

// NewNeutronProbe creates a neutron probe that will enhance the graph
func NewNeutronProbe(g *graph.Graph, authURL, username, password, tenantName, regionName, domainName string, availability gophercloud.Availability) (*NeutronProbe, error) {
	// only looking for interfaces matching the following regex as nova, neutron interfaces match this pattern
	intfRegexp := regexp.MustCompile(`(tap|qr-|qg-|qvo)[a-fA-F0-9]{8}-[a-fA-F0-9]{2}`)
	nsRegexp := regexp.MustCompile(`(qrouter|qdhcp)-[a-fA-F0-9]{8}`)

	opts := gophercloud.AuthOptions{
		IdentityEndpoint: authURL,
		Username:         username,
		Password:         password,
		TenantName:       tenantName,
		DomainName:       domainName,
		AllowReauth:      true,
	}

	mapper := &NeutronProbe{
		graph:           g,
		intfRegexp:      intfRegexp,
		nsRegexp:        nsRegexp,
		regionName:      regionName,
		availability:    availability,
		opts:            opts,
		nodeUpdaterChan: make(chan graph.Identifier, 500),
		portMetadata:    make(map[graph.Identifier]portMetadata),
	}

	g.AddEventListener(mapper)

	return mapper, nil
}

// NewNeutronProbeFromConfig creates a new neutron probe based on configuration
func NewNeutronProbeFromConfig(g *graph.Graph) (*NeutronProbe, error) {
	authURL := config.GetConfig().GetString("openstack.auth_url")
	username := config.GetConfig().GetString("openstack.username")
	password := config.GetConfig().GetString("openstack.password")
	tenantName := config.GetConfig().GetString("openstack.tenant_name")
	regionName := config.GetConfig().GetString("openstack.region_name")
	endpointType := config.GetConfig().GetString("openstack.endpoint_type")
	domainName := config.GetConfig().GetString("openstack.domain_name")

	endpointTypes := map[string]gophercloud.Availability{
		"public":   gophercloud.AvailabilityPublic,
		"admin":    gophercloud.AvailabilityAdmin,
		"internal": gophercloud.AvailabilityInternal,
	}

	if a, ok := endpointTypes[endpointType]; ok {
		return NewNeutronProbe(g, authURL, username, password, tenantName, regionName, domainName, a)
	}

	return nil, fmt.Errorf("Endpoint type '%s' is not valid (must be 'public', 'admin' or 'internal')", endpointType)
}
<|endoftext|>


<commit_before>package tartrans

import (
	"archive/tar"
	"context"
	"crypto/sha512"
	"fmt"
	"io"
	"strings"

	. "github.com/polydawn/go-errcat"
	"github.com/polydawn/refmt/misc"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/go-timeless-api/util"
	"go.polydawn.net/rio/config"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/fs/osfs"
	"go.polydawn.net/rio/fsOp"
	"go.polydawn.net/rio/lib/treewalk"
	"go.polydawn.net/rio/transmat/mixins/cache"
	"go.polydawn.net/rio/transmat/mixins/filters"
	"go.polydawn.net/rio/transmat/mixins/fshash"
	"go.polydawn.net/rio/transmat/util"
)

var (
	_ rio.UnpackFunc = Unpack
)

func Unpack(
	ctx context.Context, // Long-running call.  Cancellable.
	wareID api.WareID, // What wareID to fetch for unpacking.
	path string, // Where to unpack the fileset (absolute path).
	filt api.FilesetFilters, // Optionally: filters we should apply while unpacking.
	placementMode rio.PlacementMode, // Optionally: a placement mode (default is "copy").
	warehouses []api.WarehouseAddr, // Warehouses we can try to fetch from.
	mon rio.Monitor, // Optionally: callbacks for progress monitoring.
) (_ api.WareID, err error) {
	if mon.Chan != nil {
		defer close(mon.Chan)
	}
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	if wareID.Type != PackType {
		return api.WareID{}, Errorf(rio.ErrUsage, "this transmat implementation only supports packtype %q (not %q)", PackType, wareID.Type)
	}
	if placementMode == "" {
		placementMode = rio.Placement_Copy
	}
	// Wrap the direct unpack func with cache behavior; call that.
	return cache.Lrn2Cache(
		osfs.New(config.GetCacheBasePath()),
		unpack,
	)(ctx, wareID, path, filt, placementMode, warehouses, mon)
}

func unpack(
	ctx context.Context,
	wareID api.WareID,
	path string,
	filt api.FilesetFilters,
	placementMode rio.PlacementMode,
	warehouses []api.WarehouseAddr,
	mon rio.Monitor,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	path2 := fs.MustAbsolutePath(path)
	filt2, err := apiutil.ProcessFilters(filt, apiutil.FilterPurposeUnpack)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrUsage, "invalid filter specification: %s", err)
	}

	// Pick a warehouse and get a reader.
	reader, err := PickReader(wareID, warehouses, false, mon)
	if err != nil {
		return api.WareID{}, err
	}
	defer reader.Close()

	// Construct filesystem wrapper to use for all our ops.
	afs := osfs.New(path2)

	// Extract.
	gotWare, err := unpackTar(ctx, afs, filt2, reader)
	if err != nil {
		return gotWare, err
	}

	// Check for hash mismatch before returning, because that IS an error,
	//  but also return the hash we got either way.
	if gotWare != wareID {
		return gotWare, ErrorDetailed(
			rio.ErrWareHashMismatch,
			fmt.Sprintf("hash mismatch: expected %q, got %q", wareID, gotWare),
			map[string]string{
				"expected": wareID.String(),
				"actual":   gotWare.String(),
			},
		)
	}
	return gotWare, nil
}

func unpackTar(
	ctx context.Context,
	afs fs.FS,
	filt apiutil.FilesetFilters,
	reader io.Reader,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Wrap input stream with decompression as necessary.
	//  Which kind of decompression to use can be autodetected by magic bytes.
	reader2, err := Decompress(reader)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar compression: %s", err)
	}

	// Convert the raw byte reader to a tar stream.
	tr := tar.NewReader(reader2)

	// Allocate bucket for keeping each metadata entry and content hash;
	// the full tree hash will be computed from this at the end.
	bucket := &fshash.MemoryBucket{}

	// Iterate over each tar entry, mutating filesystem as we go.
	for {
		fmeta := fs.Metadata{}
		thdr, err := tr.Next()

		// Check for done.
		if err == io.EOF {
			break // sucess!  end of archive.
		}
		if err != nil {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar: %s", err)
		}
		if ctx.Err() != nil {
			return api.WareID{}, Errorf(rio.ErrCancelled, "cancelled")
		}

		// Reshuffle metainfo to our default format.
		if err := TarHdrToMetadata(thdr, &fmeta); err != nil {
			return api.WareID{}, err
		}
		if strings.HasPrefix(fmeta.Name.String(), "..") {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar: paths that use '../' to leave the base dir are invalid")
		}

		// Apply filters.
		filters.Apply(filt, &fmeta)

		// Infer parents, if necessary.  The tar format allows implicit parent dirs.
		//
		// Note that if any of the implicitly conjured dirs is specified later, unpacking won't notice,
		// but bucket hashing iteration will (correctly) blow up for repeat entries.
		// It may well be possible to construct a tar like that, but it's already well established that
		// tars with repeated filenames are just asking for trouble and shall be rejected without
		// ceremony because they're just a ridiculous idea.
		for _, parent := range fmeta.Name.SplitParent() {
			_, err := afs.LStat(parent)
			// if it already exists, move along; if the error is anything interesting, let PlaceFile decide how to deal with it
			switch Category(err) {
			case nil:
				continue
			case fs.ErrNotExists:
				// pass
			default:
				break
			}
			// if we're missing a dir, conjure a node with defaulted values (same as we do for "./")
			conjuredFmeta := fshash.DefaultDirMetadata()
			conjuredFmeta.Name = parent
			if err := fsOp.PlaceFile(afs, conjuredFmeta, nil, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(conjuredFmeta, nil)
		}

		// Place the file.
		switch fmeta.Type {
		case fs.Type_File:
			reader := &util.HashingReader{tr, sha512.New384()}
			if err := fsOp.PlaceFile(afs, fmeta, reader, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(fmeta, reader.Hasher.Sum(nil))
		default:
			if err := fsOp.PlaceFile(afs, fmeta, nil, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(fmeta, nil)
		}
	}

	// Cleanup dir times with a post-order traversal over the bucket.
	//  Files and dirs placed inside dirs cause the parent's mtime to update, so we have to re-pave them.
	if err := treewalk.Walk(bucket.Iterator(), nil, func(node treewalk.Node) error {
		record := node.(fshash.RecordIterator).Record()
		if record.Metadata.Type != fs.Type_Dir {
			return nil
		}
		return afs.SetTimesNano(record.Metadata.Name, record.Metadata.Mtime, fs.DefaultAtime)
	}); err != nil {
		return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
	}

	// Hash the thing!
	hash := fshash.HashBucket(bucket, sha512.New384)

	return api.WareID{"tar", misc.Base58Encode(hash)}, nil
}
<commit_msg>transmat/tar: remember dirs handled during unpack.<commit_after>package tartrans

import (
	"archive/tar"
	"context"
	"crypto/sha512"
	"fmt"
	"io"
	"strings"

	. "github.com/polydawn/go-errcat"
	"github.com/polydawn/refmt/misc"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/go-timeless-api/util"
	"go.polydawn.net/rio/config"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/fs/osfs"
	"go.polydawn.net/rio/fsOp"
	"go.polydawn.net/rio/lib/treewalk"
	"go.polydawn.net/rio/transmat/mixins/cache"
	"go.polydawn.net/rio/transmat/mixins/filters"
	"go.polydawn.net/rio/transmat/mixins/fshash"
	"go.polydawn.net/rio/transmat/util"
)

var (
	_ rio.UnpackFunc = Unpack
)

func Unpack(
	ctx context.Context, // Long-running call.  Cancellable.
	wareID api.WareID, // What wareID to fetch for unpacking.
	path string, // Where to unpack the fileset (absolute path).
	filt api.FilesetFilters, // Optionally: filters we should apply while unpacking.
	placementMode rio.PlacementMode, // Optionally: a placement mode (default is "copy").
	warehouses []api.WarehouseAddr, // Warehouses we can try to fetch from.
	mon rio.Monitor, // Optionally: callbacks for progress monitoring.
) (_ api.WareID, err error) {
	if mon.Chan != nil {
		defer close(mon.Chan)
	}
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	if wareID.Type != PackType {
		return api.WareID{}, Errorf(rio.ErrUsage, "this transmat implementation only supports packtype %q (not %q)", PackType, wareID.Type)
	}
	if placementMode == "" {
		placementMode = rio.Placement_Copy
	}
	// Wrap the direct unpack func with cache behavior; call that.
	return cache.Lrn2Cache(
		osfs.New(config.GetCacheBasePath()),
		unpack,
	)(ctx, wareID, path, filt, placementMode, warehouses, mon)
}

func unpack(
	ctx context.Context,
	wareID api.WareID,
	path string,
	filt api.FilesetFilters,
	placementMode rio.PlacementMode,
	warehouses []api.WarehouseAddr,
	mon rio.Monitor,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Sanitize arguments.
	path2 := fs.MustAbsolutePath(path)
	filt2, err := apiutil.ProcessFilters(filt, apiutil.FilterPurposeUnpack)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrUsage, "invalid filter specification: %s", err)
	}

	// Pick a warehouse and get a reader.
	reader, err := PickReader(wareID, warehouses, false, mon)
	if err != nil {
		return api.WareID{}, err
	}
	defer reader.Close()

	// Construct filesystem wrapper to use for all our ops.
	afs := osfs.New(path2)

	// Extract.
	gotWare, err := unpackTar(ctx, afs, filt2, reader)
	if err != nil {
		return gotWare, err
	}

	// Check for hash mismatch before returning, because that IS an error,
	//  but also return the hash we got either way.
	if gotWare != wareID {
		return gotWare, ErrorDetailed(
			rio.ErrWareHashMismatch,
			fmt.Sprintf("hash mismatch: expected %q, got %q", wareID, gotWare),
			map[string]string{
				"expected": wareID.String(),
				"actual":   gotWare.String(),
			},
		)
	}
	return gotWare, nil
}

func unpackTar(
	ctx context.Context,
	afs fs.FS,
	filt apiutil.FilesetFilters,
	reader io.Reader,
) (_ api.WareID, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	// Wrap input stream with decompression as necessary.
	//  Which kind of decompression to use can be autodetected by magic bytes.
	reader2, err := Decompress(reader)
	if err != nil {
		return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar compression: %s", err)
	}

	// Convert the raw byte reader to a tar stream.
	tr := tar.NewReader(reader2)

	// Allocate bucket for keeping each metadata entry and content hash;
	// the full tree hash will be computed from this at the end.
	bucket := &fshash.MemoryBucket{}

	// Also allocate a map for keeping records of which dirs we've created.
	// This is necessary for correct bookkeepping in the face of the tar format's
	// allowance for implicit parent dirs.
	dirs := map[fs.RelPath]struct{}{}

	// Iterate over each tar entry, mutating filesystem as we go.
	for {
		fmeta := fs.Metadata{}
		thdr, err := tr.Next()

		// Check for done.
		if err == io.EOF {
			break // sucess!  end of archive.
		}
		if err != nil {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar: %s", err)
		}
		if ctx.Err() != nil {
			return api.WareID{}, Errorf(rio.ErrCancelled, "cancelled")
		}

		// Reshuffle metainfo to our default format.
		if err := TarHdrToMetadata(thdr, &fmeta); err != nil {
			return api.WareID{}, err
		}
		if strings.HasPrefix(fmeta.Name.String(), "..") {
			return api.WareID{}, Errorf(rio.ErrWareCorrupt, "corrupt tar: paths that use '../' to leave the base dir are invalid")
		}

		// Apply filters.
		filters.Apply(filt, &fmeta)

		// Infer parents, if necessary.  The tar format allows implicit parent dirs.
		//
		// Note that if any of the implicitly conjured dirs is specified later, unpacking won't notice,
		// but bucket hashing iteration will (correctly) blow up for repeat entries.
		// It may well be possible to construct a tar like that, but it's already well established that
		// tars with repeated filenames are just asking for trouble and shall be rejected without
		// ceremony because they're just a ridiculous idea.
		for _, parent := range fmeta.Name.SplitParent() {
			// If we already initialized this parent, superb; move along.
			if _, exists := dirs[parent]; exists {
				break
			}
			// If we're missing a dir, conjure a node with defaulted values.
			conjuredFmeta := fshash.DefaultDirMetadata()
			conjuredFmeta.Name = parent
			filters.Apply(filt, &conjuredFmeta)
			dirs[conjuredFmeta.Name] = struct{}{}
			if err := fsOp.PlaceFile(afs, conjuredFmeta, nil, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(conjuredFmeta, nil)
		}

		// Place the file.
		switch fmeta.Type {
		case fs.Type_File:
			reader := &util.HashingReader{tr, sha512.New384()}
			if err := fsOp.PlaceFile(afs, fmeta, reader, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(fmeta, reader.Hasher.Sum(nil))
		case fs.Type_Dir:
			dirs[fmeta.Name] = struct{}{}
			fallthrough
		default:
			if err := fsOp.PlaceFile(afs, fmeta, nil, false); err != nil {
				return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
			}
			bucket.AddRecord(fmeta, nil)
		}
	}

	// Cleanup dir times with a post-order traversal over the bucket.
	//  Files and dirs placed inside dirs cause the parent's mtime to update, so we have to re-pave them.
	if err := treewalk.Walk(bucket.Iterator(), nil, func(node treewalk.Node) error {
		record := node.(fshash.RecordIterator).Record()
		if record.Metadata.Type != fs.Type_Dir {
			return nil
		}
		return afs.SetTimesNano(record.Metadata.Name, record.Metadata.Mtime, fs.DefaultAtime)
	}); err != nil {
		return api.WareID{}, Errorf(rio.ErrInoperablePath, "error while unpacking: %s", err)
	}

	// Hash the thing!
	hash := fshash.HashBucket(bucket, sha512.New384)

	return api.WareID{"tar", misc.Base58Encode(hash)}, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The Mangos Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tlstcp implements the TLS over TCP transport for mangos.
package tlstcp

import (
	"crypto/tls"
	"net"
	"time"

	"nanomsg.org/go-mangos"
)

type options map[string]interface{}

func (o options) get(name string) (interface{}, error) {
	if v, ok := o[name]; ok {
		return v, nil
	}
	return nil, mangos.ErrBadOption
}

func (o options) set(name string, val interface{}) error {
	switch name {
	case mangos.OptionTLSConfig:
		switch v := val.(type) {
		case *tls.Config:
			o[name] = v
		default:
			return mangos.ErrBadValue
		}
	default:
		return mangos.ErrBadOption
	}
	return nil
}

func (o options) configTCP(conn *net.TCPConn) error {
	if v, ok := o[mangos.OptionNoDelay]; ok {
		if err := conn.SetNoDelay(v.(bool)); err != nil {
			return err
		}
	}
	if v, ok := o[mangos.OptionKeepAlive]; ok {
		if err := conn.SetKeepAlive(v.(bool)); err != nil {
			return err
		}
	}
	if v, ok := o[mangos.OptionKeepAliveTime]; ok {
		if err := conn.SetKeepAlivePeriod(v.(time.Duration)); err != nil {
			return err
		}
	}

	return nil
}

func newOptions(t *tlsTran) options {
	o := make(map[string]interface{})
	o[mangos.OptionTLSConfig] = t.config
	return options(o)
}

type dialer struct {
	addr string
	sock mangos.Socket
	opts options
}

func (d *dialer) Dial() (_ mangos.Pipe, err error) {
	var (
		addr   *net.TCPAddr
		config *tls.Config
	)

	if addr, err = mangos.ResolveTCPAddr(d.addr); err != nil {
		return nil, err
	}

	tconn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return nil, err
	}
	if err = d.opts.configTCP(tconn); err != nil {
		tconn.Close()
		return nil, err
	}
	if v, ok := d.opts[mangos.OptionTLSConfig]; ok {
		config = v.(*tls.Config)
	}
	conn := tls.Client(tconn, config)
	if err = conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}
	return mangos.NewConnPipe(conn, d.sock,
		mangos.PropTLSConnState, conn.ConnectionState())
}

func (d *dialer) SetOption(n string, v interface{}) error {
	return d.opts.set(n, v)
}

func (d *dialer) GetOption(n string) (interface{}, error) {
	return d.opts.get(n)
}

type listener struct {
	sock     mangos.Socket
	addr     *net.TCPAddr
	bound    net.Addr
	listener *net.TCPListener
	opts     options
	config   *tls.Config
}

func (l *listener) Listen() error {
	var err error
	v, ok := l.opts[mangos.OptionTLSConfig]
	if !ok {
		return mangos.ErrTLSNoConfig
	}
	l.config = v.(*tls.Config)
	if l.config == nil {
		return mangos.ErrTLSNoConfig
	}
	if l.config.Certificates == nil || len(l.config.Certificates) == 0 {
		return mangos.ErrTLSNoCert
	}

	if l.listener, err = net.ListenTCP("tcp", l.addr); err != nil {
		return err
	}

	l.bound = l.listener.Addr()

	return nil
}

func (l *listener) Address() string {
	if b := l.bound; b != nil {
		return "tls+tcp://" + b.String()
	}
	return "tls+tcp://" + l.addr.String()
}

func (l *listener) Accept() (mangos.Pipe, error) {

	tconn, err := l.listener.AcceptTCP()
	if err != nil {
		return nil, err
	}

	if err = l.opts.configTCP(tconn); err != nil {
		tconn.Close()
		return nil, err
	}

	conn := tls.Server(tconn, l.config)
	if err = conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}
	return mangos.NewConnPipe(conn, l.sock,
		mangos.PropTLSConnState, conn.ConnectionState())
}

func (l *listener) Close() error {
	l.listener.Close()
	return nil
}

func (l *listener) SetOption(n string, v interface{}) error {
	return l.opts.set(n, v)
}

func (l *listener) GetOption(n string) (interface{}, error) {
	return l.opts.get(n)
}

type tlsTran struct {
	config    *tls.Config
	localAddr net.Addr
}

func (t *tlsTran) Scheme() string {
	return "tls+tcp"
}

func (t *tlsTran) NewDialer(addr string, sock mangos.Socket) (mangos.PipeDialer, error) {
	var err error

	if addr, err = mangos.StripScheme(t, addr); err != nil {
		return nil, err
	}

	// check to ensure the provided addr resolves correctly.
	if _, err = mangos.ResolveTCPAddr(addr); err != nil {
		return nil, err
	}

	d := &dialer{sock: sock, opts: newOptions(t), addr: addr}

	return d, nil
}

// NewListener implements the Transport NewAccepter method.
func (t *tlsTran) NewListener(addr string, sock mangos.Socket) (mangos.PipeListener, error) {
	var err error
	l := &listener{sock: sock, opts: newOptions(t)}

	if addr, err = mangos.StripScheme(t, addr); err != nil {
		return nil, err
	}
	if l.addr, err = mangos.ResolveTCPAddr(addr); err != nil {
		return nil, err
	}

	return l, nil
}

// NewTransport allocates a new inproc transport.
func NewTransport() mangos.Transport {
	return &tlsTran{}
}
<commit_msg>tls+tcp: support underlying TCP transport options (#346)<commit_after>// Copyright 2018 The Mangos Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tlstcp implements the TLS over TCP transport for mangos.
package tlstcp

import (
	"crypto/tls"
	"net"
	"time"

	"nanomsg.org/go-mangos"
)

type options map[string]interface{}

func (o options) get(name string) (interface{}, error) {
	if v, ok := o[name]; ok {
		return v, nil
	}
	return nil, mangos.ErrBadOption
}

func (o options) set(name string, val interface{}) error {
	switch name {
	case mangos.OptionNoDelay:
		fallthrough
	case mangos.OptionKeepAlive:
		switch v := val.(type) {
		case bool:
			o[name] = v
			return nil
		default:
			return mangos.ErrBadValue
		}
	case mangos.OptionKeepAliveTime:
		switch v := val.(type) {
		case time.Duration:
			if v.Nanoseconds() >= 0 {
				o[name] = v
				return nil
			} else {
				return mangos.ErrBadValue
			}
		default:
			return mangos.ErrBadValue
		}
	case mangos.OptionTLSConfig:
		switch v := val.(type) {
		case *tls.Config:
			o[name] = v
		default:
			return mangos.ErrBadValue
		}
	default:
		return mangos.ErrBadOption
	}
	return nil
}

func (o options) configTCP(conn *net.TCPConn) error {
	if v, ok := o[mangos.OptionNoDelay]; ok {
		if err := conn.SetNoDelay(v.(bool)); err != nil {
			return err
		}
	}
	if v, ok := o[mangos.OptionKeepAlive]; ok {
		if err := conn.SetKeepAlive(v.(bool)); err != nil {
			return err
		}
	}
	if v, ok := o[mangos.OptionKeepAliveTime]; ok {
		if err := conn.SetKeepAlivePeriod(v.(time.Duration)); err != nil {
			return err
		}
	}

	return nil
}

func newOptions(t *tlsTran) options {
	o := make(map[string]interface{})
	o[mangos.OptionTLSConfig] = t.config
	return options(o)
}

type dialer struct {
	addr string
	sock mangos.Socket
	opts options
}

func (d *dialer) Dial() (_ mangos.Pipe, err error) {
	var (
		addr   *net.TCPAddr
		config *tls.Config
	)

	if addr, err = mangos.ResolveTCPAddr(d.addr); err != nil {
		return nil, err
	}

	tconn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return nil, err
	}
	if err = d.opts.configTCP(tconn); err != nil {
		tconn.Close()
		return nil, err
	}
	if v, ok := d.opts[mangos.OptionTLSConfig]; ok {
		config = v.(*tls.Config)
	}
	conn := tls.Client(tconn, config)
	if err = conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}
	return mangos.NewConnPipe(conn, d.sock,
		mangos.PropTLSConnState, conn.ConnectionState())
}

func (d *dialer) SetOption(n string, v interface{}) error {
	return d.opts.set(n, v)
}

func (d *dialer) GetOption(n string) (interface{}, error) {
	return d.opts.get(n)
}

type listener struct {
	sock     mangos.Socket
	addr     *net.TCPAddr
	bound    net.Addr
	listener *net.TCPListener
	opts     options
	config   *tls.Config
}

func (l *listener) Listen() error {
	var err error
	v, ok := l.opts[mangos.OptionTLSConfig]
	if !ok {
		return mangos.ErrTLSNoConfig
	}
	l.config = v.(*tls.Config)
	if l.config == nil {
		return mangos.ErrTLSNoConfig
	}
	if l.config.Certificates == nil || len(l.config.Certificates) == 0 {
		return mangos.ErrTLSNoCert
	}

	if l.listener, err = net.ListenTCP("tcp", l.addr); err != nil {
		return err
	}

	l.bound = l.listener.Addr()

	return nil
}

func (l *listener) Address() string {
	if b := l.bound; b != nil {
		return "tls+tcp://" + b.String()
	}
	return "tls+tcp://" + l.addr.String()
}

func (l *listener) Accept() (mangos.Pipe, error) {

	tconn, err := l.listener.AcceptTCP()
	if err != nil {
		return nil, err
	}

	if err = l.opts.configTCP(tconn); err != nil {
		tconn.Close()
		return nil, err
	}

	conn := tls.Server(tconn, l.config)
	if err = conn.Handshake(); err != nil {
		conn.Close()
		return nil, err
	}
	return mangos.NewConnPipe(conn, l.sock,
		mangos.PropTLSConnState, conn.ConnectionState())
}

func (l *listener) Close() error {
	l.listener.Close()
	return nil
}

func (l *listener) SetOption(n string, v interface{}) error {
	return l.opts.set(n, v)
}

func (l *listener) GetOption(n string) (interface{}, error) {
	return l.opts.get(n)
}

type tlsTran struct {
	config    *tls.Config
	localAddr net.Addr
}

func (t *tlsTran) Scheme() string {
	return "tls+tcp"
}

func (t *tlsTran) NewDialer(addr string, sock mangos.Socket) (mangos.PipeDialer, error) {
	var err error

	if addr, err = mangos.StripScheme(t, addr); err != nil {
		return nil, err
	}

	// check to ensure the provided addr resolves correctly.
	if _, err = mangos.ResolveTCPAddr(addr); err != nil {
		return nil, err
	}

	d := &dialer{sock: sock, opts: newOptions(t), addr: addr}

	return d, nil
}

// NewListener implements the Transport NewAccepter method.
func (t *tlsTran) NewListener(addr string, sock mangos.Socket) (mangos.PipeListener, error) {
	var err error
	l := &listener{sock: sock, opts: newOptions(t)}

	if addr, err = mangos.StripScheme(t, addr); err != nil {
		return nil, err
	}
	if l.addr, err = mangos.ResolveTCPAddr(addr); err != nil {
		return nil, err
	}

	return l, nil
}

// NewTransport allocates a new inproc transport.
func NewTransport() mangos.Transport {
	return &tlsTran{}
}
<|endoftext|>