<commit_before>package install

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
)

// The Executor will carry out the installation plan
type Executor interface {
	Install(p *Plan, av *AnsibleVars) error
	GetVars(p *Plan, options *CliOpts) (*AnsibleVars, error)
}

type ansibleExecutor struct {
	out        io.Writer
	errOut     io.Writer
	pythonPath string
	ansibleDir string
	certsDir   string
}

// CommandLineVars returns "--extra-vars" as json string
func (av *AnsibleVars) CommandLineVars() (string, error) {
	b, err := json.Marshal(av)
	if err != nil {
		return "", fmt.Errorf("error marshaling ansible vars")
	}
	return string(b), nil
}

// NewAnsibleExecutor returns an ansible based installation executor.
func NewAnsibleExecutor(out io.Writer, errOut io.Writer, certsDir string) (Executor, error) {
	ppath, err := getPythonPath()
	if err != nil {
		return nil, err
	}
	return &ansibleExecutor{
		out:        out,
		errOut:     errOut,
		pythonPath: ppath,
		ansibleDir: "ansible", // TODO: What's the best way to handle this?
		certsDir:   certsDir,
	}, nil
}

func (e *ansibleExecutor) GetVars(p *Plan, options *CliOpts) (*AnsibleVars, error) {
	tlsDir, err := filepath.Abs(e.certsDir)
	if err != nil {
		return &AnsibleVars{}, fmt.Errorf("error getting absolute path from cert location: %v", err)
	}

	dnsServiceIP, err := getDNSServiceIP(p)
	if err != nil {
		return &AnsibleVars{}, fmt.Errorf("error getting DNS servie IP address: %v", err)
	}

	vars := AnsibleVars{
		ClusterName:                   p.Cluster.Name,
		AdminPassword:                 p.Cluster.AdminPassword,
		TLSDirectory:                  tlsDir,
		KubernetesServicesCIDR:        p.Cluster.Networking.ServiceCIDRBlock,
		KubernetesPodsCIDR:            p.Cluster.Networking.PodCIDRBlock,
		KubernetesDNSServiceIP:        dnsServiceIP,
		CalicoNetworkType:             p.Cluster.Networking.Type,
		LocalRepository:               p.Cluster.LocalRepository,
		ForceEtcdRestart:              strconv.FormatBool(options.RestartEtcdService),
		ForceApiserverRestart:         strconv.FormatBool(options.RestartKubernetesService),
		ForceControllerManagerRestart: strconv.FormatBool(options.RestartKubernetesService),
		ForceSchedulerRestart:         strconv.FormatBool(options.RestartKubernetesService),
		ForceProxyRestart:             strconv.FormatBool(options.RestartKubernetesService),
		ForceKubeletRestart:           strconv.FormatBool(options.RestartKubernetesService),
		ForceCalicoRestart:            strconv.FormatBool(options.RestartCalicoService),
		ForceDockerRestart:            strconv.FormatBool(options.RestartDockerService),
	}

	return &vars, nil
}

func (e *ansibleExecutor) Install(p *Plan, av *AnsibleVars) error {
	// build inventory
	inventory := buildNodeInventory(p)
	inventoryFile := filepath.Join(e.ansibleDir, "inventory.ini")
	err := ioutil.WriteFile(inventoryFile, inventory, 0644)
	if err != nil {
		return fmt.Errorf("error writing ansible inventory file: %v", err)
	}

	// run ansible
	playbook := filepath.Join(e.ansibleDir, "playbooks", "kubernetes.yaml")
	err = e.runAnsiblePlaybook(inventoryFile, playbook, *av)
	if err != nil {
		return fmt.Errorf("error running ansible playbook: %v", err)
	}

	return nil
}

func (e *ansibleExecutor) runAnsiblePlaybook(inventoryFile, playbookFile string, vars AnsibleVars) error {
	extraVars, err := vars.CommandLineVars()
	if err != nil {
		return fmt.Errorf("error getting vars: %v", err)
	}

	cmd := exec.Command(filepath.Join(e.ansibleDir, "bin", "ansible-playbook"), "-i", inventoryFile, "-s", playbookFile, "--extra-vars", extraVars)
	cmd.Stdout = e.out
	cmd.Stderr = e.errOut
	os.Setenv("PYTHONPATH", e.pythonPath)
	// Ansible config
	os.Setenv("ANSIBLE_HOST_KEY_CHECKING", "False")

	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error running playbook: %v", err)
	}

	return nil
}

func buildNodeInventory(p *Plan) []byte {
	inv := &bytes.Buffer{}
	writeHostGroup(inv, "etcd", p.Etcd.Nodes, p.Cluster.SSH)
	writeHostGroup(inv, "master", p.Master.Nodes, p.Cluster.SSH)
	writeHostGroup(inv, "worker", p.Worker.Nodes, p.Cluster.SSH)

	return inv.Bytes()
}

func writeHostGroup(inv io.Writer, groupName string, nodes []Node, ssh SSHConfig) {
	fmt.Fprintf(inv, "[%s]\n", groupName)
	for _, n := range nodes {
		internalIP := n.IP
		if n.InternalIP != "" {
			internalIP = n.InternalIP
		}
		fmt.Fprintf(inv, "%s ansible_host=%s internal_ipv4=%s ansible_ssh_private_key_file=%s ansible_port=%d ansible_user=%s\n", n.Host, n.IP, internalIP, ssh.Key, ssh.Port, ssh.User)
	}
}

func getPythonPath() (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("error getting working dir: %v", err)
	}
	lib := filepath.Join(wd, "ansible", "lib", "python2.7", "site-packages")
	lib64 := filepath.Join(wd, "ansible", "lib64", "python2.7", "site-packages")
	return fmt.Sprintf("%s:%s", lib, lib64), nil
}
<commit_msg>Enabled very verbose logging for the call to Ansible. This is a temporary measure to help with triage of integration tests.<commit_after>package install

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
)

// The Executor will carry out the installation plan
type Executor interface {
	Install(p *Plan, av *AnsibleVars) error
	GetVars(p *Plan, options *CliOpts) (*AnsibleVars, error)
}

type ansibleExecutor struct {
	out        io.Writer
	errOut     io.Writer
	pythonPath string
	ansibleDir string
	certsDir   string
}

// CommandLineVars returns "--extra-vars" as json string
func (av *AnsibleVars) CommandLineVars() (string, error) {
	b, err := json.Marshal(av)
	if err != nil {
		return "", fmt.Errorf("error marshaling ansible vars")
	}
	return string(b), nil
}

// NewAnsibleExecutor returns an ansible based installation executor.
func NewAnsibleExecutor(out io.Writer, errOut io.Writer, certsDir string) (Executor, error) {
	ppath, err := getPythonPath()
	if err != nil {
		return nil, err
	}
	return &ansibleExecutor{
		out:        out,
		errOut:     errOut,
		pythonPath: ppath,
		ansibleDir: "ansible", // TODO: What's the best way to handle this?
		certsDir:   certsDir,
	}, nil
}

func (e *ansibleExecutor) GetVars(p *Plan, options *CliOpts) (*AnsibleVars, error) {
	tlsDir, err := filepath.Abs(e.certsDir)
	if err != nil {
		return &AnsibleVars{}, fmt.Errorf("error getting absolute path from cert location: %v", err)
	}

	dnsServiceIP, err := getDNSServiceIP(p)
	if err != nil {
		return &AnsibleVars{}, fmt.Errorf("error getting DNS servie IP address: %v", err)
	}

	vars := AnsibleVars{
		ClusterName:                   p.Cluster.Name,
		AdminPassword:                 p.Cluster.AdminPassword,
		TLSDirectory:                  tlsDir,
		KubernetesServicesCIDR:        p.Cluster.Networking.ServiceCIDRBlock,
		KubernetesPodsCIDR:            p.Cluster.Networking.PodCIDRBlock,
		KubernetesDNSServiceIP:        dnsServiceIP,
		CalicoNetworkType:             p.Cluster.Networking.Type,
		LocalRepository:               p.Cluster.LocalRepository,
		ForceEtcdRestart:              strconv.FormatBool(options.RestartEtcdService),
		ForceApiserverRestart:         strconv.FormatBool(options.RestartKubernetesService),
		ForceControllerManagerRestart: strconv.FormatBool(options.RestartKubernetesService),
		ForceSchedulerRestart:         strconv.FormatBool(options.RestartKubernetesService),
		ForceProxyRestart:             strconv.FormatBool(options.RestartKubernetesService),
		ForceKubeletRestart:           strconv.FormatBool(options.RestartKubernetesService),
		ForceCalicoRestart:            strconv.FormatBool(options.RestartCalicoService),
		ForceDockerRestart:            strconv.FormatBool(options.RestartDockerService),
	}

	return &vars, nil
}

func (e *ansibleExecutor) Install(p *Plan, av *AnsibleVars) error {
	// build inventory
	inventory := buildNodeInventory(p)
	inventoryFile := filepath.Join(e.ansibleDir, "inventory.ini")
	err := ioutil.WriteFile(inventoryFile, inventory, 0644)
	if err != nil {
		return fmt.Errorf("error writing ansible inventory file: %v", err)
	}

	// run ansible
	playbook := filepath.Join(e.ansibleDir, "playbooks", "kubernetes.yaml")
	err = e.runAnsiblePlaybook(inventoryFile, playbook, *av)
	if err != nil {
		return fmt.Errorf("error running ansible playbook: %v", err)
	}

	return nil
}

func (e *ansibleExecutor) runAnsiblePlaybook(inventoryFile, playbookFile string, vars AnsibleVars) error {
	extraVars, err := vars.CommandLineVars()
	if err != nil {
		return fmt.Errorf("error getting vars: %v", err)
	}

	cmd := exec.Command(filepath.Join(e.ansibleDir, "bin", "ansible-playbook"), "-i", inventoryFile, "-s", playbookFile, "-vvvvvvv", "--extra-vars", extraVars)
	cmd.Stdout = e.out
	cmd.Stderr = e.errOut
	os.Setenv("PYTHONPATH", e.pythonPath)
	// Ansible config
	os.Setenv("ANSIBLE_HOST_KEY_CHECKING", "False")

	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("error running playbook: %v", err)
	}

	return nil
}

func buildNodeInventory(p *Plan) []byte {
	inv := &bytes.Buffer{}
	writeHostGroup(inv, "etcd", p.Etcd.Nodes, p.Cluster.SSH)
	writeHostGroup(inv, "master", p.Master.Nodes, p.Cluster.SSH)
	writeHostGroup(inv, "worker", p.Worker.Nodes, p.Cluster.SSH)

	return inv.Bytes()
}

func writeHostGroup(inv io.Writer, groupName string, nodes []Node, ssh SSHConfig) {
	fmt.Fprintf(inv, "[%s]\n", groupName)
	for _, n := range nodes {
		internalIP := n.IP
		if n.InternalIP != "" {
			internalIP = n.InternalIP
		}
		fmt.Fprintf(inv, "%s ansible_host=%s internal_ipv4=%s ansible_ssh_private_key_file=%s ansible_port=%d ansible_user=%s\n", n.Host, n.IP, internalIP, ssh.Key, ssh.Port, ssh.User)
	}
}

func getPythonPath() (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("error getting working dir: %v", err)
	}
	lib := filepath.Join(wd, "ansible", "lib", "python2.7", "site-packages")
	lib64 := filepath.Join(wd, "ansible", "lib64", "python2.7", "site-packages")
	return fmt.Sprintf("%s:%s", lib, lib64), nil
}
<|endoftext|>


<commit_before>// Copyright © 2021 The Knative 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 install

import (
	"fmt"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

var servingVersion = "1.1.0"
var kourierVersion = "1.1.0"
var eventingVersion = "1.1.0"

// Kourier installs Kourier networking layer from Github YAML files
func Kourier() error {
	fmt.Println("🕸 Installing Kourier networking layer v" + kourierVersion + " ...")

	if err := retryingApply("https://github.com/knative-sandbox/net-kourier/releases/download/knative-v" + kourierVersion + "/kourier.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}
	if err := waitForPodsReady("kourier-system"); err != nil {
		return fmt.Errorf("kourier: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("serving: %w", err)
	}
	fmt.Println("    Kourier installed...")

	ingress := exec.Command("kubectl", "patch", "configmap/config-network", "--namespace", "knative-serving", "--type", "merge", "--patch", "{\"data\":{\"ingress.class\":\"kourier.ingress.networking.knative.dev\"}}")
	if err := runCommand(ingress); err != nil {
		return fmt.Errorf("ingress error: %w", err)
	}
	fmt.Println("    Ingress patched...")

	fmt.Println("    Finished installing Kourier Networking layer")

	return nil
}

// KourierKind runs the kind-specific setup for Kourier
func KourierKind() error {
	fmt.Println("🕸 Configuring Kourier for Kind...")

	config := `apiVersion: v1
kind: Service
metadata:
  name: kourier-ingress
  namespace: kourier-system
  labels:
    networking.knative.dev/ingress-provider: kourier
spec:
  type: NodePort
  selector:
    app: 3scale-kourier-gateway
  ports:
    - name: http2
      nodePort: 31080
      port: 80
      targetPort: 8080`

	kourierIngress := exec.Command("kubectl", "apply", "-f", "-")
	kourierIngress.Stdin = strings.NewReader(config)
	if err := runCommand(kourierIngress); err != nil {
		return fmt.Errorf("kourier service: %w", err)
	}

	fmt.Println("    Kourier service installed...")

	domainDns := exec.Command("kubectl", "patch", "configmap", "-n", "knative-serving", "config-domain", "-p", "{\"data\": {\"127.0.0.1.sslip.io\": \"\"}}")
	if err := domainDns.Run(); err != nil {
		return fmt.Errorf("domain dns: %w", err)
	}
	fmt.Println("    Domain DNS set up...")
	fmt.Println("    Finished configuring Kourier")

	return nil
}

// KourierMinikube runs the minikube-specific setup for Kourier
func KourierMinikube() error {
	fmt.Println("🕸 Configuring Kourier for Minikube...")

	if err := retryingApply("https://github.com/knative/serving/releases/download/knative-v" + servingVersion + "/serving-default-domain.yaml"); err != nil {
		return fmt.Errorf("default domain: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Domain DNS set up...")

	// For windows and mac users, do not automatically spawn a tunnel
	// Instead, they will be directed to create one manually after
	// the plugin finishes
	if runtime.GOOS != "window" && runtime.GOOS != "darwin" {
		tunnel := exec.Command("minikube", "tunnel", "--profile", "knative")
		if err := tunnel.Start(); err != nil {
			return fmt.Errorf("tunnel: %w", err)
		}
		fmt.Println("    Minikube tunnel...")
	}

	fmt.Println("    Finished configuring Kourier")
	return nil
}

// Serving installs Knative Serving from Github YAML files
func Serving() error {
	fmt.Println("🍿 Installing Knative Serving v" + servingVersion + " ...")
	baseURL := "https://github.com/knative/serving/releases/download/knative-v" + servingVersion

	if err := retryingApply(baseURL + "/serving-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/serving-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Core installed...")

	fmt.Println("    Finished installing Knative Serving")

	return nil
}

// Eventing installs Knative Eventing from Github YAML files
func Eventing() error {
	fmt.Println("🔥 Installing Knative Eventing v" + eventingVersion + " ... ")
	baseURL := "https://github.com/knative/eventing/releases/download/knative-v" + eventingVersion

	if err := retryingApply(baseURL + "/eventing-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/eventing-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("core: %w", err)
	}
	fmt.Println("    Core installed...")

	if err := retryingApply(baseURL + "/in-memory-channel.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("channel: %w", err)
	}
	fmt.Println("    In-memory channel installed...")

	if err := retryingApply(baseURL + "/mt-channel-broker.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("broker: %w", err)
	}
	fmt.Println("    Mt-channel broker installed...")

	config := `apiVersion: eventing.knative.dev/v1
kind: broker
metadata:
 name: example-broker
 namespace: default`

	exampleBroker := exec.Command("kubectl", "apply", "-f", "-")
	exampleBroker.Stdin = strings.NewReader(config)
	if err := runCommand(exampleBroker); err != nil {
		return fmt.Errorf("example broker: %w", err)
	}

	fmt.Println("    Example broker installed...")
	fmt.Println("    Finished installing Knative Eventing")

	return nil
}

func runCommand(c *exec.Cmd) error {
	if out, err := c.CombinedOutput(); err != nil {
		fmt.Println(string(out))
		return err
	}
	return nil
}

// retryingApply retries a kubectl apply call with the given path 3 times, sleeping
// for 10s between each try.
func retryingApply(path string) error {
	cmd := exec.Command("kubectl", "apply", "-f", path)
	var err error
	for i := 0; i < 3; i++ {
		err = runCommand(cmd)
		if err == nil {
			break
		}
		time.Sleep(10 * time.Second)
	}
	return err
}

// waitForCRDsEstablished waits for all CRDs to be established.
func waitForCRDsEstablished() error {
	return runCommand(exec.Command("kubectl", "wait", "--for=condition=Established", "--all", "crd"))
}

// waitForPodsReady waits for all pods in the given namespace to be ready.
func waitForPodsReady(ns string) error {
	return runCommand(exec.Command("kubectl", "wait", "pod", "--timeout=10m", "--for=condition=Ready", "-l", "!job-name", "-n", ns))
}
<commit_msg>updates knative components to v1.2 (#210)<commit_after>// Copyright © 2021 The Knative 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 install

import (
	"fmt"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

var servingVersion = "1.2.0"
var kourierVersion = "1.2.0"
var eventingVersion = "1.2.0"

// Kourier installs Kourier networking layer from Github YAML files
func Kourier() error {
	fmt.Println("🕸 Installing Kourier networking layer v" + kourierVersion + " ...")

	if err := retryingApply("https://github.com/knative-sandbox/net-kourier/releases/download/knative-v" + kourierVersion + "/kourier.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}
	if err := waitForPodsReady("kourier-system"); err != nil {
		return fmt.Errorf("kourier: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("serving: %w", err)
	}
	fmt.Println("    Kourier installed...")

	ingress := exec.Command("kubectl", "patch", "configmap/config-network", "--namespace", "knative-serving", "--type", "merge", "--patch", "{\"data\":{\"ingress.class\":\"kourier.ingress.networking.knative.dev\"}}")
	if err := runCommand(ingress); err != nil {
		return fmt.Errorf("ingress error: %w", err)
	}
	fmt.Println("    Ingress patched...")

	fmt.Println("    Finished installing Kourier Networking layer")

	return nil
}

// KourierKind runs the kind-specific setup for Kourier
func KourierKind() error {
	fmt.Println("🕸 Configuring Kourier for Kind...")

	config := `apiVersion: v1
kind: Service
metadata:
  name: kourier-ingress
  namespace: kourier-system
  labels:
    networking.knative.dev/ingress-provider: kourier
spec:
  type: NodePort
  selector:
    app: 3scale-kourier-gateway
  ports:
    - name: http2
      nodePort: 31080
      port: 80
      targetPort: 8080`

	kourierIngress := exec.Command("kubectl", "apply", "-f", "-")
	kourierIngress.Stdin = strings.NewReader(config)
	if err := runCommand(kourierIngress); err != nil {
		return fmt.Errorf("kourier service: %w", err)
	}

	fmt.Println("    Kourier service installed...")

	domainDns := exec.Command("kubectl", "patch", "configmap", "-n", "knative-serving", "config-domain", "-p", "{\"data\": {\"127.0.0.1.sslip.io\": \"\"}}")
	if err := domainDns.Run(); err != nil {
		return fmt.Errorf("domain dns: %w", err)
	}
	fmt.Println("    Domain DNS set up...")
	fmt.Println("    Finished configuring Kourier")

	return nil
}

// KourierMinikube runs the minikube-specific setup for Kourier
func KourierMinikube() error {
	fmt.Println("🕸 Configuring Kourier for Minikube...")

	if err := retryingApply("https://github.com/knative/serving/releases/download/knative-v" + servingVersion + "/serving-default-domain.yaml"); err != nil {
		return fmt.Errorf("default domain: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Domain DNS set up...")

	// For windows and mac users, do not automatically spawn a tunnel
	// Instead, they will be directed to create one manually after
	// the plugin finishes
	if runtime.GOOS != "window" && runtime.GOOS != "darwin" {
		tunnel := exec.Command("minikube", "tunnel", "--profile", "knative")
		if err := tunnel.Start(); err != nil {
			return fmt.Errorf("tunnel: %w", err)
		}
		fmt.Println("    Minikube tunnel...")
	}

	fmt.Println("    Finished configuring Kourier")
	return nil
}

// Serving installs Knative Serving from Github YAML files
func Serving() error {
	fmt.Println("🍿 Installing Knative Serving v" + servingVersion + " ...")
	baseURL := "https://github.com/knative/serving/releases/download/knative-v" + servingVersion

	if err := retryingApply(baseURL + "/serving-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/serving-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Core installed...")

	fmt.Println("    Finished installing Knative Serving")

	return nil
}

// Eventing installs Knative Eventing from Github YAML files
func Eventing() error {
	fmt.Println("🔥 Installing Knative Eventing v" + eventingVersion + " ... ")
	baseURL := "https://github.com/knative/eventing/releases/download/knative-v" + eventingVersion

	if err := retryingApply(baseURL + "/eventing-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/eventing-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("core: %w", err)
	}
	fmt.Println("    Core installed...")

	if err := retryingApply(baseURL + "/in-memory-channel.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("channel: %w", err)
	}
	fmt.Println("    In-memory channel installed...")

	if err := retryingApply(baseURL + "/mt-channel-broker.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("broker: %w", err)
	}
	fmt.Println("    Mt-channel broker installed...")

	config := `apiVersion: eventing.knative.dev/v1
kind: broker
metadata:
 name: example-broker
 namespace: default`

	exampleBroker := exec.Command("kubectl", "apply", "-f", "-")
	exampleBroker.Stdin = strings.NewReader(config)
	if err := runCommand(exampleBroker); err != nil {
		return fmt.Errorf("example broker: %w", err)
	}

	fmt.Println("    Example broker installed...")
	fmt.Println("    Finished installing Knative Eventing")

	return nil
}

func runCommand(c *exec.Cmd) error {
	if out, err := c.CombinedOutput(); err != nil {
		fmt.Println(string(out))
		return err
	}
	return nil
}

// retryingApply retries a kubectl apply call with the given path 3 times, sleeping
// for 10s between each try.
func retryingApply(path string) error {
	cmd := exec.Command("kubectl", "apply", "-f", path)
	var err error
	for i := 0; i < 3; i++ {
		err = runCommand(cmd)
		if err == nil {
			break
		}
		time.Sleep(10 * time.Second)
	}
	return err
}

// waitForCRDsEstablished waits for all CRDs to be established.
func waitForCRDsEstablished() error {
	return runCommand(exec.Command("kubectl", "wait", "--for=condition=Established", "--all", "crd"))
}

// waitForPodsReady waits for all pods in the given namespace to be ready.
func waitForPodsReady(ns string) error {
	return runCommand(exec.Command("kubectl", "wait", "pod", "--timeout=10m", "--for=condition=Ready", "-l", "!job-name", "-n", ns))
}
<|endoftext|>


<commit_before>package knative

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"knative.dev/serving/pkg/apis/networking"
	networkingv1alpha1 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	networkingClientSet "knative.dev/serving/pkg/client/clientset/versioned/typed/networking/v1alpha1"
	servingClientSet "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
	"knative.dev/serving/pkg/reconciler"
	"os"
	"time"
)

const (
	internalServiceName     = "kourier-internal"
	externalServiceName     = "kourier-external"
	namespaceEnv            = "KOURIER_NAMESPACE"
	kourierIngressClassName = "kourier.ingress.networking.knative.dev"
)

type KNativeClient struct {
	ServingClient    *servingClientSet.ServingV1alpha1Client
	NetworkingClient *networkingClientSet.NetworkingV1alpha1Client
	KourierNamespace string
}

func NewKnativeClient(config *rest.Config) KNativeClient {
	kourierNamespace := os.Getenv(namespaceEnv)
	if kourierNamespace == "" {
		log.Info("Env KOURIER_NAMESPACE empty, using default: \"knative-serving\"")
		kourierNamespace = "knative-serving"
	}

	servingClient, err := servingClientSet.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	networkingClient, err := networkingClientSet.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	return KNativeClient{ServingClient: servingClient, NetworkingClient: networkingClient, KourierNamespace: kourierNamespace}
}

func (kNativeClient *KNativeClient) Services(namespace string) (*v1alpha1.ServiceList, error) {
	return kNativeClient.ServingClient.Services(namespace).List(v1.ListOptions{})
}

func (kNativeClient *KNativeClient) IngressAccessors() ([]networkingv1alpha1.IngressAccessor, error) {
	ingresses, err := kNativeClient.ingresses()
	if err != nil {
		return nil, err
	}

	clusterIngresses, err := kNativeClient.clusterIngresses()
	if err != nil {
		return nil, err
	}

	var ingressList []networkingv1alpha1.IngressAccessor

	for i, _ := range ingresses {
		ingressList = append(ingressList, networkingv1alpha1.IngressAccessor(&ingresses[i]))
	}

	for i, _ := range clusterIngresses {
		ingressList = append(ingressList, networkingv1alpha1.IngressAccessor(&clusterIngresses[i]))
	}

	return ingressList, nil
}

// Pushes an event to the "events" queue received when theres a change in a ClusterIngress is added/deleted/updated.
func (kNativeClient *KNativeClient) WatchChangesInClusterIngress(namespace string, eventsQueue *workqueue.Type, stopChan <-chan struct{}) {

	restClient := kNativeClient.NetworkingClient.RESTClient()

	watchlist := cache.NewListWatchFromClient(restClient, "clusteringresses", namespace,
		fields.Everything())

	_, controller := cache.NewInformer(
		watchlist,
		&networkingv1alpha1.ClusterIngress{},
		time.Second*30, //TODO: Review resync time and adjust.
		cache.FilteringResourceEventHandler{
			FilterFunc: kourierIngressClassNameFilterFunc(),
			Handler: cache.ResourceEventHandlerFuncs{
				AddFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				DeleteFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				UpdateFunc: func(oldObj, newObj interface{}) {
					if oldObj != newObj {
						eventsQueue.Add(struct{}{})
					}
				},
			},
		},
	)

	controller.Run(stopChan)
}

// Pushes an event to the "events" queue received when theres a change in a Ingress is added/deleted/updated.
func (kNativeClient *KNativeClient) WatchChangesInIngress(namespace string, eventsQueue *workqueue.Type, stopChan <-chan struct{}) {

	restClient := kNativeClient.NetworkingClient.RESTClient()

	watchlist := cache.NewListWatchFromClient(restClient, "ingresses", namespace,
		fields.Everything())

	_, controller := cache.NewInformer(
		watchlist,
		&networkingv1alpha1.Ingress{},
		time.Second*30, //TODO: Review resync time and adjust.
		cache.FilteringResourceEventHandler{
			FilterFunc: kourierIngressClassNameFilterFunc(),
			Handler: cache.ResourceEventHandlerFuncs{
				AddFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				DeleteFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				UpdateFunc: func(oldObj, newObj interface{}) {
					if oldObj != newObj {
						eventsQueue.Add(struct{}{})
					}
				},
			},
		},
	)

	controller.Run(stopChan)
}

func (kNativeClient *KNativeClient) MarkIngressReady(ingress networkingv1alpha1.IngressAccessor) error {
	// TODO: Improve. Currently once we go trough the generation of the envoy cache, we mark the objects as Ready,
	//  but that is not exactly true, it can take a while until envoy exposes the routes. Is there a way to get a "callback" from envoy?
	var err error
	status := ingress.GetStatus()
	if ingress.GetGeneration() != status.ObservedGeneration || !ingress.GetStatus().IsReady() {

		internalDomain := internalServiceName + "." + kNativeClient.KourierNamespace + ".svc.cluster.local"
		externalDomain := externalServiceName + "." + kNativeClient.KourierNamespace + ".svc.cluster.local"

		status.InitializeConditions()
		status.MarkLoadBalancerReady(
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: externalDomain,
				},
			},
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: externalDomain,
				},
			},
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: internalDomain,
				},
			})
		status.MarkNetworkConfigured()
		status.ObservedGeneration = ingress.GetGeneration()
		status.ObservedGeneration = ingress.GetGeneration()
		ingress.SetStatus(*status)

		// Handle both types of ingresses
		switch ingress.(type) {
		case *networkingv1alpha1.ClusterIngress:
			in := ingress.(*networkingv1alpha1.ClusterIngress)
			_, err = kNativeClient.NetworkingClient.ClusterIngresses().UpdateStatus(in)
			return err
		case *networkingv1alpha1.Ingress:
			in := ingress.(*networkingv1alpha1.Ingress)
			_, err = kNativeClient.NetworkingClient.Ingresses(ingress.GetNamespace()).UpdateStatus(in)
			return err
		default:
			return fmt.Errorf("can't update object, not Ingress or ClusterIngress")
		}
	}
	return nil
}

func kourierIngressClassNameFilterFunc() func(interface{}) bool {
	return reconciler.AnnotationFilterFunc(
		networking.IngressClassAnnotationKey,
		kourierIngressClassName,
		true,
	)
}

func (kNativeClient *KNativeClient) clusterIngresses() ([]networkingv1alpha1.ClusterIngress, error) {
	list, err := kNativeClient.NetworkingClient.ClusterIngresses().List(v1.ListOptions{})

	if err != nil {
		return nil, err
	}

	return list.Items, err
}

func (kNativeClient *KNativeClient) ingresses() ([]networkingv1alpha1.Ingress, error) {
	list, err := kNativeClient.NetworkingClient.Ingresses(v1.NamespaceAll).List(v1.ListOptions{})

	if err != nil {
		return nil, err
	}

	return list.Items, err
}
<commit_msg>knative: return ingressAccessors filtered by IngressClass<commit_after>package knative

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"knative.dev/serving/pkg/apis/networking"
	networkingv1alpha1 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	networkingClientSet "knative.dev/serving/pkg/client/clientset/versioned/typed/networking/v1alpha1"
	servingClientSet "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
	"knative.dev/serving/pkg/reconciler"
	"os"
	"time"
)

const (
	internalServiceName     = "kourier-internal"
	externalServiceName     = "kourier-external"
	namespaceEnv            = "KOURIER_NAMESPACE"
	kourierIngressClassName = "kourier.ingress.networking.knative.dev"
)

type KNativeClient struct {
	ServingClient    *servingClientSet.ServingV1alpha1Client
	NetworkingClient *networkingClientSet.NetworkingV1alpha1Client
	KourierNamespace string
}

func NewKnativeClient(config *rest.Config) KNativeClient {
	kourierNamespace := os.Getenv(namespaceEnv)
	if kourierNamespace == "" {
		log.Info("Env KOURIER_NAMESPACE empty, using default: \"knative-serving\"")
		kourierNamespace = "knative-serving"
	}

	servingClient, err := servingClientSet.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	networkingClient, err := networkingClientSet.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	return KNativeClient{ServingClient: servingClient, NetworkingClient: networkingClient, KourierNamespace: kourierNamespace}
}

func (kNativeClient *KNativeClient) Services(namespace string) (*v1alpha1.ServiceList, error) {
	return kNativeClient.ServingClient.Services(namespace).List(v1.ListOptions{})
}

// IngressAccessors returns both the Ingresses and ClusterIngresses that have
// the Kourier ingressClass in the Annotations
func (kNativeClient *KNativeClient) IngressAccessors() ([]networkingv1alpha1.IngressAccessor, error) {
	ingresses, err := kNativeClient.ingresses()
	if err != nil {
		return nil, err
	}

	clusterIngresses, err := kNativeClient.clusterIngresses()
	if err != nil {
		return nil, err
	}

	var ingressList []networkingv1alpha1.IngressAccessor

	for i, _ := range ingresses {
		ingressList = append(ingressList, networkingv1alpha1.IngressAccessor(&ingresses[i]))
	}

	for i, _ := range clusterIngresses {
		ingressList = append(ingressList, networkingv1alpha1.IngressAccessor(&clusterIngresses[i]))
	}

	return filterByIngressClass(ingressList), nil
}

// Pushes an event to the "events" queue received when theres a change in a ClusterIngress is added/deleted/updated.
func (kNativeClient *KNativeClient) WatchChangesInClusterIngress(namespace string, eventsQueue *workqueue.Type, stopChan <-chan struct{}) {

	restClient := kNativeClient.NetworkingClient.RESTClient()

	watchlist := cache.NewListWatchFromClient(restClient, "clusteringresses", namespace,
		fields.Everything())

	_, controller := cache.NewInformer(
		watchlist,
		&networkingv1alpha1.ClusterIngress{},
		time.Second*30, //TODO: Review resync time and adjust.
		cache.FilteringResourceEventHandler{
			FilterFunc: kourierIngressClassNameFilterFunc(),
			Handler: cache.ResourceEventHandlerFuncs{
				AddFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				DeleteFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				UpdateFunc: func(oldObj, newObj interface{}) {
					if oldObj != newObj {
						eventsQueue.Add(struct{}{})
					}
				},
			},
		},
	)

	controller.Run(stopChan)
}

// Pushes an event to the "events" queue received when theres a change in a Ingress is added/deleted/updated.
func (kNativeClient *KNativeClient) WatchChangesInIngress(namespace string, eventsQueue *workqueue.Type, stopChan <-chan struct{}) {

	restClient := kNativeClient.NetworkingClient.RESTClient()

	watchlist := cache.NewListWatchFromClient(restClient, "ingresses", namespace,
		fields.Everything())

	_, controller := cache.NewInformer(
		watchlist,
		&networkingv1alpha1.Ingress{},
		time.Second*30, //TODO: Review resync time and adjust.
		cache.FilteringResourceEventHandler{
			FilterFunc: kourierIngressClassNameFilterFunc(),
			Handler: cache.ResourceEventHandlerFuncs{
				AddFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				DeleteFunc: func(obj interface{}) {
					eventsQueue.Add(struct{}{})
				},

				UpdateFunc: func(oldObj, newObj interface{}) {
					if oldObj != newObj {
						eventsQueue.Add(struct{}{})
					}
				},
			},
		},
	)

	controller.Run(stopChan)
}

func (kNativeClient *KNativeClient) MarkIngressReady(ingress networkingv1alpha1.IngressAccessor) error {
	// TODO: Improve. Currently once we go trough the generation of the envoy cache, we mark the objects as Ready,
	//  but that is not exactly true, it can take a while until envoy exposes the routes. Is there a way to get a "callback" from envoy?
	var err error
	status := ingress.GetStatus()
	if ingress.GetGeneration() != status.ObservedGeneration || !ingress.GetStatus().IsReady() {

		internalDomain := internalServiceName + "." + kNativeClient.KourierNamespace + ".svc.cluster.local"
		externalDomain := externalServiceName + "." + kNativeClient.KourierNamespace + ".svc.cluster.local"

		status.InitializeConditions()
		status.MarkLoadBalancerReady(
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: externalDomain,
				},
			},
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: externalDomain,
				},
			},
			[]networkingv1alpha1.LoadBalancerIngressStatus{
				{
					DomainInternal: internalDomain,
				},
			})
		status.MarkNetworkConfigured()
		status.ObservedGeneration = ingress.GetGeneration()
		status.ObservedGeneration = ingress.GetGeneration()
		ingress.SetStatus(*status)

		// Handle both types of ingresses
		switch ingress.(type) {
		case *networkingv1alpha1.ClusterIngress:
			in := ingress.(*networkingv1alpha1.ClusterIngress)
			_, err = kNativeClient.NetworkingClient.ClusterIngresses().UpdateStatus(in)
			return err
		case *networkingv1alpha1.Ingress:
			in := ingress.(*networkingv1alpha1.Ingress)
			_, err = kNativeClient.NetworkingClient.Ingresses(ingress.GetNamespace()).UpdateStatus(in)
			return err
		default:
			return fmt.Errorf("can't update object, not Ingress or ClusterIngress")
		}
	}
	return nil
}

func kourierIngressClassNameFilterFunc() func(interface{}) bool {
	return reconciler.AnnotationFilterFunc(
		networking.IngressClassAnnotationKey,
		kourierIngressClassName,
		true,
	)
}

func (kNativeClient *KNativeClient) clusterIngresses() ([]networkingv1alpha1.ClusterIngress, error) {
	list, err := kNativeClient.NetworkingClient.ClusterIngresses().List(v1.ListOptions{})

	if err != nil {
		return nil, err
	}

	return list.Items, err
}

func (kNativeClient *KNativeClient) ingresses() ([]networkingv1alpha1.Ingress, error) {
	list, err := kNativeClient.NetworkingClient.Ingresses(v1.NamespaceAll).List(v1.ListOptions{})

	if err != nil {
		return nil, err
	}

	return list.Items, err
}

func filterByIngressClass(ingressAccessors []networkingv1alpha1.IngressAccessor) []networkingv1alpha1.IngressAccessor {
	var res []networkingv1alpha1.IngressAccessor

	for _, ingressAccessor := range ingressAccessors {
		ingressClass := ingressClass(ingressAccessor)

		if ingressClass == kourierIngressClassName {
			res = append(res, ingressAccessor)
		}
	}

	return res
}

func ingressClass(ingressAccessor networkingv1alpha1.IngressAccessor) string {
	return ingressAccessor.GetObjectMeta().GetAnnotations()[networking.IngressClassAnnotationKey]
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy_test

import (
	"io"
	"testing"

	"github.com/jacobsa/gcloud/gcs/mock_gcs"
	"github.com/jacobsa/gcsfuse/gcsproxy"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

func TestOgletest(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Invariant-checking object proxy
////////////////////////////////////////////////////////////////////////

// A wrapper around ObjectProxy that calls CheckInvariants whenever invariants
// should hold. For catching logic errors early in the test.
type checkingObjectProxy struct {
	wrapped *gcsproxy.ObjectProxy
}

func (op *checkingObjectProxy) NoteLatest(o *storage.Object) error {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.NoteLatest(o)
}

func (op *checkingObjectProxy) Size() (uint64, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Size()
}

func (op *checkingObjectProxy) ReadAt(b []byte, o int64) (int, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.ReadAt(context.Background(), b, o)
}

func (op *checkingObjectProxy) WriteAt(b []byte, o int64) (int, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.WriteAt(context.Background(), b, o)
}

func (op *checkingObjectProxy) Truncate(n uint64) error {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Truncate(context.Background(), n)
}

func (op *checkingObjectProxy) Sync(ctx context.Context) (*storage.Object, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Sync(ctx)
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type ObjectProxyTest struct {
	objectName string
	bucket     mock_gcs.MockBucket
	op         checkingObjectProxy
}

var _ SetUpInterface = &ObjectProxyTest{}

func (t *ObjectProxyTest) SetUp(ti *TestInfo) {
	t.objectName = "some/object"
	t.bucket = mock_gcs.NewMockBucket(ti.MockController, "bucket")

	var err error
	t.op.wrapped, err = gcsproxy.NewObjectProxy(
		t.bucket,
		t.objectName)

	if err != nil {
		panic(err)
	}
}

////////////////////////////////////////////////////////////////////////
// No source object
////////////////////////////////////////////////////////////////////////

// A test whose initial conditions are a fresh object proxy without a source
// object set.
type NoSourceObjectTest struct {
	ObjectProxyTest
}

var _ SetUpInterface = &NoSourceObjectTest{}

func init() { RegisterTestSuite(&NoSourceObjectTest{}) }

func (t *NoSourceObjectTest) NoteLatest_NegativeSize() {
	o := &storage.Object{
		Name:       t.objectName,
		Generation: 1234,
		Size:       -1,
	}

	err := t.op.NoteLatest(o)

	AssertNe(nil, err)
	ExpectThat(err, Error(HasSubstr("size")))
	ExpectThat(err, Error(HasSubstr("-1")))
}

func (t *NoSourceObjectTest) NoteLatest_WrongName() {
	o := &storage.Object{
		Name:       t.objectName + "foo",
		Generation: 1234,
		Size:       0,
	}

	err := t.op.NoteLatest(o)

	AssertNe(nil, err)
	ExpectThat(err, Error(HasSubstr("name")))
	ExpectThat(err, Error(HasSubstr("foo")))
}

func (t *NoSourceObjectTest) Size_InitialState() {
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Size_AfterTruncatingToZero() {
	var err error

	// Truncate
	err = t.op.Truncate(0)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Size_AfterTruncatingToNonZero() {
	var err error

	// Truncate
	err = t.op.Truncate(123)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(123, size)
}

func (t *NoSourceObjectTest) Size_AfterReading() {
	var err error

	// Read
	buf := make([]byte, 0)
	n, err := t.op.ReadAt(buf, 0)

	AssertEq(nil, err)
	AssertEq(0, n)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Read_InitialState() {
	type testCase struct {
		offset      int64
		size        int
		expectedErr error
		expectedN   int
	}

	testCases := []testCase{
		// Empty ranges
		testCase{0, 0, nil, 0},
		testCase{17, 0, nil, 0},

		// Non-empty ranges
		testCase{0, 10, io.EOF, 0},
		testCase{17, 10, io.EOF, 0},
	}

	for _, tc := range testCases {
		buf := make([]byte, tc.size)
		n, err := t.op.ReadAt(buf, tc.offset)

		AssertEq(tc.expectedErr, err, "Test case: %v", tc)
		AssertEq(tc.expectedN, n, "Test case: %v", tc)
	}
}

func (t *NoSourceObjectTest) WriteToEndOfObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Extend the object by writing twice.
	n, err = t.op.WriteAt([]byte("taco"), 0)
	AssertEq(nil, err)
	AssertEq(len("taco"), n)

	n, err = t.op.WriteAt([]byte("burrito"), int64(len("taco")))
	AssertEq(nil, err)
	AssertEq(len("burrito"), n)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(len("tacoburrito"), n)
	ExpectEq("tacoburrito", string(buf[:n]))

	// Read a range in the middle.
	buf = make([]byte, 4)
	n, err = t.op.ReadAt(buf, 3)

	AssertEq(nil, err)
	ExpectEq(4, n)
	ExpectEq("obur", string(buf[:n]))
}

func (t *NoSourceObjectTest) WritePastEndOfObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Extend the object by writing past its end.
	n, err = t.op.WriteAt([]byte("taco"), 2)
	AssertEq(nil, err)
	AssertEq(len("taco"), n)

	// Check its size.
	size, err := t.op.Size()
	AssertEq(nil, err)
	ExpectEq(2+len("taco"), size)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(2+len("taco"), n)
	ExpectEq("\x00\x00taco", string(buf[:n]))

	// Read a range in the middle.
	buf = make([]byte, 4)
	n, err = t.op.ReadAt(buf, 1)

	AssertEq(nil, err)
	ExpectEq(4, n)
	ExpectEq("\x00tac", string(buf[:n]))
}

func (t *NoSourceObjectTest) WriteWithinObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Write several bytes to extend the object.
	n, err = t.op.WriteAt([]byte("00000"), 0)
	AssertEq(nil, err)
	AssertEq(len("00000"), n)

	// Overwrite some in the middle.
	n, err = t.op.WriteAt([]byte("11"), 1)
	AssertEq(nil, err)
	AssertEq(len("11"), n)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(len("01100"), n)
	ExpectEq("01100", string(buf[:n]))
}

func (t *NoSourceObjectTest) GrowByTruncating() {
	var n int
	var err error
	var buf []byte

	// Truncate
	err = t.op.Truncate(4)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()
	AssertEq(nil, err)
	ExpectEq(4, size)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(4, n)
	ExpectEq("\x00\x00\x00\x00", string(buf[:n]))
}

func (t *NoSourceObjectTest) Sync_NoChanges() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_AfterWriting() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_AfterTruncating() {
	AssertTrue(false, "TODO")
}

////////////////////////////////////////////////////////////////////////
// Source object present
////////////////////////////////////////////////////////////////////////

// A test whose initial conditions are an object proxy branching from a source
// object in the bucket.
type SourceObjectPresentTest struct {
	ObjectProxyTest
	sourceObject storage.Object
}

var _ SetUpInterface = &SourceObjectPresentTest{}

func init() { RegisterTestSuite(&SourceObjectPresentTest{}) }

func (t *SourceObjectPresentTest) SetUp(ti *TestInfo) {
	t.ObjectProxyTest.SetUp(ti)

	// Set up the source object.
	t.sourceObject = storage.Object{
		Name:       t.objectName,
		Generation: 123,
		Size:       456,
	}

	if err := t.op.NoteLatest(&t.sourceObject); err != nil {
		panic(err)
	}
}

func (t *SourceObjectPresentTest) DoesFoo() {
	AssertTrue(false, "TODO")
}
<commit_msg>NoSourceObjectTest.Sync_NoInteractions<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsproxy_test

import (
	"errors"
	"fmt"
	"io"
	"testing"

	"github.com/jacobsa/gcloud/gcs/mock_gcs"
	"github.com/jacobsa/gcsfuse/gcsproxy"
	. "github.com/jacobsa/oglematchers"
	"github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

func TestOgletest(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// An oglemock.Matcher that accepts a predicate function and a description,
// making it easy to make anonymous matcher types.
type predicateMatcher struct {
	Desc      string
	Predicate func(interface{}) error
}

var _ Matcher = &predicateMatcher{}

func (m *predicateMatcher) Matches(candidate interface{}) error {
	return m.Predicate(candidate)
}

func (m *predicateMatcher) Description() string {
	return m.Desc
}

func nameIs(name string) Matcher {
	return &predicateMatcher{
		Desc: fmt.Sprintf("Name is: %s", name),
		Predicate: func(candidate interface{}) error {
			panic("TODO")
		},
	}
}

func contentsAre(s string) Matcher {
	return &predicateMatcher{
		Desc: fmt.Sprintf("Object contents are: %s", s),
		Predicate: func(candidate interface{}) error {
			panic("TODO")
		},
	}
}

////////////////////////////////////////////////////////////////////////
// Invariant-checking object proxy
////////////////////////////////////////////////////////////////////////

// A wrapper around ObjectProxy that calls CheckInvariants whenever invariants
// should hold. For catching logic errors early in the test.
type checkingObjectProxy struct {
	wrapped *gcsproxy.ObjectProxy
}

func (op *checkingObjectProxy) NoteLatest(o *storage.Object) error {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.NoteLatest(o)
}

func (op *checkingObjectProxy) Size() (uint64, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Size()
}

func (op *checkingObjectProxy) ReadAt(b []byte, o int64) (int, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.ReadAt(context.Background(), b, o)
}

func (op *checkingObjectProxy) WriteAt(b []byte, o int64) (int, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.WriteAt(context.Background(), b, o)
}

func (op *checkingObjectProxy) Truncate(n uint64) error {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Truncate(context.Background(), n)
}

func (op *checkingObjectProxy) Sync() (*storage.Object, error) {
	op.wrapped.CheckInvariants()
	defer op.wrapped.CheckInvariants()
	return op.wrapped.Sync(context.Background())
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type ObjectProxyTest struct {
	objectName string
	bucket     mock_gcs.MockBucket
	op         checkingObjectProxy
}

var _ SetUpInterface = &ObjectProxyTest{}

func (t *ObjectProxyTest) SetUp(ti *TestInfo) {
	t.objectName = "some/object"
	t.bucket = mock_gcs.NewMockBucket(ti.MockController, "bucket")

	var err error
	t.op.wrapped, err = gcsproxy.NewObjectProxy(
		t.bucket,
		t.objectName)

	if err != nil {
		panic(err)
	}
}

////////////////////////////////////////////////////////////////////////
// No source object
////////////////////////////////////////////////////////////////////////

// A test whose initial conditions are a fresh object proxy without a source
// object set.
type NoSourceObjectTest struct {
	ObjectProxyTest
}

var _ SetUpInterface = &NoSourceObjectTest{}

func init() { RegisterTestSuite(&NoSourceObjectTest{}) }

func (t *NoSourceObjectTest) NoteLatest_NegativeSize() {
	o := &storage.Object{
		Name:       t.objectName,
		Generation: 1234,
		Size:       -1,
	}

	err := t.op.NoteLatest(o)

	AssertNe(nil, err)
	ExpectThat(err, Error(HasSubstr("size")))
	ExpectThat(err, Error(HasSubstr("-1")))
}

func (t *NoSourceObjectTest) NoteLatest_WrongName() {
	o := &storage.Object{
		Name:       t.objectName + "foo",
		Generation: 1234,
		Size:       0,
	}

	err := t.op.NoteLatest(o)

	AssertNe(nil, err)
	ExpectThat(err, Error(HasSubstr("name")))
	ExpectThat(err, Error(HasSubstr("foo")))
}

func (t *NoSourceObjectTest) Size_InitialState() {
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Size_AfterTruncatingToZero() {
	var err error

	// Truncate
	err = t.op.Truncate(0)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Size_AfterTruncatingToNonZero() {
	var err error

	// Truncate
	err = t.op.Truncate(123)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(123, size)
}

func (t *NoSourceObjectTest) Size_AfterReading() {
	var err error

	// Read
	buf := make([]byte, 0)
	n, err := t.op.ReadAt(buf, 0)

	AssertEq(nil, err)
	AssertEq(0, n)

	// Size
	size, err := t.op.Size()

	AssertEq(nil, err)
	ExpectEq(0, size)
}

func (t *NoSourceObjectTest) Read_InitialState() {
	type testCase struct {
		offset      int64
		size        int
		expectedErr error
		expectedN   int
	}

	testCases := []testCase{
		// Empty ranges
		testCase{0, 0, nil, 0},
		testCase{17, 0, nil, 0},

		// Non-empty ranges
		testCase{0, 10, io.EOF, 0},
		testCase{17, 10, io.EOF, 0},
	}

	for _, tc := range testCases {
		buf := make([]byte, tc.size)
		n, err := t.op.ReadAt(buf, tc.offset)

		AssertEq(tc.expectedErr, err, "Test case: %v", tc)
		AssertEq(tc.expectedN, n, "Test case: %v", tc)
	}
}

func (t *NoSourceObjectTest) WriteToEndOfObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Extend the object by writing twice.
	n, err = t.op.WriteAt([]byte("taco"), 0)
	AssertEq(nil, err)
	AssertEq(len("taco"), n)

	n, err = t.op.WriteAt([]byte("burrito"), int64(len("taco")))
	AssertEq(nil, err)
	AssertEq(len("burrito"), n)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(len("tacoburrito"), n)
	ExpectEq("tacoburrito", string(buf[:n]))

	// Read a range in the middle.
	buf = make([]byte, 4)
	n, err = t.op.ReadAt(buf, 3)

	AssertEq(nil, err)
	ExpectEq(4, n)
	ExpectEq("obur", string(buf[:n]))
}

func (t *NoSourceObjectTest) WritePastEndOfObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Extend the object by writing past its end.
	n, err = t.op.WriteAt([]byte("taco"), 2)
	AssertEq(nil, err)
	AssertEq(len("taco"), n)

	// Check its size.
	size, err := t.op.Size()
	AssertEq(nil, err)
	ExpectEq(2+len("taco"), size)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(2+len("taco"), n)
	ExpectEq("\x00\x00taco", string(buf[:n]))

	// Read a range in the middle.
	buf = make([]byte, 4)
	n, err = t.op.ReadAt(buf, 1)

	AssertEq(nil, err)
	ExpectEq(4, n)
	ExpectEq("\x00tac", string(buf[:n]))
}

func (t *NoSourceObjectTest) WriteWithinObjectThenRead() {
	var n int
	var err error
	var buf []byte

	// Write several bytes to extend the object.
	n, err = t.op.WriteAt([]byte("00000"), 0)
	AssertEq(nil, err)
	AssertEq(len("00000"), n)

	// Overwrite some in the middle.
	n, err = t.op.WriteAt([]byte("11"), 1)
	AssertEq(nil, err)
	AssertEq(len("11"), n)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(len("01100"), n)
	ExpectEq("01100", string(buf[:n]))
}

func (t *NoSourceObjectTest) GrowByTruncating() {
	var n int
	var err error
	var buf []byte

	// Truncate
	err = t.op.Truncate(4)
	AssertEq(nil, err)

	// Size
	size, err := t.op.Size()
	AssertEq(nil, err)
	ExpectEq(4, size)

	// Read the whole thing.
	buf = make([]byte, 1024)
	n, err = t.op.ReadAt(buf, 0)

	AssertEq(io.EOF, err)
	ExpectEq(4, n)
	ExpectEq("\x00\x00\x00\x00", string(buf[:n]))
}

func (t *NoSourceObjectTest) Sync_NoInteractions() {
	// CreateObject -- should receive an empty string.
	ExpectCall(t.bucket, "CreateObject")(
		Any(),
		AllOf(nameIs(t.objectName), contentsAre(""))).
		WillOnce(oglemock.Return(nil, errors.New("")))

	// Sync
	t.op.Sync()
}

func (t *NoSourceObjectTest) Sync_ReadCallsOnly() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_AfterWriting() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_AfterTruncating() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_CreateObjectFails() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) Sync_Successful() {
	AssertTrue(false, "TODO")
}

func (t *NoSourceObjectTest) SyncTwice() {
	AssertTrue(false, "TODO")
}

////////////////////////////////////////////////////////////////////////
// Source object present
////////////////////////////////////////////////////////////////////////

// A test whose initial conditions are an object proxy branching from a source
// object in the bucket.
type SourceObjectPresentTest struct {
	ObjectProxyTest
	sourceObject storage.Object
}

var _ SetUpInterface = &SourceObjectPresentTest{}

func init() { RegisterTestSuite(&SourceObjectPresentTest{}) }

func (t *SourceObjectPresentTest) SetUp(ti *TestInfo) {
	t.ObjectProxyTest.SetUp(ti)

	// Set up the source object.
	t.sourceObject = storage.Object{
		Name:       t.objectName,
		Generation: 123,
		Size:       456,
	}

	if err := t.op.NoteLatest(&t.sourceObject); err != nil {
		panic(err)
	}
}

func (t *SourceObjectPresentTest) DoesFoo() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>package overlay

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/system"
	"github.com/containers/storage/pkg/unshare"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

// Options type holds various configuration options for overlay
// MountWithOptions accepts following type so it is easier to specify
// more verbose configuration for overlay mount.
type Options struct {
	// The Upper directory is normally writable layer in an overlay mount.
	// Note!! : Following API does not handles escaping or validates correctness of the values
	// passed to UpperDirOptionFragment instead API will try to pass values as is it
	// to the `mount` command. It is user's responsibility to make sure they pre-validate
	// these values. Invalid inputs may lead to undefined behviour.
	// This is provided as-is, use it if it works for you, we can/will change/break that in the future.
	// See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959
	// TODO: Should we address above comment and handle escaping of metacharacters like
	// `comma`, `backslash` ,`colon` and any other special characters
	UpperDirOptionFragment string
	// The Workdir is used to prepare files as they are switched between the layers.
	// Note!! : Following API does not handles escaping or validates correctness of the values
	// passed to WorkDirOptionFragment instead API will try to pass values as is it
	// to the `mount` command. It is user's responsibility to make sure they pre-validate
	// these values. Invalid inputs may lead to undefined behviour.
	// This is provided as-is, use it if it works for you, we can/will change/break that in the future.
	// See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959
	// TODO: Should we address above comment and handle escaping of metacharacters like
	// `comma`, `backslash` ,`colon` and any other special characters
	WorkDirOptionFragment string
	// Graph options relayed from podman, will be responsible for choosing mount program
	GraphOpts []string
	// Mark if following overlay is read only
	ReadOnly bool
	// RootUID is not used yet but keeping it here for legacy reasons.
	RootUID int
	// RootGID is not used yet but keeping it here for legacy reasons.
	RootGID int
}

// TempDir generates an overlay Temp directory in the container content
func TempDir(containerDir string, rootUID, rootGID int) (string, error) {
	contentDir := filepath.Join(containerDir, "overlay")
	if err := idtools.MkdirAllAs(contentDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", contentDir)
	}

	contentDir, err := ioutil.TempDir(contentDir, "")
	if err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay tmpdir in %s directory", contentDir)
	}

	return generateOverlayStructure(contentDir, rootUID, rootGID)
}

// GenerateStructure generates an overlay directory structure for container content
func GenerateStructure(containerDir, containerID, name string, rootUID, rootGID int) (string, error) {
	contentDir := filepath.Join(containerDir, "overlay-containers", containerID, name)
	if err := idtools.MkdirAllAs(contentDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", contentDir)
	}

	return generateOverlayStructure(contentDir, rootUID, rootGID)
}

// generateOverlayStructure generates upper, work and merge directory structure for overlay directory
func generateOverlayStructure(containerDir string, rootUID, rootGID int) (string, error) {
	upperDir := filepath.Join(containerDir, "upper")
	workDir := filepath.Join(containerDir, "work")
	if err := idtools.MkdirAllAs(upperDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", upperDir)
	}
	if err := idtools.MkdirAllAs(workDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", workDir)
	}
	mergeDir := filepath.Join(containerDir, "merge")
	if err := idtools.MkdirAllAs(mergeDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", mergeDir)
	}

	return containerDir, nil
}

// Mount creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.
func Mount(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) {
	overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: false, RootUID: rootUID, RootGID: rootGID}
	return MountWithOptions(contentDir, source, dest, &overlayOpts)
}

// MountReadOnly creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.  Note that no
// upper layer will be created rendering it a read-only mount
func MountReadOnly(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) {
	overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: true, RootUID: rootUID, RootGID: rootGID}
	return MountWithOptions(contentDir, source, dest, &overlayOpts)
}

// MountWithOptions creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.
// But allows api to set custom workdir, upperdir and other overlay options
// Following API is being used by podman at the moment
func MountWithOptions(contentDir, source, dest string, opts *Options) (mount specs.Mount, Err error) {
	mergeDir := filepath.Join(contentDir, "merge")

	// Create overlay mount options for rw/ro.
	var overlayOptions string
	if opts.ReadOnly {
		// Read-only overlay mounts require two lower layer.
		lowerTwo := filepath.Join(contentDir, "lower")
		if err := os.Mkdir(lowerTwo, 0755); err != nil {
			return mount, err
		}
		overlayOptions = fmt.Sprintf("lowerdir=%s:%s,private", escapeColon(source), lowerTwo)
	} else {
		// Read-write overlay mounts want a lower, upper and a work layer.
		workDir := filepath.Join(contentDir, "work")
		upperDir := filepath.Join(contentDir, "upper")

		if opts.WorkDirOptionFragment != "" && opts.UpperDirOptionFragment != "" {
			workDir = opts.WorkDirOptionFragment
			upperDir = opts.UpperDirOptionFragment
		}

		st, err := os.Stat(source)
		if err != nil {
			return mount, err
		}
		if err := os.Chmod(upperDir, st.Mode()); err != nil {
			return mount, err
		}
		if stat, ok := st.Sys().(*syscall.Stat_t); ok {
			if err := os.Chown(upperDir, int(stat.Uid), int(stat.Gid)); err != nil {
				return mount, err
			}
		}
		overlayOptions = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", escapeColon(source), upperDir, workDir)
	}

	if unshare.IsRootless() {
		mountProgram := ""

		mountMap := map[string]bool{
			".mount_program":         true,
			"overlay.mount_program":  true,
			"overlay2.mount_program": true,
		}

		for _, i := range opts.GraphOpts {
			s := strings.SplitN(i, "=", 2)
			if len(s) != 2 {
				continue
			}
			key := s[0]
			val := s[1]
			if mountMap[key] {
				mountProgram = val
				break
			}
		}
		if mountProgram != "" {
			cmd := exec.Command(mountProgram, "-o", overlayOptions, mergeDir)

			if err := cmd.Run(); err != nil {
				return mount, errors.Wrapf(err, "exec %s", mountProgram)
			}

			mount.Source = mergeDir
			mount.Destination = dest
			mount.Type = "bind"
			mount.Options = []string{"bind", "slave"}
			return mount, nil
		}
		/* If a mount_program is not specified, fallback to try mount native overlay.  */
		overlayOptions = fmt.Sprintf("%s,userxattr", overlayOptions)
	}

	mount.Source = mergeDir
	mount.Destination = dest
	mount.Type = "overlay"
	mount.Options = strings.Split(overlayOptions, ",")

	return mount, nil
}

// Convert ":" to "\:", the path which will be overlay mounted need to be escaped
func escapeColon(source string) string {
	return strings.ReplaceAll(source, ":", "\\:")
}

// RemoveTemp removes temporary mountpoint and all content from its parent
// directory
func RemoveTemp(contentDir string) error {
	if err := Unmount(contentDir); err != nil {
		return err
	}

	return os.RemoveAll(contentDir)
}

// Unmount the overlay mountpoint
func Unmount(contentDir string) error {
	mergeDir := filepath.Join(contentDir, "merge")

	if unshare.IsRootless() {
		// Attempt to unmount the FUSE mount using either fusermount or fusermount3.
		// If they fail, fallback to unix.Unmount
		for _, v := range []string{"fusermount3", "fusermount"} {
			err := exec.Command(v, "-u", mergeDir).Run()
			if err != nil && errors.Cause(err) != exec.ErrNotFound {
				logrus.Debugf("Error unmounting %s with %s - %v", mergeDir, v, err)
			}
			if err == nil {
				return nil
			}
		}
		// If fusermount|fusermount3 failed to unmount the FUSE file system, attempt unmount
	}

	// Ignore EINVAL as the specified merge dir is not a mount point
	if err := unix.Unmount(mergeDir, 0); err != nil && !os.IsNotExist(err) && err != unix.EINVAL {
		return errors.Wrapf(err, "unmount overlay %s", mergeDir)
	}
	return nil
}

func recreate(contentDir string) error {
	st, err := system.Stat(contentDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return errors.Wrap(err, "failed to stat overlay upper directory")
	}

	if err := os.RemoveAll(contentDir); err != nil {
		return errors.WithStack(err)
	}

	if err := idtools.MkdirAllAs(contentDir, os.FileMode(st.Mode()), int(st.UID()), int(st.GID())); err != nil {
		return errors.Wrap(err, "failed to create overlay directory")
	}
	return nil
}

// CleanupMount removes all temporary mountpoint content
func CleanupMount(contentDir string) (Err error) {
	if err := recreate(filepath.Join(contentDir, "upper")); err != nil {
		return err
	}
	if err := recreate(filepath.Join(contentDir, "work")); err != nil {
		return err
	}
	return nil
}

// CleanupContent removes all temporary mountpoint and all content from
// directory
func CleanupContent(containerDir string) (Err error) {
	contentDir := filepath.Join(containerDir, "overlay")

	files, err := ioutil.ReadDir(contentDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return errors.Wrap(err, "read directory")
	}
	for _, f := range files {
		dir := filepath.Join(contentDir, f.Name())
		if err := Unmount(dir); err != nil {
			return err
		}
	}

	if err := os.RemoveAll(contentDir); err != nil && !os.IsNotExist(err) {
		return errors.Wrap(err, "failed to cleanup overlay directory")
	}
	return nil
}
<commit_msg>overlay: move mount program lookup to separate function<commit_after>package overlay

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/system"
	"github.com/containers/storage/pkg/unshare"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

// Options type holds various configuration options for overlay
// MountWithOptions accepts following type so it is easier to specify
// more verbose configuration for overlay mount.
type Options struct {
	// The Upper directory is normally writable layer in an overlay mount.
	// Note!! : Following API does not handles escaping or validates correctness of the values
	// passed to UpperDirOptionFragment instead API will try to pass values as is it
	// to the `mount` command. It is user's responsibility to make sure they pre-validate
	// these values. Invalid inputs may lead to undefined behviour.
	// This is provided as-is, use it if it works for you, we can/will change/break that in the future.
	// See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959
	// TODO: Should we address above comment and handle escaping of metacharacters like
	// `comma`, `backslash` ,`colon` and any other special characters
	UpperDirOptionFragment string
	// The Workdir is used to prepare files as they are switched between the layers.
	// Note!! : Following API does not handles escaping or validates correctness of the values
	// passed to WorkDirOptionFragment instead API will try to pass values as is it
	// to the `mount` command. It is user's responsibility to make sure they pre-validate
	// these values. Invalid inputs may lead to undefined behviour.
	// This is provided as-is, use it if it works for you, we can/will change/break that in the future.
	// See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959
	// TODO: Should we address above comment and handle escaping of metacharacters like
	// `comma`, `backslash` ,`colon` and any other special characters
	WorkDirOptionFragment string
	// Graph options relayed from podman, will be responsible for choosing mount program
	GraphOpts []string
	// Mark if following overlay is read only
	ReadOnly bool
	// RootUID is not used yet but keeping it here for legacy reasons.
	RootUID int
	// RootGID is not used yet but keeping it here for legacy reasons.
	RootGID int
}

// TempDir generates an overlay Temp directory in the container content
func TempDir(containerDir string, rootUID, rootGID int) (string, error) {
	contentDir := filepath.Join(containerDir, "overlay")
	if err := idtools.MkdirAllAs(contentDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", contentDir)
	}

	contentDir, err := ioutil.TempDir(contentDir, "")
	if err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay tmpdir in %s directory", contentDir)
	}

	return generateOverlayStructure(contentDir, rootUID, rootGID)
}

// GenerateStructure generates an overlay directory structure for container content
func GenerateStructure(containerDir, containerID, name string, rootUID, rootGID int) (string, error) {
	contentDir := filepath.Join(containerDir, "overlay-containers", containerID, name)
	if err := idtools.MkdirAllAs(contentDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", contentDir)
	}

	return generateOverlayStructure(contentDir, rootUID, rootGID)
}

// generateOverlayStructure generates upper, work and merge directory structure for overlay directory
func generateOverlayStructure(containerDir string, rootUID, rootGID int) (string, error) {
	upperDir := filepath.Join(containerDir, "upper")
	workDir := filepath.Join(containerDir, "work")
	if err := idtools.MkdirAllAs(upperDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", upperDir)
	}
	if err := idtools.MkdirAllAs(workDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", workDir)
	}
	mergeDir := filepath.Join(containerDir, "merge")
	if err := idtools.MkdirAllAs(mergeDir, 0700, rootUID, rootGID); err != nil {
		return "", errors.Wrapf(err, "failed to create the overlay %s directory", mergeDir)
	}

	return containerDir, nil
}

// Mount creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.
func Mount(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) {
	overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: false, RootUID: rootUID, RootGID: rootGID}
	return MountWithOptions(contentDir, source, dest, &overlayOpts)
}

// MountReadOnly creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.  Note that no
// upper layer will be created rendering it a read-only mount
func MountReadOnly(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) {
	overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: true, RootUID: rootUID, RootGID: rootGID}
	return MountWithOptions(contentDir, source, dest, &overlayOpts)
}

// findMountProgram finds if any mount program is specified in the graph options.
func findMountProgram(graphOptions []string) string {
	mountMap := map[string]bool{
		".mount_program":         true,
		"overlay.mount_program":  true,
		"overlay2.mount_program": true,
	}

	for _, i := range graphOptions {
		s := strings.SplitN(i, "=", 2)
		if len(s) != 2 {
			continue
		}
		key := s[0]
		val := s[1]
		if mountMap[key] {
			return val
		}
	}

	return ""
}

// MountWithOptions creates a subdir of the contentDir based on the source directory
// from the source system.  It then mounts up the source directory on to the
// generated mount point and returns the mount point to the caller.
// But allows api to set custom workdir, upperdir and other overlay options
// Following API is being used by podman at the moment
func MountWithOptions(contentDir, source, dest string, opts *Options) (mount specs.Mount, Err error) {
	mergeDir := filepath.Join(contentDir, "merge")

	// Create overlay mount options for rw/ro.
	var overlayOptions string
	if opts.ReadOnly {
		// Read-only overlay mounts require two lower layer.
		lowerTwo := filepath.Join(contentDir, "lower")
		if err := os.Mkdir(lowerTwo, 0755); err != nil {
			return mount, err
		}
		overlayOptions = fmt.Sprintf("lowerdir=%s:%s,private", escapeColon(source), lowerTwo)
	} else {
		// Read-write overlay mounts want a lower, upper and a work layer.
		workDir := filepath.Join(contentDir, "work")
		upperDir := filepath.Join(contentDir, "upper")

		if opts.WorkDirOptionFragment != "" && opts.UpperDirOptionFragment != "" {
			workDir = opts.WorkDirOptionFragment
			upperDir = opts.UpperDirOptionFragment
		}

		st, err := os.Stat(source)
		if err != nil {
			return mount, err
		}
		if err := os.Chmod(upperDir, st.Mode()); err != nil {
			return mount, err
		}
		if stat, ok := st.Sys().(*syscall.Stat_t); ok {
			if err := os.Chown(upperDir, int(stat.Uid), int(stat.Gid)); err != nil {
				return mount, err
			}
		}
		overlayOptions = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", escapeColon(source), upperDir, workDir)
	}

	if unshare.IsRootless() {
		mountProgram := findMountProgram(opts.GraphOpts)
		if mountProgram != "" {
			cmd := exec.Command(mountProgram, "-o", overlayOptions, mergeDir)

			if err := cmd.Run(); err != nil {
				return mount, errors.Wrapf(err, "exec %s", mountProgram)
			}

			mount.Source = mergeDir
			mount.Destination = dest
			mount.Type = "bind"
			mount.Options = []string{"bind", "slave"}
			return mount, nil
		}
		/* If a mount_program is not specified, fallback to try mount native overlay.  */
		overlayOptions = fmt.Sprintf("%s,userxattr", overlayOptions)
	}

	mount.Source = mergeDir
	mount.Destination = dest
	mount.Type = "overlay"
	mount.Options = strings.Split(overlayOptions, ",")

	return mount, nil
}

// Convert ":" to "\:", the path which will be overlay mounted need to be escaped
func escapeColon(source string) string {
	return strings.ReplaceAll(source, ":", "\\:")
}

// RemoveTemp removes temporary mountpoint and all content from its parent
// directory
func RemoveTemp(contentDir string) error {
	if err := Unmount(contentDir); err != nil {
		return err
	}

	return os.RemoveAll(contentDir)
}

// Unmount the overlay mountpoint
func Unmount(contentDir string) error {
	mergeDir := filepath.Join(contentDir, "merge")

	if unshare.IsRootless() {
		// Attempt to unmount the FUSE mount using either fusermount or fusermount3.
		// If they fail, fallback to unix.Unmount
		for _, v := range []string{"fusermount3", "fusermount"} {
			err := exec.Command(v, "-u", mergeDir).Run()
			if err != nil && errors.Cause(err) != exec.ErrNotFound {
				logrus.Debugf("Error unmounting %s with %s - %v", mergeDir, v, err)
			}
			if err == nil {
				return nil
			}
		}
		// If fusermount|fusermount3 failed to unmount the FUSE file system, attempt unmount
	}

	// Ignore EINVAL as the specified merge dir is not a mount point
	if err := unix.Unmount(mergeDir, 0); err != nil && !os.IsNotExist(err) && err != unix.EINVAL {
		return errors.Wrapf(err, "unmount overlay %s", mergeDir)
	}
	return nil
}

func recreate(contentDir string) error {
	st, err := system.Stat(contentDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return errors.Wrap(err, "failed to stat overlay upper directory")
	}

	if err := os.RemoveAll(contentDir); err != nil {
		return errors.WithStack(err)
	}

	if err := idtools.MkdirAllAs(contentDir, os.FileMode(st.Mode()), int(st.UID()), int(st.GID())); err != nil {
		return errors.Wrap(err, "failed to create overlay directory")
	}
	return nil
}

// CleanupMount removes all temporary mountpoint content
func CleanupMount(contentDir string) (Err error) {
	if err := recreate(filepath.Join(contentDir, "upper")); err != nil {
		return err
	}
	if err := recreate(filepath.Join(contentDir, "work")); err != nil {
		return err
	}
	return nil
}

// CleanupContent removes all temporary mountpoint and all content from
// directory
func CleanupContent(containerDir string) (Err error) {
	contentDir := filepath.Join(containerDir, "overlay")

	files, err := ioutil.ReadDir(contentDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return errors.Wrap(err, "read directory")
	}
	for _, f := range files {
		dir := filepath.Join(contentDir, f.Name())
		if err := Unmount(dir); err != nil {
			return err
		}
	}

	if err := os.RemoveAll(contentDir); err != nil && !os.IsNotExist(err) {
		return errors.Wrap(err, "failed to cleanup overlay directory")
	}
	return nil
}
<|endoftext|>


<commit_before>package process

import (
	"fmt"
	"strings"

	"github.com/stretchr/objx"

	"github.com/grafana/tanka/pkg/kubernetes/manifest"
)

// Extract scans the raw Jsonnet evaluation result (JSON tree) for objects that
// look like Kubernetes objects and extracts those into a flat map, indexed by
// their path in the original JSON tree
func Extract(raw interface{}) (map[string]manifest.Manifest, error) {
	extracted := make(map[string]manifest.Manifest)
	if err := walkJSON(raw, extracted, nil); err != nil {
		return nil, err
	}
	return extracted, nil
}

// walkJSON recurses into either a map or list, returning a list of all objects that look
// like kubernetes resources. We support resources at an arbitrary level of nesting, and
// return an error if a node is not walkable.
//
// Handling the different types is quite gross, so we split this method into a generic
// walkJSON, and then walkObj/walkList to handle the two different types of collection we
// support.
func walkJSON(ptr interface{}, extracted map[string]manifest.Manifest, path trace) error {
	// check for known types
	switch v := ptr.(type) {
	case map[string]interface{}:
		return walkObj(v, extracted, path)
	case []interface{}:
		return walkList(v, extracted, path)
	}

	return ErrorPrimitiveReached{
		path:      path.Base(),
		key:       path.Name(),
		primitive: ptr,
	}
}

func walkList(list []interface{}, extracted map[string]manifest.Manifest, path trace) error {
	for idx, value := range list {
		err := walkJSON(value, extracted, append(path, fmt.Sprintf("[%d]", idx)))
		if err != nil {
			return err
		}
	}
	return nil
}

func walkObj(obj objx.Map, extracted map[string]manifest.Manifest, path trace) error {
	obj = obj.Exclude([]string{"__ksonnet"}) // remove our private ksonnet field

	// This looks like a kubernetes manifest, so make one and return it
	if isKubernetesManifest(obj) {
		m, err := manifest.NewFromObj(obj)
		if err != nil {
			return err.(*manifest.SchemaError).WithName(path.Full())
		}

		extracted[path.Full()] = m
		return nil
	}

	for key, value := range obj {
		path := append(path, key)

		if value == nil { // result from false if condition in Jsonnet
			continue
		}
		err := walkJSON(value, extracted, path)
		if err != nil {
			return err
		}
	}

	return nil
}

type trace []string

func (t trace) Full() string {
	return "." + strings.Join(t, ".")
}

func (t trace) Base() string {
	if len(t) > 0 {
		t = t[:len(t)-1]
	}
	return "." + strings.Join(t, ".")
}

func (t trace) Name() string {
	if len(t) > 0 {
		return t[len(t)-1]
	}

	return ""
}

// ErrorPrimitiveReached occurs when walkJSON reaches the end of nested dicts without finding a valid Kubernetes manifest
type ErrorPrimitiveReached struct {
	path, key string
	primitive interface{}
}

func (e ErrorPrimitiveReached) Error() string {
	return fmt.Sprintf("recursion did not resolve in a valid Kubernetes object. "+
		" In path `%s` found key `%s` of type `%T` instead.",
		e.path, e.key, e.primitive)
}

// isKubernetesManifest attempts to infer whether the given object is a valid
// kubernetes resource by verifying the presence of apiVersion and kind. These
// two fields are required for kubernetes to accept any resource.
func isKubernetesManifest(obj objx.Map) bool {
	return true &&
		obj.Get("apiVersion").IsStr() && obj.Get("apiVersion").Str() != "" &&
		obj.Get("kind").IsStr() && obj.Get("kind").Str() != ""
}
<commit_msg>fix(process): Order keys in walkObj (#307)<commit_after>package process

import (
	"fmt"
	"sort"
	"strings"

	"github.com/stretchr/objx"

	"github.com/grafana/tanka/pkg/kubernetes/manifest"
)

// Extract scans the raw Jsonnet evaluation result (JSON tree) for objects that
// look like Kubernetes objects and extracts those into a flat map, indexed by
// their path in the original JSON tree
func Extract(raw interface{}) (map[string]manifest.Manifest, error) {
	extracted := make(map[string]manifest.Manifest)
	if err := walkJSON(raw, extracted, nil); err != nil {
		return nil, err
	}
	return extracted, nil
}

// walkJSON recurses into either a map or list, returning a list of all objects that look
// like kubernetes resources. We support resources at an arbitrary level of nesting, and
// return an error if a node is not walkable.
//
// Handling the different types is quite gross, so we split this method into a generic
// walkJSON, and then walkObj/walkList to handle the two different types of collection we
// support.
func walkJSON(ptr interface{}, extracted map[string]manifest.Manifest, path trace) error {
	// check for known types
	switch v := ptr.(type) {
	case map[string]interface{}:
		return walkObj(v, extracted, path)
	case []interface{}:
		return walkList(v, extracted, path)
	}

	return ErrorPrimitiveReached{
		path:      path.Base(),
		key:       path.Name(),
		primitive: ptr,
	}
}

func walkList(list []interface{}, extracted map[string]manifest.Manifest, path trace) error {
	for idx, value := range list {
		err := walkJSON(value, extracted, append(path, fmt.Sprintf("[%d]", idx)))
		if err != nil {
			return err
		}
	}
	return nil
}

func walkObj(obj objx.Map, extracted map[string]manifest.Manifest, path trace) error {
	obj = obj.Exclude([]string{"__ksonnet"}) // remove our private ksonnet field

	// This looks like a kubernetes manifest, so make one and return it
	if isKubernetesManifest(obj) {
		m, err := manifest.NewFromObj(obj)
		if err != nil {
			return err.(*manifest.SchemaError).WithName(path.Full())
		}

		extracted[path.Full()] = m
		return nil
	}

	keys := make([]string, 0, len(obj))
	for k := range obj {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, key := range keys {
		path := append(path, key)
		if obj[key] == nil { // result from false if condition in Jsonnet
			continue
		}
		err := walkJSON(obj[key], extracted, path)
		if err != nil {
			return err
		}
	}

	return nil
}

type trace []string

func (t trace) Full() string {
	return "." + strings.Join(t, ".")
}

func (t trace) Base() string {
	if len(t) > 0 {
		t = t[:len(t)-1]
	}
	return "." + strings.Join(t, ".")
}

func (t trace) Name() string {
	if len(t) > 0 {
		return t[len(t)-1]
	}

	return ""
}

// ErrorPrimitiveReached occurs when walkJSON reaches the end of nested dicts without finding a valid Kubernetes manifest
type ErrorPrimitiveReached struct {
	path, key string
	primitive interface{}
}

func (e ErrorPrimitiveReached) Error() string {
	return fmt.Sprintf("recursion did not resolve in a valid Kubernetes object. "+
		" In path `%s` found key `%s` of type `%T` instead.",
		e.path, e.key, e.primitive)
}

// isKubernetesManifest attempts to infer whether the given object is a valid
// kubernetes resource by verifying the presence of apiVersion and kind. These
// two fields are required for kubernetes to accept any resource.
func isKubernetesManifest(obj objx.Map) bool {
	return true &&
		obj.Get("apiVersion").IsStr() && obj.Get("apiVersion").Str() != "" &&
		obj.Get("kind").IsStr() && obj.Get("kind").Str() != ""
}
<|endoftext|>


<commit_before>package project

var (
	description        = "The aws-operator manages Kubernetes clusters running on AWS."
	gitSHA             = "n/a"
	name        string = "aws-operator"
	source      string = "https://github.com/giantswarm/aws-operator"
	version            = "9.1.3"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<commit_msg>Bump version to 9.1.4-dev (#2815)<commit_after>package project

var (
	description        = "The aws-operator manages Kubernetes clusters running on AWS."
	gitSHA             = "n/a"
	name        string = "aws-operator"
	source      string = "https://github.com/giantswarm/aws-operator"
	version            = "9.1.4-dev"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<|endoftext|>


<commit_before>package session

import (
	"fmt"
	"sync"
	"time"

	"github.com/codeskyblue/go-uuid"
	"github.com/gorilla/websocket"
	"github.com/op/go-logging"
	"github.com/raintank/raintank-apps/pkg/message"
)

var log = logging.MustGetLogger("default")

type Handler interface {
	HandleMessage(message *message.Event)
}

type Session struct {
	sync.Mutex
	Id               string
	EventHandlers    map[string]*message.Handler
	Conn             *websocket.Conn
	writeMessageChan chan *message.Message
	closing          bool
	rDone            chan struct{}
	wDone            chan struct{}
}

func NewSession(conn *websocket.Conn, writeQueueSize int) *Session {
	s := &Session{
		Id:               uuid.NewUUID().String(),
		EventHandlers:    make(map[string]*message.Handler),
		Conn:             conn,
		writeMessageChan: make(chan *message.Message, writeQueueSize),
	}
	return s
}

func (s *Session) On(event string, f interface{}) error {
	s.Lock()
	defer s.Unlock()
	if _, ok := s.EventHandlers[event]; ok {
		return fmt.Errorf("Handler for event %s already defined", event)
	}
	h, err := message.NewHandler(f)
	if err != nil {
		return err
	}
	s.EventHandlers[event] = h
	return nil
}

func (s *Session) Emit(event *message.Event) error {
	msg, err := event.ToMessage()
	if err != nil {
		return err
	}
	s.writeMessageChan <- msg
	return nil
}

func (s *Session) Start() {
	s.rDone = make(chan struct{})
	s.wDone = make(chan struct{})
	go s.socketReader(s.rDone)
	go s.socketWriter(s.wDone)

	select {
	case <-s.wDone:
		log.Debug("writer closed.")
		s.disconnected()
		return
	case <-s.rDone:
		log.Debug("reader closed.")
		s.disconnected()
		return
	}
}

func (s *Session) Close() {
	s.Lock()
	s.closing = true
	s.Unlock()
	s.writeMessageChan <- &message.Message{websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")}
	close(s.writeMessageChan)
	log.Info("waiting for socketWriter to finish sending all messages.")
	select {
	case <-s.wDone:
	case <-time.After(time.Second * 2):
		log.Warningf("socketWriter taking too long. Closing connectio now. %d messages in queue will be lost.", len(s.writeMessageChan)+1)
	}
	s.Conn.Close()
}

func (s *Session) disconnected() {
	//dont emit a disconnect event if Close() was called.
	s.Lock()
	if !s.closing {
		if h, ok := s.EventHandlers["disconnect"]; ok {
			h.Call([]byte{})
		}
	}
	s.Unlock()
}

func (s *Session) socketReader(done chan struct{}) {
	defer s.Conn.Close()
	defer close(done)
	for {
		mtype, body, err := s.Conn.ReadMessage()
		if err != nil {
			log.Errorf("read: %s", err)
			return
		}
		msg := &message.Message{mtype, body}
		e, err := msg.ToEvent()
		if err != nil {
			log.Error("Error: failed to decode message to Event.")
		}
		s.Lock()
		h, ok := s.EventHandlers[e.Event]
		s.Unlock()
		if ok {
			h.Call(e.Payload)
		} else {
			log.Warningf("no handler for event: %s", e.Event)
		}
	}
}

func (s *Session) socketWriter(done chan struct{}) {
	defer s.Conn.Close()
	defer close(done)

	for msg := range s.writeMessageChan {
		log.Debugf("socket %s sending message", s.Id)
		err := s.Conn.WriteMessage(msg.MessageType, msg.Body)
		retryDelay := time.Millisecond * 25
		for err != nil {
			s.Lock()
			closing := s.closing
			s.Unlock()
			if closing {
				return
			}
			log.Errorf("write: %s", err)
			if retryDelay < time.Second {
				retryDelay = retryDelay * 2
			}
			time.Sleep(retryDelay)
			err = s.Conn.WriteMessage(msg.MessageType, msg.Body)
		}
	}
	log.Debug("writeMessageChan closed.")
}
<commit_msg>fix deadlock in handling disconnect.<commit_after>package session

import (
	"fmt"
	"sync"
	"time"

	"github.com/codeskyblue/go-uuid"
	"github.com/gorilla/websocket"
	"github.com/op/go-logging"
	"github.com/raintank/raintank-apps/pkg/message"
)

var log = logging.MustGetLogger("default")

type Handler interface {
	HandleMessage(message *message.Event)
}

type Session struct {
	sync.Mutex
	Id               string
	EventHandlers    map[string]*message.Handler
	Conn             *websocket.Conn
	writeMessageChan chan *message.Message
	closing          bool
	rDone            chan struct{}
	wDone            chan struct{}
}

func NewSession(conn *websocket.Conn, writeQueueSize int) *Session {
	s := &Session{
		Id:               uuid.NewUUID().String(),
		EventHandlers:    make(map[string]*message.Handler),
		Conn:             conn,
		writeMessageChan: make(chan *message.Message, writeQueueSize),
	}
	return s
}

func (s *Session) On(event string, f interface{}) error {
	s.Lock()
	defer s.Unlock()
	if _, ok := s.EventHandlers[event]; ok {
		return fmt.Errorf("Handler for event %s already defined", event)
	}
	h, err := message.NewHandler(f)
	if err != nil {
		return err
	}
	s.EventHandlers[event] = h
	return nil
}

func (s *Session) Emit(event *message.Event) error {
	msg, err := event.ToMessage()
	if err != nil {
		return err
	}
	s.writeMessageChan <- msg
	return nil
}

func (s *Session) Start() {
	s.rDone = make(chan struct{})
	s.wDone = make(chan struct{})
	go s.socketReader(s.rDone)
	go s.socketWriter(s.wDone)

	select {
	case <-s.wDone:
		log.Debug("writer closed.")
		s.disconnected()
		return
	case <-s.rDone:
		log.Debug("reader closed.")
		s.disconnected()
		return
	}
}

func (s *Session) Close() {
	s.Lock()
	s.closing = true
	s.Unlock()
	s.writeMessageChan <- &message.Message{websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")}
	close(s.writeMessageChan)
	log.Info("waiting for socketWriter to finish sending all messages.")
	select {
	case <-s.wDone:
	case <-time.After(time.Second * 2):
		log.Warningf("socketWriter taking too long. Closing connectio now. %d messages in queue will be lost.", len(s.writeMessageChan)+1)
	}
	s.Conn.Close()
}

func (s *Session) disconnected() {
	//dont emit a disconnect event if Close() was called.
	closing := false
	s.Lock()
	if !s.closing {
		closing = true
	}
	s.Unlock()
	if closing {
		if h, ok := s.EventHandlers["disconnect"]; ok {
			h.Call([]byte{})
		}
	}
}

func (s *Session) socketReader(done chan struct{}) {
	defer s.Conn.Close()
	defer close(done)
	for {
		mtype, body, err := s.Conn.ReadMessage()
		if err != nil {
			log.Errorf("read: %s", err)
			return
		}
		msg := &message.Message{mtype, body}
		e, err := msg.ToEvent()
		if err != nil {
			log.Error("Error: failed to decode message to Event.")
		}
		s.Lock()
		h, ok := s.EventHandlers[e.Event]
		s.Unlock()
		if ok {
			h.Call(e.Payload)
		} else {
			log.Warningf("no handler for event: %s", e.Event)
		}
	}
}

func (s *Session) socketWriter(done chan struct{}) {
	defer s.Conn.Close()
	defer close(done)

	for msg := range s.writeMessageChan {
		log.Debugf("socket %s sending message", s.Id)
		err := s.Conn.WriteMessage(msg.MessageType, msg.Body)
		retryDelay := time.Millisecond * 25
		for err != nil {
			s.Lock()
			closing := s.closing
			s.Unlock()
			if closing {
				return
			}
			log.Errorf("write: %s", err)
			if retryDelay < time.Second {
				retryDelay = retryDelay * 2
			}
			time.Sleep(retryDelay)
			err = s.Conn.WriteMessage(msg.MessageType, msg.Body)
		}
	}
	log.Debug("writeMessageChan closed.")
}
<|endoftext|>