<commit_before>// Copyright 2018 the Service Broker Project 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 brokerpak

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"text/tabwriter"

	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/client"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/generator"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/providers/tf"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils/stream"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils/ziputil"
)

// Init initializes a new brokerpak in the given directory with an example manifest and service definition.
func Init(directory string) error {
	exampleManifest := NewExampleManifest()
	if err := stream.Copy(stream.FromYaml(exampleManifest), stream.ToFile(directory, manifestName)); err != nil {
		return err
	}

	for _, path := range exampleManifest.ServiceDefinitions {
		if err := stream.Copy(stream.FromYaml(tf.NewExampleTfServiceDefinition()), stream.ToFile(directory, path)); err != nil {
			return err
		}
	}

	return nil
}

// Pack creates a new brokerpak from the given directory which MUST contain a
// manifest.yml file. If the pack was successful, the returned string will be
// the path to the created brokerpak.
func Pack(directory string) (string, error) {
	abs, err := filepath.Abs(directory)
	if err != nil {
		return "", err
	}
	manifestPath := filepath.Join(directory, manifestName)
	manifest := &Manifest{}
	if err := stream.Copy(stream.FromFile(manifestPath), stream.ToYaml(manifest)); err != nil {
		return "", err
	}

	packname := filepath.Base(abs) + ".brokerpak"
	return packname, manifest.Pack(directory, packname)
}

// Info writes out human-readable information about the brokerpak.
func Info(pack string) error {
	return finfo(pack, os.Stdout)
}

func finfo(pack string, out io.Writer) error {
	brokerPak, err := OpenBrokerPak(pack)
	if err != nil {
		return err
	}

	mf, err := brokerPak.Manifest()
	if err != nil {
		return err
	}

	services, err := brokerPak.Services()
	if err != nil {
		return err
	}

	// Pack information
	fmt.Fprintln(out, "Information")
	{
		w := cmdTabWriter(out)
		fmt.Fprintf(w, "format\t%d\n", mf.PackVersion)
		fmt.Fprintf(w, "name\t%s\n", mf.Name)
		fmt.Fprintf(w, "version\t%s\n", mf.Version)
		fmt.Fprintln(w, "platforms")
		for _, arch := range mf.Platforms {
			fmt.Fprintf(w, "\t%s\n", arch.String())
		}
		fmt.Fprintln(w, "metadata")
		for k, v := range mf.Metadata {
			fmt.Fprintf(w, "\t%s\t%s\n", k, v)
		}

		w.Flush()
		fmt.Fprintln(out)
	}

	{
		fmt.Fprintln(out, "Parameters")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "NAME\tDESCRIPTION")
		for _, param := range mf.Parameters {
			fmt.Fprintf(w, "%s\t%s\n", param.Name, param.Description)
		}
		w.Flush()
		fmt.Fprintln(out)
	}
	{
		fmt.Fprintln(out, "Dependencies")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "NAME\tVERSION\tSOURCE")
		for _, resource := range mf.TerraformResources {
			fmt.Fprintf(w, "%s\t%s\t%s\n", resource.Name, resource.Version, resource.Source)
		}
		w.Flush()
		fmt.Fprintln(out)
	}

	{
		fmt.Fprintln(out, "Services")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "ID\tNAME\tDESCRIPTION\tPLANS")
		for _, svc := range services {
			fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.Id, svc.Name, svc.Description, len(svc.Plans))
		}
		w.Flush()
		fmt.Println()
	}

	fmt.Fprintln(out, "Contents")
	ziputil.List(&brokerPak.contents.Reader, out)
	fmt.Fprintln(out)

	return nil
}

func cmdTabWriter(out io.Writer) *tabwriter.Writer {
	// args: output, minwidth, tabwidth, padding, padchar, flags
	return tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.StripEscape)
}

// Validate checks the brokerpak for syntactic and limited semantic errors.
func Validate(pack string) error {
	brokerPak, err := OpenBrokerPak(pack)
	if err != nil {
		return err
	}
	defer brokerPak.Close()

	return brokerPak.Validate()
}

// RegisterAll fetches all brokerpaks from the settings file and registers them
// with the given registry.
func RegisterAll(registry broker.BrokerRegistry) error {
	pakConfig, err := NewServerConfigFromEnv()
	if err != nil {
		return err
	}

	return NewRegistrar(pakConfig).Register(registry)
}

// RunExamples executes the examples from a brokerpak.
func RunExamples(pack string) error {
	registry, err := registryFromLocalBrokerpak(pack)
	if err != nil {
		return err
	}

	apiClient, err := client.NewClientFromEnv()
	if err != nil {
		return err
	}

	return client.RunExamplesForService(registry, apiClient, "", "")
}

// Docs generates the markdown usage docs for the given pack and writes them to stdout.
func Docs(pack string) error {
	registry, err := registryFromLocalBrokerpak(pack)
	if err != nil {
		return err
	}

	fmt.Println(generator.CatalogDocumentation(registry))
	return nil
}

func registryFromLocalBrokerpak(packPath string) (broker.BrokerRegistry, error) {
	config := newLocalFileServerConfig(packPath)

	registry := broker.BrokerRegistry{}
	if err := NewRegistrar(config).Register(registry); err != nil {
		return nil, err
	}

	return registry, nil
}
<commit_msg>generate the brokerpak name based on its version and manifest name (#493)<commit_after>// Copyright 2018 the Service Broker Project 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 brokerpak

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"text/tabwriter"

	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/client"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/generator"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/providers/tf"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils/stream"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils/ziputil"
)

// Init initializes a new brokerpak in the given directory with an example manifest and service definition.
func Init(directory string) error {
	exampleManifest := NewExampleManifest()
	if err := stream.Copy(stream.FromYaml(exampleManifest), stream.ToFile(directory, manifestName)); err != nil {
		return err
	}

	for _, path := range exampleManifest.ServiceDefinitions {
		if err := stream.Copy(stream.FromYaml(tf.NewExampleTfServiceDefinition()), stream.ToFile(directory, path)); err != nil {
			return err
		}
	}

	return nil
}

// Pack creates a new brokerpak from the given directory which MUST contain a
// manifest.yml file. If the pack was successful, the returned string will be
// the path to the created brokerpak.
func Pack(directory string) (string, error) {
	manifestPath := filepath.Join(directory, manifestName)
	manifest := &Manifest{}
	if err := stream.Copy(stream.FromFile(manifestPath), stream.ToYaml(manifest)); err != nil {
		return "", err
	}

	packname := fmt.Sprintf("%s-%s.brokerpak", manifest.Name, manifest.Version)
	return packname, manifest.Pack(directory, packname)
}

// Info writes out human-readable information about the brokerpak.
func Info(pack string) error {
	return finfo(pack, os.Stdout)
}

func finfo(pack string, out io.Writer) error {
	brokerPak, err := OpenBrokerPak(pack)
	if err != nil {
		return err
	}

	mf, err := brokerPak.Manifest()
	if err != nil {
		return err
	}

	services, err := brokerPak.Services()
	if err != nil {
		return err
	}

	// Pack information
	fmt.Fprintln(out, "Information")
	{
		w := cmdTabWriter(out)
		fmt.Fprintf(w, "format\t%d\n", mf.PackVersion)
		fmt.Fprintf(w, "name\t%s\n", mf.Name)
		fmt.Fprintf(w, "version\t%s\n", mf.Version)
		fmt.Fprintln(w, "platforms")
		for _, arch := range mf.Platforms {
			fmt.Fprintf(w, "\t%s\n", arch.String())
		}
		fmt.Fprintln(w, "metadata")
		for k, v := range mf.Metadata {
			fmt.Fprintf(w, "\t%s\t%s\n", k, v)
		}

		w.Flush()
		fmt.Fprintln(out)
	}

	{
		fmt.Fprintln(out, "Parameters")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "NAME\tDESCRIPTION")
		for _, param := range mf.Parameters {
			fmt.Fprintf(w, "%s\t%s\n", param.Name, param.Description)
		}
		w.Flush()
		fmt.Fprintln(out)
	}
	{
		fmt.Fprintln(out, "Dependencies")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "NAME\tVERSION\tSOURCE")
		for _, resource := range mf.TerraformResources {
			fmt.Fprintf(w, "%s\t%s\t%s\n", resource.Name, resource.Version, resource.Source)
		}
		w.Flush()
		fmt.Fprintln(out)
	}

	{
		fmt.Fprintln(out, "Services")
		w := cmdTabWriter(out)
		fmt.Fprintln(w, "ID\tNAME\tDESCRIPTION\tPLANS")
		for _, svc := range services {
			fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.Id, svc.Name, svc.Description, len(svc.Plans))
		}
		w.Flush()
		fmt.Println()
	}

	fmt.Fprintln(out, "Contents")
	ziputil.List(&brokerPak.contents.Reader, out)
	fmt.Fprintln(out)

	return nil
}

func cmdTabWriter(out io.Writer) *tabwriter.Writer {
	// args: output, minwidth, tabwidth, padding, padchar, flags
	return tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.StripEscape)
}

// Validate checks the brokerpak for syntactic and limited semantic errors.
func Validate(pack string) error {
	brokerPak, err := OpenBrokerPak(pack)
	if err != nil {
		return err
	}
	defer brokerPak.Close()

	return brokerPak.Validate()
}

// RegisterAll fetches all brokerpaks from the settings file and registers them
// with the given registry.
func RegisterAll(registry broker.BrokerRegistry) error {
	pakConfig, err := NewServerConfigFromEnv()
	if err != nil {
		return err
	}

	return NewRegistrar(pakConfig).Register(registry)
}

// RunExamples executes the examples from a brokerpak.
func RunExamples(pack string) error {
	registry, err := registryFromLocalBrokerpak(pack)
	if err != nil {
		return err
	}

	apiClient, err := client.NewClientFromEnv()
	if err != nil {
		return err
	}

	return client.RunExamplesForService(registry, apiClient, "", "")
}

// Docs generates the markdown usage docs for the given pack and writes them to stdout.
func Docs(pack string) error {
	registry, err := registryFromLocalBrokerpak(pack)
	if err != nil {
		return err
	}

	fmt.Println(generator.CatalogDocumentation(registry))
	return nil
}

func registryFromLocalBrokerpak(packPath string) (broker.BrokerRegistry, error) {
	config := newLocalFileServerConfig(packPath)

	registry := broker.BrokerRegistry{}
	if err := NewRegistrar(config).Register(registry); err != nil {
		return nil, err
	}

	return registry, nil
}
<|endoftext|>


<commit_before>package gateway

import (
	"bytes"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"text/template"
)

var (
	nginxTemplateData = `
pid {{ .NGINXConfig.PIDFile }};

events {
    worker_connections 512;
}

http {
    server {
        listen 7332;
        location /health {
            return 200 'Healthy!';
        }
    }
{{ range $svc := .ServiceMap.Services }}
    server {
        listen {{ $svc.ListenPort }};
        location / {
            proxy_pass http://{{ $svc.Namespace }}__{{ $svc.Name }};
        }
    }
    upstream {{ $svc.Namespace }}__{{ $svc.Name }} {
{{ range $ep := $svc.Endpoints }}
        server {{ $ep.IP }};  # {{ $ep.Name }}
{{- end }}
    }
{{ end }}
}
`

	nginxTemplate = template.Must(template.New("nginx").Parse(nginxTemplateData))

	DefaultNGINXConfig = NGINXConfig{
		ConfigFile: "/etc/nginx/nginx.conf",
		PIDFile:    "/var/run/nginx.pid",
	}
)

const (
	nginxStatusRunning = "running"
	nginxStatusStopped = "stopped"
	nginxStatusUnknown = "unknown"
)

type NGINXConfig struct {
	ConfigFile string
	PIDFile    string
}

type NGINXManager interface {
	Status() (string, error)
	WriteConfig(*ServiceMap) error
	Start() error
	Reload() error
}

func newNGINXManager() NGINXManager {
	return &nginxManager{cfg: DefaultNGINXConfig}
}

type nginxManager struct {
	cfg NGINXConfig
}

func (n *nginxManager) Status() (string, error) {
	log.Printf("Checking status")
	if _, err := os.Stat(n.cfg.PIDFile); err != nil {
		if os.IsNotExist(err) {
			return nginxStatusStopped, nil
		} else {
			return nginxStatusUnknown, err
		}
	}

	return nginxStatusRunning, nil
}

func (n *nginxManager) WriteConfig(sm *ServiceMap) error {
	cfg, err := renderConfig(&n.cfg, sm)
	if err != nil {
		return err
	}
	if err := ioutil.WriteFile(n.cfg.ConfigFile, cfg, os.FileMode(0644)); err != nil {
		return err
	}
	return nil
}

func (n *nginxManager) assertConfigOK() error {
	return n.run("-t")
}

func (n *nginxManager) Start() error {
	log.Printf("Starting nginx")
	return n.run()
}

func (n *nginxManager) Reload() error {
	log.Printf("Reloading nginx")
	return n.run("-s", "reload")
}

func (n *nginxManager) run(args ...string) error {
	args = append([]string{"-c", n.cfg.ConfigFile}, args...)
	log.Printf("Calling run on nginx with args: %q", args)
	output, err := exec.Command("nginx", args...).CombinedOutput()
	if err != nil {
		log.Printf("nginx command failed w/ output:\n%s", output)
		return err
	}
	return nil
}

func renderConfig(cfg *NGINXConfig, sm *ServiceMap) ([]byte, error) {
	log.Printf("Rendering config")
	config := struct {
		*NGINXConfig
		*ServiceMap
	}{
		NGINXConfig: cfg,
		ServiceMap:  sm,
	}

	var buf bytes.Buffer
	if err := nginxTemplate.Execute(&buf, config); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func newLoggingNGINXManager() NGINXManager {
	return &loggingNGINXManager{status: nginxStatusStopped}
}

type loggingNGINXManager struct {
	status string
}

func (l *loggingNGINXManager) Status() (string, error) {
	log.Printf("called NGINXManager.Status()")
	return l.status, nil
}

func (l *loggingNGINXManager) Start() error {
	log.Printf("called NGINXManager.Start()")
	l.status = nginxStatusRunning
	return nil
}

func (l *loggingNGINXManager) Reload() error {
	log.Printf("called NGINXManager.Reload()")
	return nil
}

func (l *loggingNGINXManager) WriteConfig(sm *ServiceMap) error {
	log.Printf("called NGINXManager.WriteConfig(*ServiceMap) w/ %+v", sm)
	return nil
}
<commit_msg>Logging success as well.<commit_after>package gateway

import (
	"bytes"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"text/template"
)

var (
	nginxTemplateData = `
pid {{ .NGINXConfig.PIDFile }};

events {
    worker_connections 512;
}

http {
    server {
        listen 7332;
        location /health {
            return 200 'Healthy!';
        }
    }
{{ range $svc := .ServiceMap.Services }}
    server {
        listen {{ $svc.ListenPort }};
        location / {
            proxy_pass http://{{ $svc.Namespace }}__{{ $svc.Name }};
        }
    }
    upstream {{ $svc.Namespace }}__{{ $svc.Name }} {
{{ range $ep := $svc.Endpoints }}
        server {{ $ep.IP }};  # {{ $ep.Name }}
{{- end }}
    }
{{ end }}
}
`

	nginxTemplate = template.Must(template.New("nginx").Parse(nginxTemplateData))

	DefaultNGINXConfig = NGINXConfig{
		ConfigFile: "/etc/nginx/nginx.conf",
		PIDFile:    "/var/run/nginx.pid",
	}
)

const (
	nginxStatusRunning = "running"
	nginxStatusStopped = "stopped"
	nginxStatusUnknown = "unknown"
)

type NGINXConfig struct {
	ConfigFile string
	PIDFile    string
}

type NGINXManager interface {
	Status() (string, error)
	WriteConfig(*ServiceMap) error
	Start() error
	Reload() error
}

func newNGINXManager() NGINXManager {
	return &nginxManager{cfg: DefaultNGINXConfig}
}

type nginxManager struct {
	cfg NGINXConfig
}

func (n *nginxManager) Status() (string, error) {
	log.Printf("Checking status")
	if _, err := os.Stat(n.cfg.PIDFile); err != nil {
		if os.IsNotExist(err) {
			return nginxStatusStopped, nil
		} else {
			return nginxStatusUnknown, err
		}
	}

	return nginxStatusRunning, nil
}

func (n *nginxManager) WriteConfig(sm *ServiceMap) error {
	cfg, err := renderConfig(&n.cfg, sm)
	if err != nil {
		return err
	}
	if err := ioutil.WriteFile(n.cfg.ConfigFile, cfg, os.FileMode(0644)); err != nil {
		return err
	}
	return nil
}

func (n *nginxManager) assertConfigOK() error {
	return n.run("-t")
}

func (n *nginxManager) Start() error {
	log.Printf("Starting nginx")
	return n.run()
}

func (n *nginxManager) Reload() error {
	log.Printf("Reloading nginx")
	return n.run("-s", "reload")
}

func (n *nginxManager) run(args ...string) error {
	args = append([]string{"-c", n.cfg.ConfigFile}, args...)
	log.Printf("Calling run on nginx with args: %q", args)
	output, err := exec.Command("nginx", args...).CombinedOutput()
	if err != nil {
		log.Printf("nginx command failed w/ output:\n%s", output)
		return err
	} else {
		log.Printf("nginx command success w/ output:\n%s", output)
	}
	return nil
}

func renderConfig(cfg *NGINXConfig, sm *ServiceMap) ([]byte, error) {
	log.Printf("Rendering config")
	config := struct {
		*NGINXConfig
		*ServiceMap
	}{
		NGINXConfig: cfg,
		ServiceMap:  sm,
	}

	var buf bytes.Buffer
	if err := nginxTemplate.Execute(&buf, config); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func newLoggingNGINXManager() NGINXManager {
	return &loggingNGINXManager{status: nginxStatusStopped}
}

type loggingNGINXManager struct {
	status string
}

func (l *loggingNGINXManager) Status() (string, error) {
	log.Printf("called NGINXManager.Status()")
	return l.status, nil
}

func (l *loggingNGINXManager) Start() error {
	log.Printf("called NGINXManager.Start()")
	l.status = nginxStatusRunning
	return nil
}

func (l *loggingNGINXManager) Reload() error {
	log.Printf("called NGINXManager.Reload()")
	return nil
}

func (l *loggingNGINXManager) WriteConfig(sm *ServiceMap) error {
	log.Printf("called NGINXManager.WriteConfig(*ServiceMap) w/ %+v", sm)
	return nil
}
<|endoftext|>


<commit_before>package modules

import (
	"strings"

	"github.com/pajbot/pajbot2/pkg"
	"github.com/pajbot/pajbot2/pkg/twitchactions"
	"github.com/pajbot/utils"
)

func init() {
	Register("value", func() pkg.ModuleSpec {
		return &moduleSpec{
			id:               "value",
			name:             "Value",
			enabledByDefault: false,

			maker: newValue,

			parameters: map[string]pkg.ModuleParameterSpec{
				"A": func() pkg.ModuleParameter {
					return newFloatParameter(parameterSpec{
						Description:  "A kjdfghk jdfhgkj dfg",
						DefaultValue: float32(3.0),
					})
				},
				"B": func() pkg.ModuleParameter {
					return newFloatParameter(parameterSpec{
						Description:  "Bdfkgjh dkfjgh sdfgkkkk",
						DefaultValue: float32(6.0),
					})
				},
			},
		}
	})
}

type value struct {
	base

	A float32
	B float32
}

func newValue(b base) pkg.Module {
	m := &value{
		base: b,
	}

	m.parameters["A"].Link(&m.A)
	m.parameters["B"].Link(&m.B)

	return m
}

func (m *value) OnMessage(event pkg.MessageEvent) pkg.Actions {
	user := event.User
	message := event.Message

	if !strings.HasPrefix(message.GetText(), "!") {
		return nil
	}
	parts := strings.Split(message.GetText(), " ")
	if parts[0] == "!value-a" {
		if len(parts) >= 2 {
			if err := m.setParameter("A", parts[1]); err != nil {
				return twitchactions.Mention(user, err.Error())
			}

			err := m.Save()
			if err != nil {
				return nil
			}
			return twitchactions.Mention(user, "A set to "+utils.Float32ToString(m.A))
		}

		return twitchactions.Mention(user, "A is "+utils.Float32ToString(m.A))
	}

	if parts[0] == "!value-b" {
		if len(parts) >= 2 {
			if err := m.setParameter("B", parts[1]); err != nil {
				return twitchactions.Mention(user, err.Error())
			}

			err := m.Save()
			if err != nil {
				return nil
			}
			return twitchactions.Mention(user, "B set to "+utils.Float32ToString(m.B))
		}

		return twitchactions.Mention(user, "B is "+utils.Float32ToString(m.B))
	}

	return nil
}
<commit_msg>use command manager for the command matching in the Value module<commit_after>package modules

import (
	"github.com/pajbot/pajbot2/pkg"
	"github.com/pajbot/pajbot2/pkg/commands"
	"github.com/pajbot/pajbot2/pkg/twitchactions"
	"github.com/pajbot/utils"
)

func init() {
	Register("value", func() pkg.ModuleSpec {
		return &moduleSpec{
			id:               "value",
			name:             "Value",
			enabledByDefault: false,

			maker: newValue,

			parameters: map[string]pkg.ModuleParameterSpec{
				"A": func() pkg.ModuleParameter {
					return newFloatParameter(parameterSpec{
						Description:  "A kjdfghk jdfhgkj dfg",
						DefaultValue: float32(3.0),
					})
				},
				"B": func() pkg.ModuleParameter {
					return newFloatParameter(parameterSpec{
						Description:  "Bdfkgjh dkfjgh sdfgkkkk",
						DefaultValue: float32(6.0),
					})
				},
			},
		}
	})
}

type value struct {
	base

	commands pkg.CommandsManager

	A float32
	B float32
}

type valueCmd struct {
	m     *value
	key   string
	value *float32
}

func (c *valueCmd) set(parts []string, event pkg.MessageEvent) pkg.Actions {
	if err := c.m.setParameter(c.key, parts[1]); err != nil {
		return twitchactions.Mention(event.User, err.Error())
	}

	err := c.m.Save()
	if err != nil {
		return nil
	}
	return twitchactions.Mention(event.User, c.key+" set to "+utils.Float32ToString(*c.value))
}

func (c *valueCmd) get(parts []string, event pkg.MessageEvent) pkg.Actions {
	return twitchactions.Mention(event.User, c.key+" is "+utils.Float32ToString(*c.value))
}

func (c *valueCmd) Trigger(parts []string, event pkg.MessageEvent) pkg.Actions {
	if len(parts) >= 2 {
		return c.set(parts, event)
	}

	return c.get(parts, event)
}

func newValue(b base) pkg.Module {
	m := &value{
		base: b,

		commands: commands.NewCommands(),
	}

	m.parameters["A"].Link(&m.A)
	m.parameters["B"].Link(&m.B)

	m.commands.Register([]string{"!value-a"}, &valueCmd{m, "A", &m.A})
	m.commands.Register([]string{"!value-b"}, &valueCmd{m, "B", &m.B})

	return m
}

func (m *value) OnMessage(event pkg.MessageEvent) pkg.Actions {
	return m.commands.OnMessage(event)
}
<|endoftext|>


<commit_before>// Copyright 2016-2018 Authors of Cilium
//
// 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 option

import (
	"fmt"
	"sort"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/color"
	"github.com/cilium/cilium/pkg/lock"
)

// VerifyFunc validates option key with value and may return an error if the
// option should not be applied
type VerifyFunc func(key string, value string) error

// ParseFunc parses the option value and may return an error if the option
// cannot be parsed or applied.
type ParseFunc func(value string) (OptionSetting, error)

// FormatFunc formats the specified value as a colored textual representation
// of the option.
type FormatFunc func(value OptionSetting) string

// Option is the structure used to specify the semantics of a configurable
// boolean option
type Option struct {
	// Define is the name of the #define used for BPF programs
	Define string
	// Description is a short human readable description
	Description string
	// Immutable marks an option which is read-only
	Immutable bool
	// Requires is a list of required options, such options will be
	// automatically enabled as required.
	Requires []string
	// Parse is called to parse the option. If not specified, defaults to
	// NormalizeBool().
	Parse ParseFunc
	// FormatFunc is called to format the value for an option. If not
	// specified, defaults to formatting 0 as "Disabled" and other values
	// as "Enabled".
	Format FormatFunc
	// Verify is called prior to applying the option
	Verify VerifyFunc
}

// OptionSetting specifies the different choices each Option has.
type OptionSetting int

const (
	OptionDisabled OptionSetting = iota
	OptionEnabled
)

// RequiresOption returns true if the option requires the specified option `name`.
func (o Option) RequiresOption(name string) bool {
	for _, o := range o.Requires {
		if o == name {
			return true
		}
	}

	return false
}

type OptionLibrary map[string]*Option

func (l OptionLibrary) Lookup(name string) (string, *Option) {
	nameLower := strings.ToLower(name)

	for k := range l {
		if strings.ToLower(k) == nameLower {
			return k, l[k]
		}
	}

	return "", nil
}

func (l OptionLibrary) Define(name string) string {
	if _, ok := l[name]; ok {
		return l[name].Define
	}

	return name
}

func NormalizeBool(value string) (OptionSetting, error) {
	switch strings.ToLower(value) {
	case "true", "on", "enable", "enabled", "1":
		return OptionEnabled, nil
	case "false", "off", "disable", "disabled", "0":
		return OptionDisabled, nil
	default:
		return OptionDisabled, fmt.Errorf("Invalid option value %s", value)
	}
}

// ValidateConfigurationMap validates a given configuration map based on the
// option library
func (l *OptionLibrary) ValidateConfigurationMap(n models.ConfigurationMap) (OptionMap, error) {
	o := make(OptionMap)
	for k, v := range n {
		_, newVal, err := ParseKeyValue(l, k, v)
		if err != nil {
			return nil, err
		}

		if err := l.Validate(k, v); err != nil {
			return nil, err
		}
		o[k] = newVal
	}

	return o, nil
}

func (l OptionLibrary) Validate(name string, value string) error {
	key, spec := l.Lookup(name)
	if key == "" {
		return fmt.Errorf("Unknown option %s", name)
	}

	if spec.Immutable {
		return fmt.Errorf("Specified option is immutable (read-only)")
	}

	if spec.Verify != nil {
		return spec.Verify(key, value)
	}

	return nil
}

type OptionMap map[string]OptionSetting

func (om OptionMap) DeepCopy() OptionMap {
	cpy := make(OptionMap, len(om))
	for k, v := range om {
		cpy[k] = v
	}
	return cpy
}

// IntOptions member functions with external access do not require
// locking by the caller, while functions with internal access presume
// the caller to have taken care of any locking needed.
type IntOptions struct {
	optsMU  lock.RWMutex   // Protects all variables from this structure below this line
	Opts    OptionMap      `json:"map"`
	Library *OptionLibrary `json:"-"`
}

// GetImmutableModel returns the set of immutable options as a ConfigurationMap API model.
func (o *IntOptions) GetImmutableModel() *models.ConfigurationMap {
	immutableCfg := make(models.ConfigurationMap)
	return &immutableCfg
}

// GetMutableModel returns the set of mutable options as a ConfigurationMap API model.
func (o *IntOptions) GetMutableModel() *models.ConfigurationMap {
	mutableCfg := make(models.ConfigurationMap)
	o.optsMU.RLock()
	for k, v := range o.Opts {
		_, config := o.Library.Lookup(k)
		if config.Format == nil {
			if v == OptionDisabled {
				mutableCfg[k] = fmt.Sprintf("Disabled")
			} else {
				mutableCfg[k] = fmt.Sprintf("Enabled")
			}
		} else {
			mutableCfg[k] = config.Format(v)
		}
	}
	o.optsMU.RUnlock()

	return &mutableCfg
}

func (o *IntOptions) DeepCopy() *IntOptions {
	o.optsMU.RLock()
	cpy := &IntOptions{
		Opts:    o.Opts.DeepCopy(),
		Library: o.Library,
	}
	o.optsMU.RUnlock()
	return cpy
}

func NewIntOptions(lib *OptionLibrary) *IntOptions {
	return &IntOptions{
		Opts:    OptionMap{},
		Library: lib,
	}
}

func (o *IntOptions) getValue(key string) OptionSetting {
	value, exists := o.Opts[key]
	if !exists {
		return OptionDisabled
	}
	return value
}

func (o *IntOptions) GetValue(key string) OptionSetting {
	o.optsMU.RLock()
	v := o.getValue(key)
	o.optsMU.RUnlock()
	return v
}

func (o *IntOptions) IsEnabled(key string) bool {
	return o.GetValue(key) != OptionDisabled
}

// SetValidated sets the option `key` to the specified value. The caller is
// expected to have validated the input to this function.
func (o *IntOptions) SetValidated(key string, value OptionSetting) {
	o.optsMU.Lock()
	o.Opts[key] = value
	o.optsMU.Unlock()
}

// SetBool sets the specified option to Enabled.
func (o *IntOptions) SetBool(key string, value bool) {
	intValue := OptionDisabled
	if value {
		intValue = OptionEnabled
	}
	o.optsMU.Lock()
	o.Opts[key] = intValue
	o.optsMU.Unlock()
}

func (o *IntOptions) Delete(key string) {
	o.optsMU.Lock()
	delete(o.Opts, key)
	o.optsMU.Unlock()
}

func (o *IntOptions) SetIfUnset(key string, value OptionSetting) {
	o.optsMU.Lock()
	if _, exists := o.Opts[key]; !exists {
		o.Opts[key] = value
	}
	o.optsMU.Unlock()
}

func (o *IntOptions) InheritDefault(parent *IntOptions, key string) {
	o.optsMU.RLock()
	o.Opts[key] = parent.GetValue(key)
	o.optsMU.RUnlock()
}

func ParseOption(arg string, lib *OptionLibrary) (string, OptionSetting, error) {
	result := OptionEnabled

	if arg[0] == '!' {
		result = OptionDisabled
		arg = arg[1:]
	}

	optionSplit := strings.SplitN(arg, "=", 2)
	arg = optionSplit[0]
	if len(optionSplit) > 1 {
		if result == OptionDisabled {
			return "", OptionDisabled, fmt.Errorf("Invalid boolean format")
		}

		return ParseKeyValue(lib, arg, optionSplit[1])
	}

	return "", OptionDisabled, fmt.Errorf("Invalid option format")
}

func ParseKeyValue(lib *OptionLibrary, arg, value string) (string, OptionSetting, error) {
	var result OptionSetting

	key, spec := lib.Lookup(arg)
	if key == "" {
		return "", OptionDisabled, fmt.Errorf("Unknown option %q", arg)
	}

	var err error
	if spec.Parse != nil {
		result, err = spec.Parse(value)
	} else {
		result, err = NormalizeBool(value)
	}
	if err != nil {
		return "", OptionDisabled, err
	}

	if spec.Immutable {
		return "", OptionDisabled, fmt.Errorf("Specified option is immutable (read-only)")
	}

	return key, result, nil
}

// getFmtOpt returns #define name if option exists and is set to true in endpoint's Opts
// map or #undef name if option does not exist or exists but is set to false
func (o *IntOptions) getFmtOpt(name string) string {
	define := o.Library.Define(name)
	if define == "" {
		return ""
	}

	value := o.getValue(name)
	if value != OptionDisabled {
		return fmt.Sprintf("#define %s %d", o.Library.Define(name), value)
	}
	return "#undef " + o.Library.Define(name)
}

func (o *IntOptions) GetFmtList() string {
	txt := ""

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		def := o.getFmtOpt(k)
		if def != "" {
			txt += def + "\n"
		}
	}
	o.optsMU.RUnlock()

	return txt
}

func (o *IntOptions) Dump() {
	if o == nil {
		return
	}

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		var text string
		_, option := o.Library.Lookup(k)
		if option == nil || option.Format == nil {
			if o.Opts[k] == OptionDisabled {
				text = color.Red("Disabled")
			} else {
				text = color.Green("Enabled")
			}
		} else {
			text = option.Format(o.Opts[k])
		}

		fmt.Printf("%-24s %s\n", k, text)
	}
	o.optsMU.RUnlock()
}

// Validate validates a given configuration map based on the option library
func (o *IntOptions) Validate(n models.ConfigurationMap) error {
	o.optsMU.RLock()
	defer o.optsMU.RUnlock()
	for k, v := range n {
		_, newVal, err := ParseKeyValue(o.Library, k, v)
		if err != nil {
			return err
		}

		// Ignore validation if value is identical
		if oldVal, ok := o.Opts[k]; ok && oldVal == newVal {
			continue
		}

		if err := o.Library.Validate(k, v); err != nil {
			return err
		}
	}

	return nil
}

// ChangedFunc is called by `Apply()` for each option changed
type ChangedFunc func(key string, value OptionSetting, data interface{})

// enable enables the option `name` with all its dependencies
func (o *IntOptions) enable(name string) {
	if o.Library != nil {
		if _, opt := o.Library.Lookup(name); opt != nil {
			for _, dependency := range opt.Requires {
				o.enable(dependency)
			}
		}
	}

	o.Opts[name] = OptionEnabled
}

// set enables the option `name` with all its dependencies, and sets the
// integer level of the option to `value`.
func (o *IntOptions) set(name string, value OptionSetting) {
	o.enable(name)
	o.Opts[name] = value
}

// disable disables the option `name`. All options which depend on the option
// to be disabled will be disabled. Options which have previously been enabled
// as a dependency will not be automatically disabled.
func (o *IntOptions) disable(name string) {
	o.Opts[name] = OptionDisabled

	if o.Library != nil {
		// Disable all options which have a dependency on the option
		// that was just disabled
		for key, opt := range *o.Library {
			if opt.RequiresOption(name) && o.Opts[key] != OptionDisabled {
				o.disable(key)
			}
		}
	}
}

type changedOptions struct {
	key   string
	value OptionSetting
}

// ApplyValidated takes a configuration map and applies the changes. For an
// option which is changed, the `ChangedFunc` function is called with the
// `data` argument passed in as well. Returns the number of options changed if
// any.
//
// The caller is expected to have validated the configuration options prior to
// calling this function.
func (o *IntOptions) ApplyValidated(n OptionMap, changed ChangedFunc, data interface{}) int {
	changes := make([]changedOptions, 0, len(n))

	o.optsMU.Lock()
	for k, optVal := range n {
		val, ok := o.Opts[k]

		if optVal == OptionDisabled {
			/* Only disable if enabled already */
			if ok && val != OptionDisabled {
				o.disable(k)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		} else {
			/* Only enable if not enabled already */
			if !ok || val == OptionDisabled {
				o.set(k, optVal)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		}
	}
	o.optsMU.Unlock()

	for _, change := range changes {
		changed(change.key, change.value, data)
	}

	return len(changes)
}
<commit_msg>option: fix nil-pointer dereference<commit_after>// Copyright 2016-2018 Authors of Cilium
//
// 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 option

import (
	"fmt"
	"sort"
	"strings"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/color"
	"github.com/cilium/cilium/pkg/lock"
)

// VerifyFunc validates option key with value and may return an error if the
// option should not be applied
type VerifyFunc func(key string, value string) error

// ParseFunc parses the option value and may return an error if the option
// cannot be parsed or applied.
type ParseFunc func(value string) (OptionSetting, error)

// FormatFunc formats the specified value as a colored textual representation
// of the option.
type FormatFunc func(value OptionSetting) string

// Option is the structure used to specify the semantics of a configurable
// boolean option
type Option struct {
	// Define is the name of the #define used for BPF programs
	Define string
	// Description is a short human readable description
	Description string
	// Immutable marks an option which is read-only
	Immutable bool
	// Requires is a list of required options, such options will be
	// automatically enabled as required.
	Requires []string
	// Parse is called to parse the option. If not specified, defaults to
	// NormalizeBool().
	Parse ParseFunc
	// FormatFunc is called to format the value for an option. If not
	// specified, defaults to formatting 0 as "Disabled" and other values
	// as "Enabled".
	Format FormatFunc
	// Verify is called prior to applying the option
	Verify VerifyFunc
}

// OptionSetting specifies the different choices each Option has.
type OptionSetting int

const (
	OptionDisabled OptionSetting = iota
	OptionEnabled
)

// RequiresOption returns true if the option requires the specified option `name`.
func (o Option) RequiresOption(name string) bool {
	for _, o := range o.Requires {
		if o == name {
			return true
		}
	}

	return false
}

type OptionLibrary map[string]*Option

func (l OptionLibrary) Lookup(name string) (string, *Option) {
	nameLower := strings.ToLower(name)

	for k := range l {
		if strings.ToLower(k) == nameLower {
			return k, l[k]
		}
	}

	return "", nil
}

func (l OptionLibrary) Define(name string) string {
	if _, ok := l[name]; ok {
		return l[name].Define
	}

	return name
}

func NormalizeBool(value string) (OptionSetting, error) {
	switch strings.ToLower(value) {
	case "true", "on", "enable", "enabled", "1":
		return OptionEnabled, nil
	case "false", "off", "disable", "disabled", "0":
		return OptionDisabled, nil
	default:
		return OptionDisabled, fmt.Errorf("Invalid option value %s", value)
	}
}

// ValidateConfigurationMap validates a given configuration map based on the
// option library
func (l *OptionLibrary) ValidateConfigurationMap(n models.ConfigurationMap) (OptionMap, error) {
	o := make(OptionMap)
	for k, v := range n {
		_, newVal, err := ParseKeyValue(l, k, v)
		if err != nil {
			return nil, err
		}

		if err := l.Validate(k, v); err != nil {
			return nil, err
		}
		o[k] = newVal
	}

	return o, nil
}

func (l OptionLibrary) Validate(name string, value string) error {
	key, spec := l.Lookup(name)
	if key == "" {
		return fmt.Errorf("Unknown option %s", name)
	}

	if spec.Immutable {
		return fmt.Errorf("Specified option is immutable (read-only)")
	}

	if spec.Verify != nil {
		return spec.Verify(key, value)
	}

	return nil
}

type OptionMap map[string]OptionSetting

func (om OptionMap) DeepCopy() OptionMap {
	cpy := make(OptionMap, len(om))
	for k, v := range om {
		cpy[k] = v
	}
	return cpy
}

// IntOptions member functions with external access do not require
// locking by the caller, while functions with internal access presume
// the caller to have taken care of any locking needed.
type IntOptions struct {
	optsMU  lock.RWMutex   // Protects all variables from this structure below this line
	Opts    OptionMap      `json:"map"`
	Library *OptionLibrary `json:"-"`
}

// GetImmutableModel returns the set of immutable options as a ConfigurationMap API model.
func (o *IntOptions) GetImmutableModel() *models.ConfigurationMap {
	immutableCfg := make(models.ConfigurationMap)
	return &immutableCfg
}

// GetMutableModel returns the set of mutable options as a ConfigurationMap API model.
func (o *IntOptions) GetMutableModel() *models.ConfigurationMap {
	mutableCfg := make(models.ConfigurationMap)
	o.optsMU.RLock()
	for k, v := range o.Opts {
		_, config := o.Library.Lookup(k)

		// It's possible that an option has since been removed and thus has
		// no corresponding configuration; need to check if configuration is
		// nil accordingly.
		if config != nil {
			if config.Format == nil {
				if v == OptionDisabled {
					mutableCfg[k] = fmt.Sprintf("Disabled")
				} else {
					mutableCfg[k] = fmt.Sprintf("Enabled")
				}
			} else {
				mutableCfg[k] = config.Format(v)
			}
		}
	}
	o.optsMU.RUnlock()

	return &mutableCfg
}

func (o *IntOptions) DeepCopy() *IntOptions {
	o.optsMU.RLock()
	cpy := &IntOptions{
		Opts:    o.Opts.DeepCopy(),
		Library: o.Library,
	}
	o.optsMU.RUnlock()
	return cpy
}

func NewIntOptions(lib *OptionLibrary) *IntOptions {
	return &IntOptions{
		Opts:    OptionMap{},
		Library: lib,
	}
}

func (o *IntOptions) getValue(key string) OptionSetting {
	value, exists := o.Opts[key]
	if !exists {
		return OptionDisabled
	}
	return value
}

func (o *IntOptions) GetValue(key string) OptionSetting {
	o.optsMU.RLock()
	v := o.getValue(key)
	o.optsMU.RUnlock()
	return v
}

func (o *IntOptions) IsEnabled(key string) bool {
	return o.GetValue(key) != OptionDisabled
}

// SetValidated sets the option `key` to the specified value. The caller is
// expected to have validated the input to this function.
func (o *IntOptions) SetValidated(key string, value OptionSetting) {
	o.optsMU.Lock()
	o.Opts[key] = value
	o.optsMU.Unlock()
}

// SetBool sets the specified option to Enabled.
func (o *IntOptions) SetBool(key string, value bool) {
	intValue := OptionDisabled
	if value {
		intValue = OptionEnabled
	}
	o.optsMU.Lock()
	o.Opts[key] = intValue
	o.optsMU.Unlock()
}

func (o *IntOptions) Delete(key string) {
	o.optsMU.Lock()
	delete(o.Opts, key)
	o.optsMU.Unlock()
}

func (o *IntOptions) SetIfUnset(key string, value OptionSetting) {
	o.optsMU.Lock()
	if _, exists := o.Opts[key]; !exists {
		o.Opts[key] = value
	}
	o.optsMU.Unlock()
}

func (o *IntOptions) InheritDefault(parent *IntOptions, key string) {
	o.optsMU.RLock()
	o.Opts[key] = parent.GetValue(key)
	o.optsMU.RUnlock()
}

func ParseOption(arg string, lib *OptionLibrary) (string, OptionSetting, error) {
	result := OptionEnabled

	if arg[0] == '!' {
		result = OptionDisabled
		arg = arg[1:]
	}

	optionSplit := strings.SplitN(arg, "=", 2)
	arg = optionSplit[0]
	if len(optionSplit) > 1 {
		if result == OptionDisabled {
			return "", OptionDisabled, fmt.Errorf("Invalid boolean format")
		}

		return ParseKeyValue(lib, arg, optionSplit[1])
	}

	return "", OptionDisabled, fmt.Errorf("Invalid option format")
}

func ParseKeyValue(lib *OptionLibrary, arg, value string) (string, OptionSetting, error) {
	var result OptionSetting

	key, spec := lib.Lookup(arg)
	if key == "" {
		return "", OptionDisabled, fmt.Errorf("Unknown option %q", arg)
	}

	var err error
	if spec.Parse != nil {
		result, err = spec.Parse(value)
	} else {
		result, err = NormalizeBool(value)
	}
	if err != nil {
		return "", OptionDisabled, err
	}

	if spec.Immutable {
		return "", OptionDisabled, fmt.Errorf("Specified option is immutable (read-only)")
	}

	return key, result, nil
}

// getFmtOpt returns #define name if option exists and is set to true in endpoint's Opts
// map or #undef name if option does not exist or exists but is set to false
func (o *IntOptions) getFmtOpt(name string) string {
	define := o.Library.Define(name)
	if define == "" {
		return ""
	}

	value := o.getValue(name)
	if value != OptionDisabled {
		return fmt.Sprintf("#define %s %d", o.Library.Define(name), value)
	}
	return "#undef " + o.Library.Define(name)
}

func (o *IntOptions) GetFmtList() string {
	txt := ""

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		def := o.getFmtOpt(k)
		if def != "" {
			txt += def + "\n"
		}
	}
	o.optsMU.RUnlock()

	return txt
}

func (o *IntOptions) Dump() {
	if o == nil {
		return
	}

	o.optsMU.RLock()
	opts := []string{}
	for k := range o.Opts {
		opts = append(opts, k)
	}
	sort.Strings(opts)

	for _, k := range opts {
		var text string
		_, option := o.Library.Lookup(k)
		if option == nil || option.Format == nil {
			if o.Opts[k] == OptionDisabled {
				text = color.Red("Disabled")
			} else {
				text = color.Green("Enabled")
			}
		} else {
			text = option.Format(o.Opts[k])
		}

		fmt.Printf("%-24s %s\n", k, text)
	}
	o.optsMU.RUnlock()
}

// Validate validates a given configuration map based on the option library
func (o *IntOptions) Validate(n models.ConfigurationMap) error {
	o.optsMU.RLock()
	defer o.optsMU.RUnlock()
	for k, v := range n {
		_, newVal, err := ParseKeyValue(o.Library, k, v)
		if err != nil {
			return err
		}

		// Ignore validation if value is identical
		if oldVal, ok := o.Opts[k]; ok && oldVal == newVal {
			continue
		}

		if err := o.Library.Validate(k, v); err != nil {
			return err
		}
	}

	return nil
}

// ChangedFunc is called by `Apply()` for each option changed
type ChangedFunc func(key string, value OptionSetting, data interface{})

// enable enables the option `name` with all its dependencies
func (o *IntOptions) enable(name string) {
	if o.Library != nil {
		if _, opt := o.Library.Lookup(name); opt != nil {
			for _, dependency := range opt.Requires {
				o.enable(dependency)
			}
		}
	}

	o.Opts[name] = OptionEnabled
}

// set enables the option `name` with all its dependencies, and sets the
// integer level of the option to `value`.
func (o *IntOptions) set(name string, value OptionSetting) {
	o.enable(name)
	o.Opts[name] = value
}

// disable disables the option `name`. All options which depend on the option
// to be disabled will be disabled. Options which have previously been enabled
// as a dependency will not be automatically disabled.
func (o *IntOptions) disable(name string) {
	o.Opts[name] = OptionDisabled

	if o.Library != nil {
		// Disable all options which have a dependency on the option
		// that was just disabled
		for key, opt := range *o.Library {
			if opt.RequiresOption(name) && o.Opts[key] != OptionDisabled {
				o.disable(key)
			}
		}
	}
}

type changedOptions struct {
	key   string
	value OptionSetting
}

// ApplyValidated takes a configuration map and applies the changes. For an
// option which is changed, the `ChangedFunc` function is called with the
// `data` argument passed in as well. Returns the number of options changed if
// any.
//
// The caller is expected to have validated the configuration options prior to
// calling this function.
func (o *IntOptions) ApplyValidated(n OptionMap, changed ChangedFunc, data interface{}) int {
	changes := make([]changedOptions, 0, len(n))

	o.optsMU.Lock()
	for k, optVal := range n {
		val, ok := o.Opts[k]

		if optVal == OptionDisabled {
			/* Only disable if enabled already */
			if ok && val != OptionDisabled {
				o.disable(k)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		} else {
			/* Only enable if not enabled already */
			if !ok || val == OptionDisabled {
				o.set(k, optVal)
				changes = append(changes, changedOptions{key: k, value: optVal})
			}
		}
	}
	o.optsMU.Unlock()

	for _, change := range changes {
		changed(change.key, change.value, data)
	}

	return len(changes)
}
<|endoftext|>


<commit_before>// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/jaypipes/pcidb"

	"github.com/jaypipes/ghw/pkg/context"
	"github.com/jaypipes/ghw/pkg/linuxpath"
	pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
	"github.com/jaypipes/ghw/pkg/topology"
	"github.com/jaypipes/ghw/pkg/util"
)

func (i *Info) load() error {
	db, err := pcidb.New(pcidb.WithChroot(i.ctx.Chroot))
	if err != nil {
		return err
	}
	i.Classes = db.Classes
	i.Vendors = db.Vendors
	i.Products = db.Products
	i.Devices = i.ListDevices()
	return nil
}

func getDeviceModaliasPath(ctx *context.Context, address string) string {
	paths := linuxpath.New(ctx)
	pciAddr := pciaddr.FromString(address)
	if pciAddr == nil {
		return ""
	}
	return filepath.Join(
		paths.SysBusPciDevices,
		pciAddr.String(),
		"modalias",
	)
}

func getDeviceRevision(ctx *context.Context, address string) string {
	paths := linuxpath.New(ctx)
	pciAddr := pciaddr.FromString(address)
	if pciAddr == nil {
		return ""
	}
	revisionPath := filepath.Join(
		paths.SysBusPciDevices,
		pciAddr.String(),
		"revision",
	)

	if _, err := os.Stat(revisionPath); err != nil {
		return ""
	}
	revision, err := ioutil.ReadFile(revisionPath)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(revision))
}

func getDeviceNUMANode(ctx *context.Context, address string) *topology.Node {
	paths := linuxpath.New(ctx)
	pciAddr := AddressFromString(address)
	if pciAddr == nil {
		return nil
	}
	numaNodePath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "numa_node")

	if _, err := os.Stat(numaNodePath); err != nil {
		return nil
	}

	nodeIdx := util.SafeIntFromFile(ctx, numaNodePath)
	if nodeIdx == -1 {
		return nil
	}

	return &topology.Node{
		ID: nodeIdx,
	}
}

func getDeviceDriver(ctx *context.Context, address string) string {
	paths := linuxpath.New(ctx)
	pciAddr := AddressFromString(address)
	if pciAddr == nil {
		return ""
	}
	driverPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "driver")

	if _, err := os.Stat(driverPath); err != nil {
		return ""
	}

	dest, err := os.Readlink(driverPath)
	if err != nil {
		return ""
	}
	return filepath.Base(dest)
}

type deviceModaliasInfo struct {
	vendorID     string
	productID    string
	subproductID string
	subvendorID  string
	classID      string
	subclassID   string
	progIfaceID  string
}

func parseModaliasFile(fp string) *deviceModaliasInfo {
	if _, err := os.Stat(fp); err != nil {
		return nil
	}
	data, err := ioutil.ReadFile(fp)
	if err != nil {
		return nil
	}

	return parseModaliasData(string(data))
}

func parseModaliasData(data string) *deviceModaliasInfo {
	// The modalias file is an encoded file that looks like this:
	//
	// $ cat /sys/devices/pci0000\:00/0000\:00\:03.0/0000\:03\:00.0/modalias
	// pci:v000010DEd00001C82sv00001043sd00008613bc03sc00i00
	//
	// It is interpreted like so:
	//
	// pci: -- ignore
	// v000010DE -- PCI vendor ID
	// d00001C82 -- PCI device ID (the product/model ID)
	// sv00001043 -- PCI subsystem vendor ID
	// sd00008613 -- PCI subsystem device ID (subdevice product/model ID)
	// bc03 -- PCI base class
	// sc00 -- PCI subclass
	// i00 -- programming interface
	vendorID := strings.ToLower(data[9:13])
	productID := strings.ToLower(data[18:22])
	subvendorID := strings.ToLower(data[28:32])
	subproductID := strings.ToLower(data[38:42])
	classID := data[44:46]
	subclassID := data[48:50]
	progIfaceID := data[51:53]
	return &deviceModaliasInfo{
		vendorID:     vendorID,
		productID:    productID,
		subproductID: subproductID,
		subvendorID:  subvendorID,
		classID:      classID,
		subclassID:   subclassID,
		progIfaceID:  progIfaceID,
	}
}

// Returns a pointer to a pcidb.Vendor struct matching the supplied vendor
// ID string. If no such vendor ID string could be found, returns the
// pcidb.Vendor struct populated with "unknown" vendor Name attribute and
// empty Products attribute.
func findPCIVendor(info *Info, vendorID string) *pcidb.Vendor {
	vendor := info.Vendors[vendorID]
	if vendor == nil {
		return &pcidb.Vendor{
			ID:       vendorID,
			Name:     util.UNKNOWN,
			Products: []*pcidb.Product{},
		}
	}
	return vendor
}

// Returns a pointer to a pcidb.Product struct matching the supplied vendor
// and product ID strings. If no such product could be found, returns the
// pcidb.Product struct populated with "unknown" product Name attribute and
// empty Subsystems attribute.
func findPCIProduct(
	info *Info,
	vendorID string,
	productID string,
) *pcidb.Product {
	product := info.Products[vendorID+productID]
	if product == nil {
		return &pcidb.Product{
			ID:         productID,
			Name:       util.UNKNOWN,
			Subsystems: []*pcidb.Product{},
		}
	}
	return product
}

// Returns a pointer to a pcidb.Product struct matching the supplied vendor,
// product, subvendor and subproduct ID strings. If no such product could be
// found, returns the pcidb.Product struct populated with "unknown" product
// Name attribute and empty Subsystems attribute.
func findPCISubsystem(
	info *Info,
	vendorID string,
	productID string,
	subvendorID string,
	subproductID string,
) *pcidb.Product {
	product := info.Products[vendorID+productID]
	subvendor := info.Vendors[subvendorID]
	if subvendor != nil && product != nil {
		for _, p := range product.Subsystems {
			if p.ID == subproductID {
				return p
			}
		}
	}
	return &pcidb.Product{
		VendorID: subvendorID,
		ID:       subproductID,
		Name:     util.UNKNOWN,
	}
}

// Returns a pointer to a pcidb.Class struct matching the supplied class ID
// string. If no such class ID string could be found, returns the
// pcidb.Class struct populated with "unknown" class Name attribute and
// empty Subclasses attribute.
func findPCIClass(info *Info, classID string) *pcidb.Class {
	class := info.Classes[classID]
	if class == nil {
		return &pcidb.Class{
			ID:         classID,
			Name:       util.UNKNOWN,
			Subclasses: []*pcidb.Subclass{},
		}
	}
	return class
}

// Returns a pointer to a pcidb.Subclass struct matching the supplied class
// and subclass ID strings.  If no such subclass could be found, returns the
// pcidb.Subclass struct populated with "unknown" subclass Name attribute
// and empty ProgrammingInterfaces attribute.
func findPCISubclass(
	info *Info,
	classID string,
	subclassID string,
) *pcidb.Subclass {
	class := info.Classes[classID]
	if class != nil {
		for _, sc := range class.Subclasses {
			if sc.ID == subclassID {
				return sc
			}
		}
	}
	return &pcidb.Subclass{
		ID:                    subclassID,
		Name:                  util.UNKNOWN,
		ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
	}
}

// Returns a pointer to a pcidb.ProgrammingInterface struct matching the
// supplied class, subclass and programming interface ID strings.  If no such
// programming interface could be found, returns the
// pcidb.ProgrammingInterface struct populated with "unknown" Name attribute
func findPCIProgrammingInterface(
	info *Info,
	classID string,
	subclassID string,
	progIfaceID string,
) *pcidb.ProgrammingInterface {
	subclass := findPCISubclass(info, classID, subclassID)
	for _, pi := range subclass.ProgrammingInterfaces {
		if pi.ID == progIfaceID {
			return pi
		}
	}
	return &pcidb.ProgrammingInterface{
		ID:   progIfaceID,
		Name: util.UNKNOWN,
	}
}

// GetDevice returns a pointer to a Device struct that describes the PCI
// device at the requested address. If no such device could be found, returns nil.
func (info *Info) GetDevice(address string) *Device {
	// check cached data first
	if dev := info.lookupDevice(address); dev != nil {
		return dev
	}

	// no cached data, let's get the information from system.
	fp := getDeviceModaliasPath(info.ctx, address)
	if fp == "" {
		info.ctx.Warn("error finding modalias info for device %q", address)
		return nil
	}

	modaliasInfo := parseModaliasFile(fp)
	if modaliasInfo == nil {
		info.ctx.Warn("error parsing modalias info for device %q", address)
		return nil
	}

	device := info.getDeviceFromModaliasInfo(address, modaliasInfo)
	device.Revision = getDeviceRevision(info.ctx, address)
	if info.arch == topology.ARCHITECTURE_NUMA {
		device.Node = getDeviceNUMANode(info.ctx, address)
	}
	device.Driver = getDeviceDriver(info.ctx, address)
	return device
}

// ParseDevice returns a pointer to a Device given its describing data.
// The PCI device obtained this way may not exist in the system;
// use GetDevice to get a *Device which is found in the system
func (info *Info) ParseDevice(address, modalias string) *Device {
	modaliasInfo := parseModaliasData(modalias)
	if modaliasInfo == nil {
		return nil
	}
	return info.getDeviceFromModaliasInfo(address, modaliasInfo)
}

func (info *Info) getDeviceFromModaliasInfo(address string, modaliasInfo *deviceModaliasInfo) *Device {
	vendor := findPCIVendor(info, modaliasInfo.vendorID)
	product := findPCIProduct(
		info,
		modaliasInfo.vendorID,
		modaliasInfo.productID,
	)
	subsystem := findPCISubsystem(
		info,
		modaliasInfo.vendorID,
		modaliasInfo.productID,
		modaliasInfo.subvendorID,
		modaliasInfo.subproductID,
	)
	class := findPCIClass(info, modaliasInfo.classID)
	subclass := findPCISubclass(
		info,
		modaliasInfo.classID,
		modaliasInfo.subclassID,
	)
	progIface := findPCIProgrammingInterface(
		info,
		modaliasInfo.classID,
		modaliasInfo.subclassID,
		modaliasInfo.progIfaceID,
	)

	return &Device{
		Address:              address,
		Vendor:               vendor,
		Subsystem:            subsystem,
		Product:              product,
		Class:                class,
		Subclass:             subclass,
		ProgrammingInterface: progIface,
	}
}

// ListDevices returns a list of pointers to Device structs present on the
// host system
// DEPRECATED. Will be removed in v1.0. Please use
// github.com/jaypipes/pcidb to explore PCIDB information
func (info *Info) ListDevices() []*Device {
	paths := linuxpath.New(info.ctx)
	devs := make([]*Device, 0)
	// We scan the /sys/bus/pci/devices directory which contains a collection
	// of symlinks. The names of the symlinks are all the known PCI addresses
	// for the host. For each address, we grab a *Device matching the
	// address and append to the returned array.
	links, err := ioutil.ReadDir(paths.SysBusPciDevices)
	if err != nil {
		info.ctx.Warn("failed to read /sys/bus/pci/devices")
		return nil
	}
	var dev *Device
	for _, link := range links {
		addr := link.Name()
		dev = info.GetDevice(addr)
		if dev == nil {
			info.ctx.Warn("failed to get device information for PCI address %s", addr)
		} else {
			devs = append(devs, dev)
		}
	}
	return devs
}
<commit_msg>pci: remove redundant multiple pciaddr parsing<commit_after>// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/jaypipes/pcidb"

	"github.com/jaypipes/ghw/pkg/context"
	"github.com/jaypipes/ghw/pkg/linuxpath"
	pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
	"github.com/jaypipes/ghw/pkg/topology"
	"github.com/jaypipes/ghw/pkg/util"
)

func (i *Info) load() error {
	db, err := pcidb.New(pcidb.WithChroot(i.ctx.Chroot))
	if err != nil {
		return err
	}
	i.Classes = db.Classes
	i.Vendors = db.Vendors
	i.Products = db.Products
	i.Devices = i.ListDevices()
	return nil
}

func getDeviceModaliasPath(ctx *context.Context, pciAddr *pciaddr.Address) string {
	paths := linuxpath.New(ctx)
	return filepath.Join(
		paths.SysBusPciDevices,
		pciAddr.String(),
		"modalias",
	)
}

func getDeviceRevision(ctx *context.Context, pciAddr *pciaddr.Address) string {
	paths := linuxpath.New(ctx)
	revisionPath := filepath.Join(
		paths.SysBusPciDevices,
		pciAddr.String(),
		"revision",
	)

	if _, err := os.Stat(revisionPath); err != nil {
		return ""
	}
	revision, err := ioutil.ReadFile(revisionPath)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(revision))
}

func getDeviceNUMANode(ctx *context.Context, pciAddr *pciaddr.Address) *topology.Node {
	paths := linuxpath.New(ctx)
	numaNodePath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "numa_node")

	if _, err := os.Stat(numaNodePath); err != nil {
		return nil
	}

	nodeIdx := util.SafeIntFromFile(ctx, numaNodePath)
	if nodeIdx == -1 {
		return nil
	}

	return &topology.Node{
		ID: nodeIdx,
	}
}

func getDeviceDriver(ctx *context.Context, pciAddr *pciaddr.Address) string {
	paths := linuxpath.New(ctx)
	driverPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "driver")

	if _, err := os.Stat(driverPath); err != nil {
		return ""
	}

	dest, err := os.Readlink(driverPath)
	if err != nil {
		return ""
	}
	return filepath.Base(dest)
}

type deviceModaliasInfo struct {
	vendorID     string
	productID    string
	subproductID string
	subvendorID  string
	classID      string
	subclassID   string
	progIfaceID  string
}

func parseModaliasFile(fp string) *deviceModaliasInfo {
	if _, err := os.Stat(fp); err != nil {
		return nil
	}
	data, err := ioutil.ReadFile(fp)
	if err != nil {
		return nil
	}

	return parseModaliasData(string(data))
}

func parseModaliasData(data string) *deviceModaliasInfo {
	// The modalias file is an encoded file that looks like this:
	//
	// $ cat /sys/devices/pci0000\:00/0000\:00\:03.0/0000\:03\:00.0/modalias
	// pci:v000010DEd00001C82sv00001043sd00008613bc03sc00i00
	//
	// It is interpreted like so:
	//
	// pci: -- ignore
	// v000010DE -- PCI vendor ID
	// d00001C82 -- PCI device ID (the product/model ID)
	// sv00001043 -- PCI subsystem vendor ID
	// sd00008613 -- PCI subsystem device ID (subdevice product/model ID)
	// bc03 -- PCI base class
	// sc00 -- PCI subclass
	// i00 -- programming interface
	vendorID := strings.ToLower(data[9:13])
	productID := strings.ToLower(data[18:22])
	subvendorID := strings.ToLower(data[28:32])
	subproductID := strings.ToLower(data[38:42])
	classID := data[44:46]
	subclassID := data[48:50]
	progIfaceID := data[51:53]
	return &deviceModaliasInfo{
		vendorID:     vendorID,
		productID:    productID,
		subproductID: subproductID,
		subvendorID:  subvendorID,
		classID:      classID,
		subclassID:   subclassID,
		progIfaceID:  progIfaceID,
	}
}

// Returns a pointer to a pcidb.Vendor struct matching the supplied vendor
// ID string. If no such vendor ID string could be found, returns the
// pcidb.Vendor struct populated with "unknown" vendor Name attribute and
// empty Products attribute.
func findPCIVendor(info *Info, vendorID string) *pcidb.Vendor {
	vendor := info.Vendors[vendorID]
	if vendor == nil {
		return &pcidb.Vendor{
			ID:       vendorID,
			Name:     util.UNKNOWN,
			Products: []*pcidb.Product{},
		}
	}
	return vendor
}

// Returns a pointer to a pcidb.Product struct matching the supplied vendor
// and product ID strings. If no such product could be found, returns the
// pcidb.Product struct populated with "unknown" product Name attribute and
// empty Subsystems attribute.
func findPCIProduct(
	info *Info,
	vendorID string,
	productID string,
) *pcidb.Product {
	product := info.Products[vendorID+productID]
	if product == nil {
		return &pcidb.Product{
			ID:         productID,
			Name:       util.UNKNOWN,
			Subsystems: []*pcidb.Product{},
		}
	}
	return product
}

// Returns a pointer to a pcidb.Product struct matching the supplied vendor,
// product, subvendor and subproduct ID strings. If no such product could be
// found, returns the pcidb.Product struct populated with "unknown" product
// Name attribute and empty Subsystems attribute.
func findPCISubsystem(
	info *Info,
	vendorID string,
	productID string,
	subvendorID string,
	subproductID string,
) *pcidb.Product {
	product := info.Products[vendorID+productID]
	subvendor := info.Vendors[subvendorID]
	if subvendor != nil && product != nil {
		for _, p := range product.Subsystems {
			if p.ID == subproductID {
				return p
			}
		}
	}
	return &pcidb.Product{
		VendorID: subvendorID,
		ID:       subproductID,
		Name:     util.UNKNOWN,
	}
}

// Returns a pointer to a pcidb.Class struct matching the supplied class ID
// string. If no such class ID string could be found, returns the
// pcidb.Class struct populated with "unknown" class Name attribute and
// empty Subclasses attribute.
func findPCIClass(info *Info, classID string) *pcidb.Class {
	class := info.Classes[classID]
	if class == nil {
		return &pcidb.Class{
			ID:         classID,
			Name:       util.UNKNOWN,
			Subclasses: []*pcidb.Subclass{},
		}
	}
	return class
}

// Returns a pointer to a pcidb.Subclass struct matching the supplied class
// and subclass ID strings.  If no such subclass could be found, returns the
// pcidb.Subclass struct populated with "unknown" subclass Name attribute
// and empty ProgrammingInterfaces attribute.
func findPCISubclass(
	info *Info,
	classID string,
	subclassID string,
) *pcidb.Subclass {
	class := info.Classes[classID]
	if class != nil {
		for _, sc := range class.Subclasses {
			if sc.ID == subclassID {
				return sc
			}
		}
	}
	return &pcidb.Subclass{
		ID:                    subclassID,
		Name:                  util.UNKNOWN,
		ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
	}
}

// Returns a pointer to a pcidb.ProgrammingInterface struct matching the
// supplied class, subclass and programming interface ID strings.  If no such
// programming interface could be found, returns the
// pcidb.ProgrammingInterface struct populated with "unknown" Name attribute
func findPCIProgrammingInterface(
	info *Info,
	classID string,
	subclassID string,
	progIfaceID string,
) *pcidb.ProgrammingInterface {
	subclass := findPCISubclass(info, classID, subclassID)
	for _, pi := range subclass.ProgrammingInterfaces {
		if pi.ID == progIfaceID {
			return pi
		}
	}
	return &pcidb.ProgrammingInterface{
		ID:   progIfaceID,
		Name: util.UNKNOWN,
	}
}

// GetDevice returns a pointer to a Device struct that describes the PCI
// device at the requested address. If no such device could be found, returns nil.
func (info *Info) GetDevice(address string) *Device {
	// check cached data first
	if dev := info.lookupDevice(address); dev != nil {
		return dev
	}

	pciAddr := pciaddr.FromString(address)
	if pciAddr == nil {
		info.ctx.Warn("error parsing the pci address %q", address)
		return nil
	}

	// no cached data, let's get the information from system.
	fp := getDeviceModaliasPath(info.ctx, pciAddr)
	if fp == "" {
		info.ctx.Warn("error finding modalias info for device %q", address)
		return nil
	}

	modaliasInfo := parseModaliasFile(fp)
	if modaliasInfo == nil {
		info.ctx.Warn("error parsing modalias info for device %q", address)
		return nil
	}

	device := info.getDeviceFromModaliasInfo(address, modaliasInfo)
	device.Revision = getDeviceRevision(info.ctx, pciAddr)
	if info.arch == topology.ARCHITECTURE_NUMA {
		device.Node = getDeviceNUMANode(info.ctx, pciAddr)
	}
	device.Driver = getDeviceDriver(info.ctx, pciAddr)
	return device
}

// ParseDevice returns a pointer to a Device given its describing data.
// The PCI device obtained this way may not exist in the system;
// use GetDevice to get a *Device which is found in the system
func (info *Info) ParseDevice(address, modalias string) *Device {
	modaliasInfo := parseModaliasData(modalias)
	if modaliasInfo == nil {
		return nil
	}
	return info.getDeviceFromModaliasInfo(address, modaliasInfo)
}

func (info *Info) getDeviceFromModaliasInfo(address string, modaliasInfo *deviceModaliasInfo) *Device {
	vendor := findPCIVendor(info, modaliasInfo.vendorID)
	product := findPCIProduct(
		info,
		modaliasInfo.vendorID,
		modaliasInfo.productID,
	)
	subsystem := findPCISubsystem(
		info,
		modaliasInfo.vendorID,
		modaliasInfo.productID,
		modaliasInfo.subvendorID,
		modaliasInfo.subproductID,
	)
	class := findPCIClass(info, modaliasInfo.classID)
	subclass := findPCISubclass(
		info,
		modaliasInfo.classID,
		modaliasInfo.subclassID,
	)
	progIface := findPCIProgrammingInterface(
		info,
		modaliasInfo.classID,
		modaliasInfo.subclassID,
		modaliasInfo.progIfaceID,
	)

	return &Device{
		Address:              address,
		Vendor:               vendor,
		Subsystem:            subsystem,
		Product:              product,
		Class:                class,
		Subclass:             subclass,
		ProgrammingInterface: progIface,
	}
}

// ListDevices returns a list of pointers to Device structs present on the
// host system
// DEPRECATED. Will be removed in v1.0. Please use
// github.com/jaypipes/pcidb to explore PCIDB information
func (info *Info) ListDevices() []*Device {
	paths := linuxpath.New(info.ctx)
	devs := make([]*Device, 0)
	// We scan the /sys/bus/pci/devices directory which contains a collection
	// of symlinks. The names of the symlinks are all the known PCI addresses
	// for the host. For each address, we grab a *Device matching the
	// address and append to the returned array.
	links, err := ioutil.ReadDir(paths.SysBusPciDevices)
	if err != nil {
		info.ctx.Warn("failed to read /sys/bus/pci/devices")
		return nil
	}
	var dev *Device
	for _, link := range links {
		addr := link.Name()
		dev = info.GetDevice(addr)
		if dev == nil {
			info.ctx.Warn("failed to get device information for PCI address %s", addr)
		} else {
			devs = append(devs, dev)
		}
	}
	return devs
}
<|endoftext|>


<commit_before>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 (
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts"
	"github.com/sapcc/limes/pkg/limes"
	"regexp"
)

type swiftPlugin struct{}

var swiftResources = []limes.ResourceInfo{
	limes.ResourceInfo{
		Name: "capacity",
		Unit: limes.UnitBytes,
	},
}

//TODO Make Auth prefix configurable
var urlRegex = regexp.MustCompile("v1/AUTH_([a-zA-Z0-9]+)")

func init() {
	limes.RegisterPlugin(&swiftPlugin{})
}

//ServiceType implements the limes.Plugin interface.
func (p *swiftPlugin) ServiceType() string {
	return "object-store"
}

//Resources implements the limes.Plugin interface.
func (p *swiftPlugin) Resources() []limes.ResourceInfo {
	return swiftResources
}

func (p *swiftPlugin) Client(driver limes.Driver) (*gophercloud.ServiceClient, error) {
	return openstack.NewObjectStorageV1(driver.Client(),
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.Plugin interface.
func (p *swiftPlugin) Scrape(driver limes.Driver, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.ProjectClient(driver, projectUUID)
	if err != nil {
		return nil, err
	}

	result := accounts.Get(client, accounts.GetOpts{})
	metaData, err := result.ExtractMetadata()
	if err != nil {
		return nil, err
	}

	var quota int64 = -1
	quota_header, ok := metaData["X-Account-Meta-Quota-Bytes"]
	if ok {
		quota = int64(quota_header)
	}

	return map[string]limes.ResourceData{
		"capacity": limes.ResourceData{
			Quota: quota,
			Usage: uint64(metaData["X-Account-Bytes-Used"]),
		},
	}, nil
}

//SetQuota implements the limes.Plugin interface.
func (p *swiftPlugin) SetQuota(driver limes.Driver, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.ProjectClient(driver, projectUUID)
	if err != nil {
		return err
	}

	return accounts.Update(client, accounts.UpdateOpts{
		Metadata: map[string]string{"X-Account-Meta-Quota-Bytes": string(quotas["capacity"])},
	}).Err
}

//Capacity implements the limes.Plugin interface.
func (p *swiftPlugin) Capacity(driver limes.Driver) (map[string]uint64, error) {
	//TODO implement
	return map[string]uint64{}, nil
}

//Get the project specif storage URL scoped client
func (p *swiftPlugin) ProjectClient(driver limes.Driver, projectUUID string) (*gophercloud.ServiceClient, error) {
	client, err := p.Client(driver)
	if err != nil {
		return nil, err
	}

	//We act as Reseller_Admin here, but cannot use the endpoint url returned from catalogue
	//Replace the resellers project id with the requested one
	client.Endpoint = urlRegex.ReplaceAllString(client.Endpoint, projectUUID)
	return client, nil
}<commit_msg>swift quota plugin<commit_after>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 (
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"
	"net/http"
	"regexp"
	"strconv"
)

type swiftPlugin struct{}

var swiftResources = []limes.ResourceInfo{
	limes.ResourceInfo{
		Name: "capacity",
		Unit: limes.UnitBytes,
	},
}

//TODO Make Auth prefix configurable
var urlRegex = regexp.MustCompile("(v1/AUTH_)[a-zA-Z0-9]+")

func init() {
	limes.RegisterQuotaPlugin(&swiftPlugin{})
}

//ServiceType implements the limes.Plugin interface.
func (p *swiftPlugin) ServiceType() string {
	return "object-store"
}

//Resources implements the limes.Plugin interface.
func (p *swiftPlugin) Resources() []limes.ResourceInfo {
	return swiftResources
}

func (p *swiftPlugin) Client(driver limes.Driver) (*gophercloud.ServiceClient, error) {
	return openstack.NewObjectStorageV1(driver.Client(),
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.Plugin interface.
func (p *swiftPlugin) Scrape(driver limes.Driver, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return nil, err
	}

	//Get account metadata
	account := getAccount(client, projectUUID)
	if account == nil {
		//Swift account does not exist, but the keystone project
		return map[string]limes.ResourceData{
			"capacity": limes.ResourceData{
				Quota: 0,
				Usage: 0,
			},
		}, nil
	} else if account.Err != nil {
		return nil, err
	}

	//Extract quota, if set
	var quota int64 = -1
	quota_header := account.Header.Get("X-Account-Meta-Quota-Bytes")
	if quota_header != "" {
		quota, _ = strconv.ParseInt(quota_header, 10, 64)
	}

	//Extract usage
	var usage int64 = 0
	usage_header := account.Header.Get("X-Account-Bytes-Used")
	if usage_header != "" {
		usage, _ = strconv.ParseInt(usage_header, 10, 64)
	}

	util.LogDebug("Swift Account %s: quota '%d' - usage '%d'", projectUUID, quota, usage)
	return map[string]limes.ResourceData{
		"capacity": limes.ResourceData{
			Quota: quota,
			Usage: uint64(usage),
		},
	}, nil
}

//SetQuota implements the limes.Plugin interface.
func (p *swiftPlugin) SetQuota(driver limes.Driver, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return err
	}

	headers := make(map[string]string)
	headers["X-Account-Meta-Quota-Bytes"] = string(quotas["capacity"])
	//this header brought to you by https://github.com/sapcc/swift-addons
	headers["X-Account-Project-Domain-Id-Override"] = domainUUID

	result, err := updateAccount(client, headers)
	if result.StatusCode == http.StatusNotFound && quotas["capacity"] > 0 {
		//account does not exist yet - if there is a non-zero quota, enable it now
		_, err = putAccount(client, headers)
		if err != nil {
			util.LogInfo("Swift Account %s created", projectUUID)
		}
	}
	return err
}

//Get the project scoped cliet with specific storage URL
func (p *swiftPlugin) projectClient(driver limes.Driver, projectUUID string) (*gophercloud.ServiceClient, error) {
	client, err := p.Client(driver)
	if err != nil {
		return nil, err
	}

	//We act as Reseller_Admin here, but cannot use the endpoint url returned from catalogue
	//Replace the resellers project id with the requested one
	client.Endpoint = urlRegex.ReplaceAllString(client.Endpoint, "${1}"+projectUUID)
	util.LogDebug(client.Endpoint)
	return client, nil
}

//Wrapping the accounts.Get because the swift account might not be created if account_auto_create = false
func getAccount(client *gophercloud.ServiceClient, projectUUID string) *accounts.GetResult {
	//Get account metadata
	var result accounts.GetResult
	result = accounts.Get(client, accounts.GetOpts{})
	if _, ok := result.Err.(gophercloud.ErrDefault404); ok {
		//Swift Account does not exist. This is expected esp. if account_auto_create is disabled
		util.LogDebug("Swift Account %s does not exist", projectUUID)
		return nil
	}
	return &result
}

//Issue a POST request to the account with own headers
func updateAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("POST", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{200, 204},
	})
}

//Issue a PUT request to the account with own headers
func putAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("PUT", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{201},
	})
}
<|endoftext|>


<commit_before>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 (
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"
	"net/http"
	"regexp"
	"strconv"
)

type swiftPlugin struct{}

var swiftResources = []limes.ResourceInfo{
	limes.ResourceInfo{
		Name: "capacity",
		Unit: limes.UnitBytes,
	},
}

//TODO Make Auth prefix configurable
var urlRegex = regexp.MustCompile("(v1/AUTH_)[a-zA-Z0-9]+")

func init() {
	limes.RegisterQuotaPlugin(&swiftPlugin{})
}

//ServiceType implements the limes.Plugin interface.
func (p *swiftPlugin) ServiceType() string {
	return "object-store"
}

//Resources implements the limes.Plugin interface.
func (p *swiftPlugin) Resources() []limes.ResourceInfo {
	return swiftResources
}

func (p *swiftPlugin) Client(driver limes.Driver) (*gophercloud.ServiceClient, error) {
	return openstack.NewObjectStorageV1(driver.Client(),
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.Plugin interface.
func (p *swiftPlugin) Scrape(driver limes.Driver, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return nil, err
	}

	//Get account metadata
	account := getAccount(client, projectUUID)
	if account == nil {
		//Swift account does not exist, but the keystone project
		return map[string]limes.ResourceData{
			"capacity": limes.ResourceData{
				Quota: 0,
				Usage: 0,
			},
		}, nil
	} else if account.Err != nil {
		return nil, err
	}

	//Extract quota, if set
	var quota int64 = -1
	quota_header := account.Header.Get("X-Account-Meta-Quota-Bytes")
	if quota_header != "" {
		quota, _ = strconv.ParseInt(quota_header, 10, 64)
	}

	//Extract usage
	var usage int64 = 0
	usage_header := account.Header.Get("X-Account-Bytes-Used")
	if usage_header != "" {
		usage, _ = strconv.ParseInt(usage_header, 10, 64)
	}

	util.LogDebug("Swift Account %s: quota '%d' - usage '%d'", projectUUID, quota, usage)
	return map[string]limes.ResourceData{
		"capacity": limes.ResourceData{
			Quota: quota,
			Usage: uint64(usage),
		},
	}, nil
}

//SetQuota implements the limes.Plugin interface.
func (p *swiftPlugin) SetQuota(driver limes.Driver, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return err
	}

	headers := make(map[string]string)
	headers["X-Account-Meta-Quota-Bytes"] = string(quotas["capacity"])
	//this header brought to you by https://github.com/sapcc/swift-addons
	headers["X-Account-Project-Domain-Id-Override"] = domainUUID

	result, err := updateAccount(client, headers)
	if result.StatusCode == http.StatusNotFound && quotas["capacity"] > 0 {
		//account does not exist yet - if there is a non-zero quota, enable it now
		_, err = putAccount(client, headers)
		if err != nil {
			util.LogInfo("Swift Account %s created", projectUUID)
		}
	}
	return err
}

//Get the project scoped cliet with specific storage URL
func (p *swiftPlugin) projectClient(driver limes.Driver, projectUUID string) (*gophercloud.ServiceClient, error) {
	client, err := p.Client(driver)
	if err != nil {
		return nil, err
	}

	//We act as Reseller_Admin here, but cannot use the endpoint url returned from catalogue
	//Replace the resellers project id with the requested one
	client.Endpoint = urlRegex.ReplaceAllString(client.Endpoint, "${1}"+projectUUID)
	util.LogDebug(client.Endpoint)
	return client, nil
}

//Wrapping the accounts.Get because the swift account might not be created if account_auto_create = false
func getAccount(client *gophercloud.ServiceClient, projectUUID string) *accounts.GetResult {
	//Get account metadata
	var result accounts.GetResult
	result = accounts.Get(client, accounts.GetOpts{})
	if _, ok := result.Err.(gophercloud.ErrDefault404); ok {
		//Swift Account does not exist. This is expected esp. if account_auto_create is disabled
		util.LogDebug("Swift Account %s does not exist", projectUUID)
		return nil
	}
	return &result
}

//Issue a POST request to the account with own headers
func updateAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("POST", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{200, 204},
	})
}

//Issue a PUT request to the account with own headers
func putAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("PUT", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{201},
	})
}
<commit_msg>golint<commit_after>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 (
	"net/http"
	"regexp"
	"strconv"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"
)

type swiftPlugin struct{}

var swiftResources = []limes.ResourceInfo{
	limes.ResourceInfo{
		Name: "capacity",
		Unit: limes.UnitBytes,
	},
}

//TODO Make Auth prefix configurable
var urlRegex = regexp.MustCompile("(v1/AUTH_)[a-zA-Z0-9]+")

func init() {
	limes.RegisterQuotaPlugin(&swiftPlugin{})
}

//ServiceType implements the limes.Plugin interface.
func (p *swiftPlugin) ServiceType() string {
	return "object-store"
}

//Resources implements the limes.Plugin interface.
func (p *swiftPlugin) Resources() []limes.ResourceInfo {
	return swiftResources
}

func (p *swiftPlugin) Client(driver limes.Driver) (*gophercloud.ServiceClient, error) {
	return openstack.NewObjectStorageV1(driver.Client(),
		gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic},
	)
}

//Scrape implements the limes.Plugin interface.
func (p *swiftPlugin) Scrape(driver limes.Driver, domainUUID, projectUUID string) (map[string]limes.ResourceData, error) {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return nil, err
	}

	//Get account metadata
	account := getAccount(client, projectUUID)
	if account == nil {
		//Swift account does not exist, but the keystone project
		return map[string]limes.ResourceData{
			"capacity": limes.ResourceData{
				Quota: 0,
				Usage: 0,
			},
		}, nil
	} else if account.Err != nil {
		return nil, err
	}

	//Extract quota, if set
	var quota int64 = -1
	quotaHeader := account.Header.Get("X-Account-Meta-Quota-Bytes")
	if quotaHeader != "" {
		quota, _ = strconv.ParseInt(quotaHeader, 10, 64)
	}

	//Extract usage
	var usage int64
	usageHeader := account.Header.Get("X-Account-Bytes-Used")
	if usageHeader != "" {
		usage, _ = strconv.ParseInt(usageHeader, 10, 64)
	}

	util.LogDebug("Swift Account %s: quota '%d' - usage '%d'", projectUUID, quota, usage)
	return map[string]limes.ResourceData{
		"capacity": limes.ResourceData{
			Quota: quota,
			Usage: uint64(usage),
		},
	}, nil
}

//SetQuota implements the limes.Plugin interface.
func (p *swiftPlugin) SetQuota(driver limes.Driver, domainUUID, projectUUID string, quotas map[string]uint64) error {
	client, err := p.projectClient(driver, projectUUID)
	if err != nil {
		return err
	}

	headers := make(map[string]string)
	headers["X-Account-Meta-Quota-Bytes"] = string(quotas["capacity"])
	//this header brought to you by https://github.com/sapcc/swift-addons
	headers["X-Account-Project-Domain-Id-Override"] = domainUUID

	result, err := updateAccount(client, headers)
	if result.StatusCode == http.StatusNotFound && quotas["capacity"] > 0 {
		//account does not exist yet - if there is a non-zero quota, enable it now
		_, err = putAccount(client, headers)
		if err != nil {
			util.LogInfo("Swift Account %s created", projectUUID)
		}
	}
	return err
}

//Get the project scoped cliet with specific storage URL
func (p *swiftPlugin) projectClient(driver limes.Driver, projectUUID string) (*gophercloud.ServiceClient, error) {
	client, err := p.Client(driver)
	if err != nil {
		return nil, err
	}

	//We act as Reseller_Admin here, but cannot use the endpoint url returned from catalogue
	//Replace the resellers project id with the requested one
	client.Endpoint = urlRegex.ReplaceAllString(client.Endpoint, "${1}"+projectUUID)
	util.LogDebug(client.Endpoint)
	return client, nil
}

//Wrapping the accounts.Get because the swift account might not be created if account_auto_create = false
func getAccount(client *gophercloud.ServiceClient, projectUUID string) *accounts.GetResult {
	//Get account metadata
	var result accounts.GetResult
	result = accounts.Get(client, accounts.GetOpts{})
	if _, ok := result.Err.(gophercloud.ErrDefault404); ok {
		//Swift Account does not exist. This is expected esp. if account_auto_create is disabled
		util.LogDebug("Swift Account %s does not exist", projectUUID)
		return nil
	}
	return &result
}

//Issue a POST request to the account with own headers
func updateAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("POST", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{200, 204},
	})
}

//Issue a PUT request to the account with own headers
func putAccount(c *gophercloud.ServiceClient, headers map[string]string) (*http.Response, error) {
	return c.Request("PUT", c.Endpoint, &gophercloud.RequestOpts{
		MoreHeaders: headers,
		OkCodes:     []int{201},
	})
}
<|endoftext|>


<commit_before>package pubsub

import (
	"strings"
)

type Topic string

func (t Topic) String() string {
	if i := strings.Index(string(t), "://"); i > 0 {
		return string(t)[i+3:]
	}
	return string(t)
}

func (t Topic) Protocol(protocol string) bool {
	return strings.Index(string(t), protocol+"://") >= 0
}

type Publisher interface {
	Publish(topic Topic, message []byte) error
}

type Subscriber interface {
	Subscribe(topic Topic) (<-chan []byte, error)
}

type PubSub interface {
	Publisher
	Subscriber
	Close()
}
<commit_msg>Publisher implements writer<commit_after>package pubsub

import (
	"strings"
)

type Topic string

func (t Topic) String() string {
	if i := strings.Index(string(t), "://"); i > 0 {
		return string(t)[i+3:]
	}
	return string(t)
}

func (t Topic) Protocol(protocol string) bool {
	return strings.Index(string(t), protocol+"://") >= 0
}

type Publisher interface {
	Publish(topic Topic, message []byte) error
}

type Subscriber interface {
	Subscribe(topic Topic) (<-chan []byte, error)
}

type PubSub interface {
	Publisher
	Subscriber
	Close()
}

type writer struct {
	pub   Publisher
	topic Topic
}

func AsWriter(topic Topic, pub Publisher) *writer {
	return &writer{
		topic: topic,
		pub:   pub,
	}
}

func (this *writer) Write(p []byte) (n int, err error) {
	n = len(p)
	this.pub.Publish(this.topic, p)
	return n, nil
}
<|endoftext|>


<commit_before>package routes

import (
	"github.com/fokal/fokal/pkg/handler"
	"github.com/fokal/fokal/pkg/model"
	"github.com/fokal/fokal/pkg/security/permissions"
	"github.com/fokal/fokal/pkg/status"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
)

func RegisterStatusRoutes(state *handler.State, api *mux.Router, chain alice.Chain) {

	head := api.Methods("HEAD").Subrouter()
	head.Handle("/status",
		chain.Append(
			permissions.Middleware{State: state,
				T:          permissions.CanView,
				TargetType: model.Images,
				M:          permissions.PermissionMiddle,
			}.Handler).Then(handler.Handler{State: state, H: status.StatusHandler}))

}
<commit_msg>Removed permissions middleware for status endpoint<commit_after>package routes

import (
	"github.com/fokal/fokal/pkg/handler"
	"github.com/fokal/fokal/pkg/status"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
)

func RegisterStatusRoutes(state *handler.State, api *mux.Router, chain alice.Chain) {

	head := api.Methods("HEAD").Subrouter()
	head.Handle("/stauts",
		chain.Then(handler.Handler{State: state, H: status.StatusHandler}))

}
<|endoftext|>