<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package remote

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/golang/glog"
	"google.golang.org/grpc"

	internalapi "k8s.io/kubernetes/pkg/kubelet/api"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
	utilexec "k8s.io/kubernetes/pkg/util/exec"
)

// RemoteRuntimeService is a gRPC implementation of internalapi.RuntimeService.
type RemoteRuntimeService struct {
	timeout       time.Duration
	runtimeClient runtimeapi.RuntimeServiceClient
}

// NewRemoteRuntimeService creates a new internalapi.RuntimeService.
func NewRemoteRuntimeService(addr string, connectionTimout time.Duration) (internalapi.RuntimeService, error) {
	glog.Infof("Connecting to runtime service %s", addr)
	conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithTimeout(connectionTimout), grpc.WithDialer(dial))
	if err != nil {
		glog.Errorf("Connect remote runtime %s failed: %v", addr, err)
		return nil, err
	}

	return &RemoteRuntimeService{
		timeout:       connectionTimout,
		runtimeClient: runtimeapi.NewRuntimeServiceClient(conn),
	}, nil
}

// Version returns the runtime name, runtime version and runtime API version.
func (r *RemoteRuntimeService) Version(apiVersion string) (*runtimeapi.VersionResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	typedVersion, err := r.runtimeClient.Version(ctx, &runtimeapi.VersionRequest{
		Version: apiVersion,
	})
	if err != nil {
		glog.Errorf("Version from runtime service failed: %v", err)
		return nil, err
	}

	if typedVersion.Version == "" || typedVersion.RuntimeName == "" || typedVersion.RuntimeApiVersion == "" || typedVersion.RuntimeVersion == "" {
		return nil, fmt.Errorf("not all fields are set in VersionResponse (%q)", *typedVersion)
	}

	return typedVersion, err
}

// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
func (r *RemoteRuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig) (string, error) {
	// Use 2 times longer timeout for sandbox operation (4 mins by defualt)
	// TODO: Make the pod sandbox timeout configurable.
	ctx, cancel := getContextWithTimeout(r.timeout * 2)
	defer cancel()

	resp, err := r.runtimeClient.RunPodSandbox(ctx, &runtimeapi.RunPodSandboxRequest{
		Config: config,
	})
	if err != nil {
		glog.Errorf("RunPodSandbox from runtime service failed: %v", err)
		return "", err
	}

	if resp.PodSandboxId == "" {
		errorMessage := fmt.Sprintf("PodSandboxId is not set for sandbox %q", config.GetMetadata())
		glog.Errorf("RunPodSandbox failed: %s", errorMessage)
		return "", errors.New(errorMessage)
	}

	return resp.PodSandboxId, nil
}

// StopPodSandbox stops the sandbox. If there are any running containers in the
// sandbox, they should be forced to termination.
func (r *RemoteRuntimeService) StopPodSandbox(podSandBoxID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StopPodSandbox(ctx, &runtimeapi.StopPodSandboxRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		glog.Errorf("StopPodSandbox %q from runtime service failed: %v", podSandBoxID, err)
		return err
	}

	return nil
}

// RemovePodSandbox removes the sandbox. If there are any containers in the
// sandbox, they should be forcibly removed.
func (r *RemoteRuntimeService) RemovePodSandbox(podSandBoxID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.RemovePodSandbox(ctx, &runtimeapi.RemovePodSandboxRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		glog.Errorf("RemovePodSandbox %q from runtime service failed: %v", podSandBoxID, err)
		return err
	}

	return nil
}

// PodSandboxStatus returns the status of the PodSandbox.
func (r *RemoteRuntimeService) PodSandboxStatus(podSandBoxID string) (*runtimeapi.PodSandboxStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.PodSandboxStatus(ctx, &runtimeapi.PodSandboxStatusRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		return nil, err
	}

	if resp.Status != nil {
		if err := verifySandboxStatus(resp.Status); err != nil {
			return nil, err
		}
	}

	return resp.Status, nil
}

// ListPodSandbox returns a list of PodSandboxes.
func (r *RemoteRuntimeService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ListPodSandbox(ctx, &runtimeapi.ListPodSandboxRequest{
		Filter: filter,
	})
	if err != nil {
		glog.Errorf("ListPodSandbox with filter %q from runtime service failed: %v", filter, err)
		return nil, err
	}

	return resp.Items, nil
}

// CreateContainer creates a new container in the specified PodSandbox.
func (r *RemoteRuntimeService) CreateContainer(podSandBoxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.CreateContainer(ctx, &runtimeapi.CreateContainerRequest{
		PodSandboxId:  podSandBoxID,
		Config:        config,
		SandboxConfig: sandboxConfig,
	})
	if err != nil {
		glog.Errorf("CreateContainer in sandbox %q from runtime service failed: %v", podSandBoxID, err)
		return "", err
	}

	if resp.ContainerId == "" {
		errorMessage := fmt.Sprintf("ContainerId is not set for container %q", config.GetMetadata())
		glog.Errorf("CreateContainer failed: %s", errorMessage)
		return "", errors.New(errorMessage)
	}

	return resp.ContainerId, nil
}

// StartContainer starts the container.
func (r *RemoteRuntimeService) StartContainer(containerID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StartContainer(ctx, &runtimeapi.StartContainerRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("StartContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// StopContainer stops a running container with a grace period (i.e., timeout).
func (r *RemoteRuntimeService) StopContainer(containerID string, timeout int64) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StopContainer(ctx, &runtimeapi.StopContainerRequest{
		ContainerId: containerID,
		Timeout:     timeout,
	})
	if err != nil {
		glog.Errorf("StopContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// RemoveContainer removes the container. If the container is running, the container
// should be forced to removal.
func (r *RemoteRuntimeService) RemoveContainer(containerID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.RemoveContainer(ctx, &runtimeapi.RemoveContainerRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("RemoveContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// ListContainers lists containers by filters.
func (r *RemoteRuntimeService) ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ListContainers(ctx, &runtimeapi.ListContainersRequest{
		Filter: filter,
	})
	if err != nil {
		glog.Errorf("ListContainers with filter %q from runtime service failed: %v", filter, err)
		return nil, err
	}

	return resp.Containers, nil
}

// ContainerStatus returns the container status.
func (r *RemoteRuntimeService) ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ContainerStatus(ctx, &runtimeapi.ContainerStatusRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("ContainerStatus %q from runtime service failed: %v", containerID, err)
		return nil, err
	}

	if resp.Status != nil {
		if err := verifyContainerStatus(resp.Status); err != nil {
			glog.Errorf("ContainerStatus of %q failed: %v", containerID, err)
			return nil, err
		}
	}

	return resp.Status, nil
}

// ExecSync executes a command in the container, and returns the stdout output.
// If command exits with a non-zero exit code, an error is returned.
func (r *RemoteRuntimeService) ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error) {
	ctx, cancel := getContextWithTimeout(timeout)
	if timeout == 0 {
		// Do not set timeout when timeout is 0.
		ctx, cancel = getContextWithCancel()
	}
	defer cancel()

	timeoutSeconds := int64(timeout.Seconds())
	req := &runtimeapi.ExecSyncRequest{
		ContainerId: containerID,
		Cmd:         cmd,
		Timeout:     timeoutSeconds,
	}
	resp, err := r.runtimeClient.ExecSync(ctx, req)
	if err != nil {
		glog.Errorf("ExecSync %s '%s' from runtime service failed: %v", containerID, strings.Join(cmd, " "), err)
		return nil, nil, err
	}

	err = nil
	if resp.ExitCode != 0 {
		err = utilexec.CodeExitError{
			Err:  fmt.Errorf("command '%s' exited with %d: %s", strings.Join(cmd, " "), resp.ExitCode, resp.Stderr),
			Code: int(resp.ExitCode),
		}
	}

	return resp.Stdout, resp.Stderr, err
}

// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
func (r *RemoteRuntimeService) Exec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Exec(ctx, req)
	if err != nil {
		glog.Errorf("Exec %s '%s' from runtime service failed: %v", req.ContainerId, strings.Join(req.Cmd, " "), err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp, nil
}

// Attach prepares a streaming endpoint to attach to a running container, and returns the address.
func (r *RemoteRuntimeService) Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Attach(ctx, req)
	if err != nil {
		glog.Errorf("Attach %s from runtime service failed: %v", req.ContainerId, err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}
	return resp, nil
}

// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
func (r *RemoteRuntimeService) PortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.PortForward(ctx, req)
	if err != nil {
		glog.Errorf("PortForward %s from runtime service failed: %v", req.PodSandboxId, err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp, nil
}

// UpdateRuntimeConfig updates the config of a runtime service. The only
// update payload currently supported is the pod CIDR assigned to a node,
// and the runtime service just proxies it down to the network plugin.
func (r *RemoteRuntimeService) UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	// Response doesn't contain anything of interest. This translates to an
	// Event notification to the network plugin, which can't fail, so we're
	// really looking to surface destination unreachable.
	_, err := r.runtimeClient.UpdateRuntimeConfig(ctx, &runtimeapi.UpdateRuntimeConfigRequest{
		RuntimeConfig: runtimeConfig,
	})

	if err != nil {
		return err
	}

	return nil
}

// Status returns the status of the runtime.
func (r *RemoteRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Status(ctx, &runtimeapi.StatusRequest{})
	if err != nil {
		glog.Errorf("Status from runtime service failed: %v", err)
		return nil, err
	}

	if resp.Status == nil || len(resp.Status.Conditions) < 2 {
		errorMessage := "RuntimeReady or NetworkReady condition are not set"
		glog.Errorf("Status failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp.Status, nil
}
<commit_msg>Fix typo of defualt<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package remote

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/golang/glog"
	"google.golang.org/grpc"

	internalapi "k8s.io/kubernetes/pkg/kubelet/api"
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
	utilexec "k8s.io/kubernetes/pkg/util/exec"
)

// RemoteRuntimeService is a gRPC implementation of internalapi.RuntimeService.
type RemoteRuntimeService struct {
	timeout       time.Duration
	runtimeClient runtimeapi.RuntimeServiceClient
}

// NewRemoteRuntimeService creates a new internalapi.RuntimeService.
func NewRemoteRuntimeService(addr string, connectionTimout time.Duration) (internalapi.RuntimeService, error) {
	glog.Infof("Connecting to runtime service %s", addr)
	conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithTimeout(connectionTimout), grpc.WithDialer(dial))
	if err != nil {
		glog.Errorf("Connect remote runtime %s failed: %v", addr, err)
		return nil, err
	}

	return &RemoteRuntimeService{
		timeout:       connectionTimout,
		runtimeClient: runtimeapi.NewRuntimeServiceClient(conn),
	}, nil
}

// Version returns the runtime name, runtime version and runtime API version.
func (r *RemoteRuntimeService) Version(apiVersion string) (*runtimeapi.VersionResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	typedVersion, err := r.runtimeClient.Version(ctx, &runtimeapi.VersionRequest{
		Version: apiVersion,
	})
	if err != nil {
		glog.Errorf("Version from runtime service failed: %v", err)
		return nil, err
	}

	if typedVersion.Version == "" || typedVersion.RuntimeName == "" || typedVersion.RuntimeApiVersion == "" || typedVersion.RuntimeVersion == "" {
		return nil, fmt.Errorf("not all fields are set in VersionResponse (%q)", *typedVersion)
	}

	return typedVersion, err
}

// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
func (r *RemoteRuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig) (string, error) {
	// Use 2 times longer timeout for sandbox operation (4 mins by default)
	// TODO: Make the pod sandbox timeout configurable.
	ctx, cancel := getContextWithTimeout(r.timeout * 2)
	defer cancel()

	resp, err := r.runtimeClient.RunPodSandbox(ctx, &runtimeapi.RunPodSandboxRequest{
		Config: config,
	})
	if err != nil {
		glog.Errorf("RunPodSandbox from runtime service failed: %v", err)
		return "", err
	}

	if resp.PodSandboxId == "" {
		errorMessage := fmt.Sprintf("PodSandboxId is not set for sandbox %q", config.GetMetadata())
		glog.Errorf("RunPodSandbox failed: %s", errorMessage)
		return "", errors.New(errorMessage)
	}

	return resp.PodSandboxId, nil
}

// StopPodSandbox stops the sandbox. If there are any running containers in the
// sandbox, they should be forced to termination.
func (r *RemoteRuntimeService) StopPodSandbox(podSandBoxID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StopPodSandbox(ctx, &runtimeapi.StopPodSandboxRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		glog.Errorf("StopPodSandbox %q from runtime service failed: %v", podSandBoxID, err)
		return err
	}

	return nil
}

// RemovePodSandbox removes the sandbox. If there are any containers in the
// sandbox, they should be forcibly removed.
func (r *RemoteRuntimeService) RemovePodSandbox(podSandBoxID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.RemovePodSandbox(ctx, &runtimeapi.RemovePodSandboxRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		glog.Errorf("RemovePodSandbox %q from runtime service failed: %v", podSandBoxID, err)
		return err
	}

	return nil
}

// PodSandboxStatus returns the status of the PodSandbox.
func (r *RemoteRuntimeService) PodSandboxStatus(podSandBoxID string) (*runtimeapi.PodSandboxStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.PodSandboxStatus(ctx, &runtimeapi.PodSandboxStatusRequest{
		PodSandboxId: podSandBoxID,
	})
	if err != nil {
		return nil, err
	}

	if resp.Status != nil {
		if err := verifySandboxStatus(resp.Status); err != nil {
			return nil, err
		}
	}

	return resp.Status, nil
}

// ListPodSandbox returns a list of PodSandboxes.
func (r *RemoteRuntimeService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ListPodSandbox(ctx, &runtimeapi.ListPodSandboxRequest{
		Filter: filter,
	})
	if err != nil {
		glog.Errorf("ListPodSandbox with filter %q from runtime service failed: %v", filter, err)
		return nil, err
	}

	return resp.Items, nil
}

// CreateContainer creates a new container in the specified PodSandbox.
func (r *RemoteRuntimeService) CreateContainer(podSandBoxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.CreateContainer(ctx, &runtimeapi.CreateContainerRequest{
		PodSandboxId:  podSandBoxID,
		Config:        config,
		SandboxConfig: sandboxConfig,
	})
	if err != nil {
		glog.Errorf("CreateContainer in sandbox %q from runtime service failed: %v", podSandBoxID, err)
		return "", err
	}

	if resp.ContainerId == "" {
		errorMessage := fmt.Sprintf("ContainerId is not set for container %q", config.GetMetadata())
		glog.Errorf("CreateContainer failed: %s", errorMessage)
		return "", errors.New(errorMessage)
	}

	return resp.ContainerId, nil
}

// StartContainer starts the container.
func (r *RemoteRuntimeService) StartContainer(containerID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StartContainer(ctx, &runtimeapi.StartContainerRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("StartContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// StopContainer stops a running container with a grace period (i.e., timeout).
func (r *RemoteRuntimeService) StopContainer(containerID string, timeout int64) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.StopContainer(ctx, &runtimeapi.StopContainerRequest{
		ContainerId: containerID,
		Timeout:     timeout,
	})
	if err != nil {
		glog.Errorf("StopContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// RemoveContainer removes the container. If the container is running, the container
// should be forced to removal.
func (r *RemoteRuntimeService) RemoveContainer(containerID string) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	_, err := r.runtimeClient.RemoveContainer(ctx, &runtimeapi.RemoveContainerRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("RemoveContainer %q from runtime service failed: %v", containerID, err)
		return err
	}

	return nil
}

// ListContainers lists containers by filters.
func (r *RemoteRuntimeService) ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ListContainers(ctx, &runtimeapi.ListContainersRequest{
		Filter: filter,
	})
	if err != nil {
		glog.Errorf("ListContainers with filter %q from runtime service failed: %v", filter, err)
		return nil, err
	}

	return resp.Containers, nil
}

// ContainerStatus returns the container status.
func (r *RemoteRuntimeService) ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.ContainerStatus(ctx, &runtimeapi.ContainerStatusRequest{
		ContainerId: containerID,
	})
	if err != nil {
		glog.Errorf("ContainerStatus %q from runtime service failed: %v", containerID, err)
		return nil, err
	}

	if resp.Status != nil {
		if err := verifyContainerStatus(resp.Status); err != nil {
			glog.Errorf("ContainerStatus of %q failed: %v", containerID, err)
			return nil, err
		}
	}

	return resp.Status, nil
}

// ExecSync executes a command in the container, and returns the stdout output.
// If command exits with a non-zero exit code, an error is returned.
func (r *RemoteRuntimeService) ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error) {
	ctx, cancel := getContextWithTimeout(timeout)
	if timeout == 0 {
		// Do not set timeout when timeout is 0.
		ctx, cancel = getContextWithCancel()
	}
	defer cancel()

	timeoutSeconds := int64(timeout.Seconds())
	req := &runtimeapi.ExecSyncRequest{
		ContainerId: containerID,
		Cmd:         cmd,
		Timeout:     timeoutSeconds,
	}
	resp, err := r.runtimeClient.ExecSync(ctx, req)
	if err != nil {
		glog.Errorf("ExecSync %s '%s' from runtime service failed: %v", containerID, strings.Join(cmd, " "), err)
		return nil, nil, err
	}

	err = nil
	if resp.ExitCode != 0 {
		err = utilexec.CodeExitError{
			Err:  fmt.Errorf("command '%s' exited with %d: %s", strings.Join(cmd, " "), resp.ExitCode, resp.Stderr),
			Code: int(resp.ExitCode),
		}
	}

	return resp.Stdout, resp.Stderr, err
}

// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
func (r *RemoteRuntimeService) Exec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Exec(ctx, req)
	if err != nil {
		glog.Errorf("Exec %s '%s' from runtime service failed: %v", req.ContainerId, strings.Join(req.Cmd, " "), err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp, nil
}

// Attach prepares a streaming endpoint to attach to a running container, and returns the address.
func (r *RemoteRuntimeService) Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Attach(ctx, req)
	if err != nil {
		glog.Errorf("Attach %s from runtime service failed: %v", req.ContainerId, err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}
	return resp, nil
}

// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
func (r *RemoteRuntimeService) PortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.PortForward(ctx, req)
	if err != nil {
		glog.Errorf("PortForward %s from runtime service failed: %v", req.PodSandboxId, err)
		return nil, err
	}

	if resp.Url == "" {
		errorMessage := "URL is not set"
		glog.Errorf("Exec failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp, nil
}

// UpdateRuntimeConfig updates the config of a runtime service. The only
// update payload currently supported is the pod CIDR assigned to a node,
// and the runtime service just proxies it down to the network plugin.
func (r *RemoteRuntimeService) UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	// Response doesn't contain anything of interest. This translates to an
	// Event notification to the network plugin, which can't fail, so we're
	// really looking to surface destination unreachable.
	_, err := r.runtimeClient.UpdateRuntimeConfig(ctx, &runtimeapi.UpdateRuntimeConfigRequest{
		RuntimeConfig: runtimeConfig,
	})

	if err != nil {
		return err
	}

	return nil
}

// Status returns the status of the runtime.
func (r *RemoteRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {
	ctx, cancel := getContextWithTimeout(r.timeout)
	defer cancel()

	resp, err := r.runtimeClient.Status(ctx, &runtimeapi.StatusRequest{})
	if err != nil {
		glog.Errorf("Status from runtime service failed: %v", err)
		return nil, err
	}

	if resp.Status == nil || len(resp.Status.Conditions) < 2 {
		errorMessage := "RuntimeReady or NetworkReady condition are not set"
		glog.Errorf("Status failed: %s", errorMessage)
		return nil, errors.New(errorMessage)
	}

	return resp.Status, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package etcd

import (
	"context"
	"fmt"

	"k8s.io/apimachinery/pkg/api/meta"
	metatable "k8s.io/apimachinery/pkg/api/meta/table"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration"
	"k8s.io/kube-aggregator/pkg/registry/apiservice"
)

// REST implements a RESTStorage for API services against etcd
type REST struct {
	*genericregistry.Store
}

// NewREST returns a RESTStorage object that will work against API services.
func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST {
	strategy := apiservice.NewStrategy(scheme)
	store := &genericregistry.Store{
		NewFunc:                  func() runtime.Object { return &apiregistration.APIService{} },
		NewListFunc:              func() runtime.Object { return &apiregistration.APIServiceList{} },
		PredicateFunc:            apiservice.MatchAPIService,
		DefaultQualifiedResource: apiregistration.Resource("apiservices"),

		CreateStrategy: strategy,
		UpdateStrategy: strategy,
		DeleteStrategy: strategy,
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: apiservice.GetAttrs}
	if err := store.CompleteWithOptions(options); err != nil {
		panic(err) // TODO: Propagate error up
	}
	return &REST{store}
}

var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()

// ConvertToTable implements the TableConvertor interface for REST.
func (c *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
	table := &metav1beta1.Table{
		ColumnDefinitions: []metav1beta1.TableColumnDefinition{
			{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
			{Name: "Service", Type: "string", Description: "The reference to the service that hosts this API endpoint."},
			{Name: "Available", Type: "string", Description: "Whether this service is available."},
			{Name: "Age", Type: "string", Description: swaggerMetadataDescriptions["creationTimestamp"]},
		},
	}
	if m, err := meta.ListAccessor(obj); err == nil {
		table.ResourceVersion = m.GetResourceVersion()
		table.SelfLink = m.GetSelfLink()
		table.Continue = m.GetContinue()
		table.SetRemainingItemCount(m.GetRemainingItemCount())
	} else {
		if m, err := meta.CommonAccessor(obj); err == nil {
			table.ResourceVersion = m.GetResourceVersion()
			table.SelfLink = m.GetSelfLink()
		}
	}

	var err error
	table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
		svc := obj.(*apiregistration.APIService)
		service := "Local"
		if svc.Spec.Service != nil {
			service = fmt.Sprintf("%s/%s", svc.Spec.Service.Namespace, svc.Spec.Service.Name)
		}
		status := string(apiregistration.ConditionUnknown)
		if condition := getCondition(svc.Status.Conditions, "Available"); condition != nil {
			switch {
			case condition.Status == apiregistration.ConditionTrue:
				status = string(condition.Status)
			case len(condition.Reason) > 0:
				status = fmt.Sprintf("%s (%s)", condition.Status, condition.Reason)
			default:
				status = string(condition.Status)
			}
		}
		return []interface{}{name, service, status, age}, nil
	})
	return table, err
}

func getCondition(conditions []apiregistration.APIServiceCondition, conditionType apiregistration.APIServiceConditionType) *apiregistration.APIServiceCondition {
	for i, condition := range conditions {
		if condition.Type == conditionType {
			return &conditions[i]
		}
	}
	return nil
}

// NewStatusREST makes a RESTStorage for status that has more limited options.
// It is based on the original REST so that we can share the same underlying store
func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
	statusStore := *rest.Store
	statusStore.CreateStrategy = nil
	statusStore.DeleteStrategy = nil
	statusStore.UpdateStrategy = apiservice.NewStatusStrategy(scheme)
	return &StatusREST{store: &statusStore}
}

// StatusREST implements the REST endpoint for changing the status of an APIService.
type StatusREST struct {
	store *genericregistry.Store
}

var _ = rest.Patcher(&StatusREST{})

// New creates a new APIService object.
func (r *StatusREST) New() runtime.Object {
	return &apiregistration.APIService{}
}

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
<commit_msg>Make GetRemainingItemCount/SetRemainingItemCount use pointers<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package etcd

import (
	"context"
	"fmt"

	"k8s.io/apimachinery/pkg/api/meta"
	metatable "k8s.io/apimachinery/pkg/api/meta/table"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration"
	"k8s.io/kube-aggregator/pkg/registry/apiservice"
)

// REST implements a RESTStorage for API services against etcd
type REST struct {
	*genericregistry.Store
}

// NewREST returns a RESTStorage object that will work against API services.
func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST {
	strategy := apiservice.NewStrategy(scheme)
	store := &genericregistry.Store{
		NewFunc:                  func() runtime.Object { return &apiregistration.APIService{} },
		NewListFunc:              func() runtime.Object { return &apiregistration.APIServiceList{} },
		PredicateFunc:            apiservice.MatchAPIService,
		DefaultQualifiedResource: apiregistration.Resource("apiservices"),

		CreateStrategy: strategy,
		UpdateStrategy: strategy,
		DeleteStrategy: strategy,
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: apiservice.GetAttrs}
	if err := store.CompleteWithOptions(options); err != nil {
		panic(err) // TODO: Propagate error up
	}
	return &REST{store}
}

var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()

// ConvertToTable implements the TableConvertor interface for REST.
func (c *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
	table := &metav1beta1.Table{
		ColumnDefinitions: []metav1beta1.TableColumnDefinition{
			{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
			{Name: "Service", Type: "string", Description: "The reference to the service that hosts this API endpoint."},
			{Name: "Available", Type: "string", Description: "Whether this service is available."},
			{Name: "Age", Type: "string", Description: swaggerMetadataDescriptions["creationTimestamp"]},
		},
	}
	if m, err := meta.ListAccessor(obj); err == nil {
		table.ResourceVersion = m.GetResourceVersion()
		table.SelfLink = m.GetSelfLink()
		table.Continue = m.GetContinue()
		table.RemainingItemCount = m.GetRemainingItemCount()
	} else {
		if m, err := meta.CommonAccessor(obj); err == nil {
			table.ResourceVersion = m.GetResourceVersion()
			table.SelfLink = m.GetSelfLink()
		}
	}

	var err error
	table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
		svc := obj.(*apiregistration.APIService)
		service := "Local"
		if svc.Spec.Service != nil {
			service = fmt.Sprintf("%s/%s", svc.Spec.Service.Namespace, svc.Spec.Service.Name)
		}
		status := string(apiregistration.ConditionUnknown)
		if condition := getCondition(svc.Status.Conditions, "Available"); condition != nil {
			switch {
			case condition.Status == apiregistration.ConditionTrue:
				status = string(condition.Status)
			case len(condition.Reason) > 0:
				status = fmt.Sprintf("%s (%s)", condition.Status, condition.Reason)
			default:
				status = string(condition.Status)
			}
		}
		return []interface{}{name, service, status, age}, nil
	})
	return table, err
}

func getCondition(conditions []apiregistration.APIServiceCondition, conditionType apiregistration.APIServiceConditionType) *apiregistration.APIServiceCondition {
	for i, condition := range conditions {
		if condition.Type == conditionType {
			return &conditions[i]
		}
	}
	return nil
}

// NewStatusREST makes a RESTStorage for status that has more limited options.
// It is based on the original REST so that we can share the same underlying store
func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
	statusStore := *rest.Store
	statusStore.CreateStrategy = nil
	statusStore.DeleteStrategy = nil
	statusStore.UpdateStrategy = apiservice.NewStatusStrategy(scheme)
	return &StatusREST{store: &statusStore}
}

// StatusREST implements the REST endpoint for changing the status of an APIService.
type StatusREST struct {
	store *genericregistry.Store
}

var _ = rest.Patcher(&StatusREST{})

// New creates a new APIService object.
func (r *StatusREST) New() runtime.Object {
	return &apiregistration.APIService{}
}

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package server

import (
	"strings"
	"testing"
)

func TestLoopbackHostPort(t *testing.T) {
	host, port, err := LoopbackHostPort("1.2.3.4:443")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if host != "1.2.3.4" {
		t.Fatalf("expected 1.2.3.4 as host, got %q", host)
	}
	if port != "443" {
		t.Fatalf("expected 443 as port, got %q", port)
	}

	host, port, err = LoopbackHostPort("0.0.0.0:443")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !strings.HasPrefix(host, "127.") {
		t.Fatalf("expected host to start with 127., got %q", host)
	}
	if port != "443" {
		t.Fatalf("expected 443 as port, got %q", port)
	}
}
<commit_msg>TestLoopbackHostPort should accept IPv6 loopback host<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package server

import (
	"net"
	"testing"
)

func TestLoopbackHostPort(t *testing.T) {
	host, port, err := LoopbackHostPort("1.2.3.4:443")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if host != "1.2.3.4" {
		t.Fatalf("expected 1.2.3.4 as host, got %q", host)
	}
	if port != "443" {
		t.Fatalf("expected 443 as port, got %q", port)
	}

	host, port, err = LoopbackHostPort("0.0.0.0:443")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ip := net.ParseIP(host); ip == nil || !ip.IsLoopback() {
		t.Fatalf("expected host to be loopback, got %q", host)
	}
	if port != "443" {
		t.Fatalf("expected 443 as port, got %q", port)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package azure_dd

import (
	"fmt"
	"os"
	"runtime"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/kubernetes/pkg/volume/util"
)

type azureDiskMounter struct {
	*dataDisk
	spec    *volume.Spec
	plugin  *azureDataDiskPlugin
	options volume.VolumeOptions
}

type azureDiskUnmounter struct {
	*dataDisk
	plugin *azureDataDiskPlugin
}

var _ volume.Unmounter = &azureDiskUnmounter{}
var _ volume.Mounter = &azureDiskMounter{}

func (m *azureDiskMounter) GetAttributes() volume.Attributes {
	volumeSource, _ := getVolumeSource(m.spec)
	return volume.Attributes{
		ReadOnly:        *volumeSource.ReadOnly,
		Managed:         !*volumeSource.ReadOnly,
		SupportsSELinux: true,
	}
}

func (m *azureDiskMounter) CanMount() error {
	return nil
}

func (m *azureDiskMounter) SetUp(fsGroup *int64) error {
	return m.SetUpAt(m.GetPath(), fsGroup)
}

func (m *azureDiskMounter) GetPath() string {
	return getPath(m.dataDisk.podUID, m.dataDisk.volumeName, m.plugin.host)
}

func (m *azureDiskMounter) SetUpAt(dir string, fsGroup *int64) error {
	mounter := m.plugin.host.GetMounter(m.plugin.GetPluginName())
	volumeSource, err := getVolumeSource(m.spec)

	if err != nil {
		glog.Infof("azureDisk - mounter failed to get volume source for spec %s", m.spec.Name())
		return err
	}

	diskName := volumeSource.DiskName
	mountPoint, err := mounter.IsLikelyNotMountPoint(dir)

	if err != nil && !os.IsNotExist(err) {
		glog.Infof("azureDisk - cannot validate mount point for disk %s on  %s %v", diskName, dir, err)
		return err
	}
	if !mountPoint {
		glog.V(4).Infof("azureDisk - already mounted to target %s", dir)
		return nil
	}

	if runtime.GOOS != "windows" {
		// in windows, we will use mklink to mount, will MkdirAll in Mount func
		if err := os.MkdirAll(dir, 0750); err != nil {
			glog.Errorf("azureDisk - mkdir failed on disk %s on dir: %s (%v)", diskName, dir, err)
			return err
		}
	}

	options := []string{"bind"}

	if *volumeSource.ReadOnly {
		options = append(options, "ro")
	}

	glog.V(4).Infof("azureDisk - Attempting to mount %s on %s", diskName, dir)
	isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
	globalPDPath, err := makeGlobalPDPath(m.plugin.host, volumeSource.DataDiskURI, isManagedDisk)

	if err != nil {
		return err
	}

	mountErr := mounter.Mount(globalPDPath, dir, *volumeSource.FSType, options)
	// Everything in the following control flow is meant as an
	// attempt cleanup a failed setupAt (bind mount)
	if mountErr != nil {
		glog.Infof("azureDisk - SetupAt:Mount disk:%s at dir:%s failed during mounting with error:%v, will attempt to clean up", diskName, dir, mountErr)
		mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
		if err != nil {
			return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup IsLikelyNotMountPoint check failed for disk:%s on dir:%s with error %v original-mountErr:%v", diskName, dir, err, mountErr)
		}

		if !mountPoint {
			if err = mounter.Unmount(dir); err != nil {
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup failed to unmount disk:%s on dir:%s with error:%v original-mountErr:%v", diskName, dir, err, mountErr)
			}
			mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
			if err != nil {
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup IsLikelyNotMountPoint for disk:%s on dir:%s check failed with error:%v original-mountErr:%v", diskName, dir, err, mountErr)
			}
			if !mountPoint {
				// not cool. leave for next sync loop.
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup disk %s is still mounted on %s during cleanup original-mountErr:%v, despite call to unmount(). Will try again next sync loop.", diskName, dir, mountErr)
			}
		}

		if err = os.Remove(dir); err != nil {
			return fmt.Errorf("azureDisk - SetupAt:Mount:Failure error cleaning up (removing dir:%s) with error:%v original-mountErr:%v", dir, err, mountErr)
		}

		glog.V(2).Infof("azureDisk - Mount of disk:%s on dir:%s failed with mount error:%v post failure clean up was completed", diskName, dir, err, mountErr)
		return mountErr
	}

	if !*volumeSource.ReadOnly {
		volume.SetVolumeOwnership(m, fsGroup)
	}

	glog.V(2).Infof("azureDisk - successfully mounted disk %s on %s", diskName, dir)
	return nil
}

func (u *azureDiskUnmounter) TearDown() error {
	return u.TearDownAt(u.GetPath())
}

func (u *azureDiskUnmounter) TearDownAt(dir string) error {
	if pathExists, pathErr := util.PathExists(dir); pathErr != nil {
		return fmt.Errorf("Error checking if path exists: %v", pathErr)
	} else if !pathExists {
		glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir)
		return nil
	}

	glog.V(4).Infof("azureDisk - TearDownAt: %s", dir)
	mounter := u.plugin.host.GetMounter(u.plugin.GetPluginName())
	mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
	if err != nil {
		return fmt.Errorf("azureDisk - TearDownAt: %s failed to do IsLikelyNotMountPoint %s", dir, err)
	}
	if mountPoint {
		if err := os.Remove(dir); err != nil {
			return fmt.Errorf("azureDisk - TearDownAt: %s failed to do os.Remove %s", dir, err)
		}
	}
	if err := mounter.Unmount(dir); err != nil {
		return fmt.Errorf("azureDisk - TearDownAt: %s failed to do mounter.Unmount %s", dir, err)
	}
	mountPoint, err = mounter.IsLikelyNotMountPoint(dir)
	if err != nil {
		return fmt.Errorf("azureDisk - TearTownAt:IsLikelyNotMountPoint check failed: %v", err)
	}

	if mountPoint {
		return os.Remove(dir)
	}

	return fmt.Errorf("azureDisk - failed to un-bind-mount volume dir")
}

func (u *azureDiskUnmounter) GetPath() string {
	return getPath(u.dataDisk.podUID, u.dataDisk.volumeName, u.plugin.host)
}
<commit_msg>fix azure pv crash due to readOnly nil<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package azure_dd

import (
	"fmt"
	"os"
	"runtime"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/kubernetes/pkg/volume/util"
)

type azureDiskMounter struct {
	*dataDisk
	spec    *volume.Spec
	plugin  *azureDataDiskPlugin
	options volume.VolumeOptions
}

type azureDiskUnmounter struct {
	*dataDisk
	plugin *azureDataDiskPlugin
}

var _ volume.Unmounter = &azureDiskUnmounter{}
var _ volume.Mounter = &azureDiskMounter{}

func (m *azureDiskMounter) GetAttributes() volume.Attributes {
	readOnly := false
	volumeSource, err := getVolumeSource(m.spec)
	if err != nil && volumeSource.ReadOnly != nil {
		readOnly = *volumeSource.ReadOnly
	}
	return volume.Attributes{
		ReadOnly:        readOnly,
		Managed:         !readOnly,
		SupportsSELinux: true,
	}
}

func (m *azureDiskMounter) CanMount() error {
	return nil
}

func (m *azureDiskMounter) SetUp(fsGroup *int64) error {
	return m.SetUpAt(m.GetPath(), fsGroup)
}

func (m *azureDiskMounter) GetPath() string {
	return getPath(m.dataDisk.podUID, m.dataDisk.volumeName, m.plugin.host)
}

func (m *azureDiskMounter) SetUpAt(dir string, fsGroup *int64) error {
	mounter := m.plugin.host.GetMounter(m.plugin.GetPluginName())
	volumeSource, err := getVolumeSource(m.spec)

	if err != nil {
		glog.Infof("azureDisk - mounter failed to get volume source for spec %s", m.spec.Name())
		return err
	}

	diskName := volumeSource.DiskName
	mountPoint, err := mounter.IsLikelyNotMountPoint(dir)

	if err != nil && !os.IsNotExist(err) {
		glog.Infof("azureDisk - cannot validate mount point for disk %s on  %s %v", diskName, dir, err)
		return err
	}
	if !mountPoint {
		glog.V(4).Infof("azureDisk - already mounted to target %s", dir)
		return nil
	}

	if runtime.GOOS != "windows" {
		// in windows, we will use mklink to mount, will MkdirAll in Mount func
		if err := os.MkdirAll(dir, 0750); err != nil {
			glog.Errorf("azureDisk - mkdir failed on disk %s on dir: %s (%v)", diskName, dir, err)
			return err
		}
	}

	options := []string{"bind"}

	if volumeSource.ReadOnly != nil && *volumeSource.ReadOnly {
		options = append(options, "ro")
	}

	glog.V(4).Infof("azureDisk - Attempting to mount %s on %s", diskName, dir)
	isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
	globalPDPath, err := makeGlobalPDPath(m.plugin.host, volumeSource.DataDiskURI, isManagedDisk)

	if err != nil {
		return err
	}

	mountErr := mounter.Mount(globalPDPath, dir, *volumeSource.FSType, options)
	// Everything in the following control flow is meant as an
	// attempt cleanup a failed setupAt (bind mount)
	if mountErr != nil {
		glog.Infof("azureDisk - SetupAt:Mount disk:%s at dir:%s failed during mounting with error:%v, will attempt to clean up", diskName, dir, mountErr)
		mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
		if err != nil {
			return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup IsLikelyNotMountPoint check failed for disk:%s on dir:%s with error %v original-mountErr:%v", diskName, dir, err, mountErr)
		}

		if !mountPoint {
			if err = mounter.Unmount(dir); err != nil {
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup failed to unmount disk:%s on dir:%s with error:%v original-mountErr:%v", diskName, dir, err, mountErr)
			}
			mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
			if err != nil {
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup IsLikelyNotMountPoint for disk:%s on dir:%s check failed with error:%v original-mountErr:%v", diskName, dir, err, mountErr)
			}
			if !mountPoint {
				// not cool. leave for next sync loop.
				return fmt.Errorf("azureDisk - SetupAt:Mount:Failure:cleanup disk %s is still mounted on %s during cleanup original-mountErr:%v, despite call to unmount(). Will try again next sync loop.", diskName, dir, mountErr)
			}
		}

		if err = os.Remove(dir); err != nil {
			return fmt.Errorf("azureDisk - SetupAt:Mount:Failure error cleaning up (removing dir:%s) with error:%v original-mountErr:%v", dir, err, mountErr)
		}

		glog.V(2).Infof("azureDisk - Mount of disk:%s on dir:%s failed with mount error:%v post failure clean up was completed", diskName, dir, err, mountErr)
		return mountErr
	}

	if volumeSource.ReadOnly == nil || !*volumeSource.ReadOnly {
		volume.SetVolumeOwnership(m, fsGroup)
	}

	glog.V(2).Infof("azureDisk - successfully mounted disk %s on %s", diskName, dir)
	return nil
}

func (u *azureDiskUnmounter) TearDown() error {
	return u.TearDownAt(u.GetPath())
}

func (u *azureDiskUnmounter) TearDownAt(dir string) error {
	if pathExists, pathErr := util.PathExists(dir); pathErr != nil {
		return fmt.Errorf("Error checking if path exists: %v", pathErr)
	} else if !pathExists {
		glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir)
		return nil
	}

	glog.V(4).Infof("azureDisk - TearDownAt: %s", dir)
	mounter := u.plugin.host.GetMounter(u.plugin.GetPluginName())
	mountPoint, err := mounter.IsLikelyNotMountPoint(dir)
	if err != nil {
		return fmt.Errorf("azureDisk - TearDownAt: %s failed to do IsLikelyNotMountPoint %s", dir, err)
	}
	if mountPoint {
		if err := os.Remove(dir); err != nil {
			return fmt.Errorf("azureDisk - TearDownAt: %s failed to do os.Remove %s", dir, err)
		}
	}
	if err := mounter.Unmount(dir); err != nil {
		return fmt.Errorf("azureDisk - TearDownAt: %s failed to do mounter.Unmount %s", dir, err)
	}
	mountPoint, err = mounter.IsLikelyNotMountPoint(dir)
	if err != nil {
		return fmt.Errorf("azureDisk - TearTownAt:IsLikelyNotMountPoint check failed: %v", err)
	}

	if mountPoint {
		return os.Remove(dir)
	}

	return fmt.Errorf("azureDisk - failed to un-bind-mount volume dir")
}

func (u *azureDiskUnmounter) GetPath() string {
	return getPath(u.dataDisk.podUID, u.dataDisk.volumeName, u.plugin.host)
}
<|endoftext|>


<commit_before>// Package imgur implements a Service which adds !commands for imgur image search
package imgur

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/matrix-org/go-neb/types"
	"github.com/matrix-org/gomatrix"
)

// ServiceType of the Imgur service
const ServiceType = "imgur"

var httpClient = &http.Client{}

type imgurGalleryImage struct {
	ID          string `json:"id"`          // id	string	The ID for the image
	Title       string `json:"title"`       // title	string	The title of the image.
	Description string `json:"description"` // description	string	Description of the image.
	DateTime    int64  `json:"datetime"`    // datetime	integer	Time inserted into the gallery, epoch time
	Type        string `json:"type"`        // type	string	Image MIME type.
	Animated    bool   `json:"animated"`    // animated	boolean	is the image animated
	Width       int    `json:"width"`       // width	integer	The width of the image in pixels
	Height      int    `json:"height"`      // height	integer	The height of the image in pixels
	Size        int64  `json:"size"`        // size	integer	The size of the image in bytes
	Views       int64  `json:"views"`       // views	integer	The number of image views
	Link        string `json:"link"`        // link	string	The direct link to the the image. (Note: if fetching an animated GIF that was over 20MB in original size, a .gif thumbnail will be returned)
	Gifv        string `json:"gifv"`        // gifv	string	OPTIONAL, The .gifv link. Only available if the image is animated and type is 'image/gif'.
	MP4         string `json:"mp4"`         // mp4	string	OPTIONAL, The direct link to the .mp4. Only available if the image is animated and type is 'image/gif'.
	MP4Size     int64  `json:"mp4_size"`    // mp4_size	integer	OPTIONAL, The Content-Length of the .mp4. Only available if the image is animated and type is 'image/gif'. Note that a zero value (0) is possible if the video has not yet been generated
	Looping     bool   `json:"looping"`     // looping	boolean	OPTIONAL, Whether the image has a looping animation. Only available if the image is animated and type is 'image/gif'.
	NSFW        bool   `json:"nsfw"`        // nsfw	boolean	Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available.
	Topic       string `json:"topic"`       // topic	string	Topic of the gallery image.
	Section     string `json:"section"`     // section	string	If the image has been categorized by our backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc)
	IsAlbum     bool   `json:"is_album"`    // is_album	boolean	If it's an album or not
	// ** Uninplemented fields **
	// bandwidth	integer	Bandwidth consumed by the image in bytes
	// deletehash	string	OPTIONAL, the deletehash, if you're logged in as the image owner
	// comment_count	int	Number of comments on the gallery image.
	// topic_id	integer	Topic ID of the gallery image.
	// vote	string	The current user's vote on the album. null if not signed in or if the user hasn't voted on it.
	// favorite	boolean	Indicates if the current user favorited the image. Defaults to false if not signed in.
	// account_url	string	The username of the account that uploaded it, or null.
	// account_id	integer	The account ID of the account that uploaded it, or null.
	// ups	integer	Upvotes for the image
	// downs	integer	Number of downvotes for the image
	// points	integer	Upvotes minus downvotes
	// score	integer	Imgur popularity score
}

type imgurGalleryAlbum struct {
	ID          string              `json:"id"`           // id	string	The ID for the album
	Title       string              `json:"title"`        // title	string	The title of the album.
	Description string              `json:"description"`  // description	string	Description of the album.
	DateTime    int64               `json:"datetime"`     // datetime	integer	Time inserted into the gallery, epoch time
	Views       int64               `json:"views"`        // views	integer	The number of album views
	Link        string              `json:"link"`         // link	string	The URL link to the album
	NSFW        bool                `json:"nsfw"`         // nsfw	boolean	Indicates if the album has been marked as nsfw or not. Defaults to null if information is not available.
	Topic       string              `json:"topic"`        // topic	string	Topic of the gallery album.
	IsAlbum     bool                `json:"is_album"`     // is_album	boolean	If it's an album or not
	Cover       string              `json:"cover"`        // cover	string	The ID of the album cover image
	CoverWidth  int                 `json:"cover_width"`  // cover_width	integer	The width, in pixels, of the album cover image
	CoverHeight int                 `json:"cover_height"` // cover_height	integer	The height, in pixels, of the album cover image
	ImagesCount int                 `json:"images_count"` // images_count	integer	The total number of images in the album
	Images      []imgurGalleryImage `json:"images"`       // images	Array of Images	An array of all the images in the album (only available when requesting the direct album)

	// ** Unimplemented fields **
	// account_url	string	The account username or null if it's anonymous.
	// account_id	integer	The account ID of the account that uploaded it, or null.
	// privacy	string	The privacy level of the album, you can only view public if not logged in as album owner
	// layout	string	The view layout of the album.
	// views	integer	The number of image views
	// ups	integer	Upvotes for the image
	// downs	integer	Number of downvotes for the image
	// points	integer	Upvotes minus downvotes
	// score	integer	Imgur popularity score
	// vote	string	The current user's vote on the album. null if not signed in or if the user hasn't voted on it.
	// favorite	boolean	Indicates if the current user favorited the album. Defaults to false if not signed in.
	// comment_count	int	Number of comments on the gallery album.
	// topic_id	integer	Topic ID of the gallery album.
}

type imgurSearchResponse struct {
	Data    []json.RawMessage `json:"data"`
	Success bool              `json:"success"` // Request completed successfully
	Status  int               `json:"status"`  // HTTP response code
}

// Service contains the Config fields for the Imgur service.
//
// Example request:
//   {
//			"client_id": "AIzaSyA4FD39..."
//			"client_secret": "ASdsaijwdfASD..."
//   }
type Service struct {
	types.DefaultService
	// The Imgur client ID
	ClientID string `json:"client_id"`
	// The API key to use when making HTTP requests to Imgur.
	ClientSecret string `json:"client_secret"`
}

// Commands supported:
//    !imgur some_search_query_without_quotes
// Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
	return []types.Command{
		types.Command{
			Path: []string{"imgur"},
			Command: func(roomID, userID string, args []string) (interface{}, error) {
				return s.cmdImgurImgSearch(client, roomID, userID, args)
			},
		},
		types.Command{
			Path: []string{"imgur", "help"},
			Command: func(roomID, userID string, args []string) (interface{}, error) {
				return usageMessage(), nil
			},
		},
	}
}

// usageMessage returns a matrix TextMessage representation of the service usage
func usageMessage() *gomatrix.TextMessage {
	return &gomatrix.TextMessage{"m.notice",
		`Usage: !imgur image_search_text`}
}

func (s *Service) cmdImgurImgSearch(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {

	if len(args) < 1 {
		return usageMessage(), nil
	}

	// Get the query text to search for.
	querySentence := strings.Join(args, " ")

	searchResultImage, searchResultAlbum, err := s.text2imgImgur(querySentence)

	if err != nil {
		return nil, err
	}

	// Image returned
	if searchResultImage != nil {
		var imgURL = searchResultImage.Link
		if imgURL == "" {
			return gomatrix.TextMessage{
				MsgType: "m.notice",
				Body:    "No image found!",
			}, nil
		}

		// FIXME -- Sometimes upload fails with a cryptic error - "msg=Upload request failed code=400"
		log.Printf("Uploading image at: %s", imgURL)
		resUpload, err := client.UploadLink(imgURL)
		if err != nil {
			return nil, fmt.Errorf("Failed to upload Imgur image (%s) to matrix: %s", imgURL, err.Error())
		}

		return gomatrix.ImageMessage{
			MsgType: "m.image",
			Body:    querySentence,
			URL:     resUpload.ContentURI,
			Info: gomatrix.ImageInfo{
				Height:   uint(searchResultImage.Height),
				Width:    uint(searchResultImage.Width),
				Mimetype: searchResultImage.Type,
			},
		}, nil
	} else if searchResultAlbum != nil {
		return gomatrix.TextMessage{
			MsgType: "m.notice",
			Body:    "Search returned an album - Not currently supported",
		}, nil
	} else {
		return gomatrix.TextMessage{
			MsgType: "m.notice",
			Body:    "No image found!",
		}, nil
	}
}

// text2imgImgur returns info about an image or an album
func (s *Service) text2imgImgur(query string) (*imgurGalleryImage, *imgurGalleryAlbum, error) {
	log.Info("Searching Imgur for an image of a ", query)

	query = url.QueryEscape(query)
	var base = "https://api.imgur.com/3/gallery/search"
	var sort = "time"  // time | viral | top
	var window = "all" // day | week | month | year | all
	var page = 1
	var urlString = fmt.Sprintf("%s/%s/%s/%d?q=%s", base, sort, window, page, query)

	u, err := url.Parse(urlString)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Add("Authorization", "Client-ID "+s.ClientID)
	res, err := httpClient.Do(req)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return nil, nil, err
	}
	if res.StatusCode > 200 {
		return nil, nil, fmt.Errorf("Request error: %d, %s", res.StatusCode, response2String(res))
	}

	var searchResults imgurSearchResponse
	if err := json.NewDecoder(res.Body).Decode(&searchResults); err != nil || searchResults.Data == nil {
		return nil, nil, fmt.Errorf("No images found - %s", err.Error())
	}

	log.Printf("%d results were returned from Imgur", len(searchResults.Data))
	for i := 0; i < len(searchResults.Data); i++ {
		// Return the first image result
		var image imgurGalleryImage
		if err := json.Unmarshal(searchResults.Data[i], &image); err == nil {
			return &image, nil, nil
		}
	}

	// Return an album
	// if dataInt["is_album"].(bool) {
	// 	var album imgurGalleryAlbum
	// 	if err := json.Unmarshal(searchResults.Data, &album); err != nil {
	// 		return nil, nil, fmt.Errorf("Failed to parse album data - %s", err.Error())
	// 	}
	//
	// 	return nil, &album, nil
	// }

	return nil, nil, fmt.Errorf("No images found")
}

// response2String returns a string representation of an HTTP response body
func response2String(res *http.Response) string {
	bs, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "Failed to decode response body"
	}
	str := string(bs)
	return str
}

// Initialise the service
func init() {
	types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
		return &Service{
			DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
		}
	})
}
<commit_msg>Check that response is actually an image<commit_after>// Package imgur implements a Service which adds !commands for imgur image search
package imgur

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/matrix-org/go-neb/types"
	"github.com/matrix-org/gomatrix"
)

// ServiceType of the Imgur service
const ServiceType = "imgur"

var httpClient = &http.Client{}

type imgurGalleryImage struct {
	ID          string `json:"id"`          // id	string	The ID for the image
	Title       string `json:"title"`       // title	string	The title of the image.
	Description string `json:"description"` // description	string	Description of the image.
	DateTime    int64  `json:"datetime"`    // datetime	integer	Time inserted into the gallery, epoch time
	Type        string `json:"type"`        // type	string	Image MIME type.
	Animated    bool   `json:"animated"`    // animated	boolean	is the image animated
	Width       int    `json:"width"`       // width	integer	The width of the image in pixels
	Height      int    `json:"height"`      // height	integer	The height of the image in pixels
	Size        int64  `json:"size"`        // size	integer	The size of the image in bytes
	Views       int64  `json:"views"`       // views	integer	The number of image views
	Link        string `json:"link"`        // link	string	The direct link to the the image. (Note: if fetching an animated GIF that was over 20MB in original size, a .gif thumbnail will be returned)
	Gifv        string `json:"gifv"`        // gifv	string	OPTIONAL, The .gifv link. Only available if the image is animated and type is 'image/gif'.
	MP4         string `json:"mp4"`         // mp4	string	OPTIONAL, The direct link to the .mp4. Only available if the image is animated and type is 'image/gif'.
	MP4Size     int64  `json:"mp4_size"`    // mp4_size	integer	OPTIONAL, The Content-Length of the .mp4. Only available if the image is animated and type is 'image/gif'. Note that a zero value (0) is possible if the video has not yet been generated
	Looping     bool   `json:"looping"`     // looping	boolean	OPTIONAL, Whether the image has a looping animation. Only available if the image is animated and type is 'image/gif'.
	NSFW        bool   `json:"nsfw"`        // nsfw	boolean	Indicates if the image has been marked as nsfw or not. Defaults to null if information is not available.
	Topic       string `json:"topic"`       // topic	string	Topic of the gallery image.
	Section     string `json:"section"`     // section	string	If the image has been categorized by our backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc)
	IsAlbum     bool   `json:"is_album"`    // is_album	boolean	If it's an album or not
	// ** Uninplemented fields **
	// bandwidth	integer	Bandwidth consumed by the image in bytes
	// deletehash	string	OPTIONAL, the deletehash, if you're logged in as the image owner
	// comment_count	int	Number of comments on the gallery image.
	// topic_id	integer	Topic ID of the gallery image.
	// vote	string	The current user's vote on the album. null if not signed in or if the user hasn't voted on it.
	// favorite	boolean	Indicates if the current user favorited the image. Defaults to false if not signed in.
	// account_url	string	The username of the account that uploaded it, or null.
	// account_id	integer	The account ID of the account that uploaded it, or null.
	// ups	integer	Upvotes for the image
	// downs	integer	Number of downvotes for the image
	// points	integer	Upvotes minus downvotes
	// score	integer	Imgur popularity score
}

type imgurGalleryAlbum struct {
	ID          string              `json:"id"`           // id	string	The ID for the album
	Title       string              `json:"title"`        // title	string	The title of the album.
	Description string              `json:"description"`  // description	string	Description of the album.
	DateTime    int64               `json:"datetime"`     // datetime	integer	Time inserted into the gallery, epoch time
	Views       int64               `json:"views"`        // views	integer	The number of album views
	Link        string              `json:"link"`         // link	string	The URL link to the album
	NSFW        bool                `json:"nsfw"`         // nsfw	boolean	Indicates if the album has been marked as nsfw or not. Defaults to null if information is not available.
	Topic       string              `json:"topic"`        // topic	string	Topic of the gallery album.
	IsAlbum     bool                `json:"is_album"`     // is_album	boolean	If it's an album or not
	Cover       string              `json:"cover"`        // cover	string	The ID of the album cover image
	CoverWidth  int                 `json:"cover_width"`  // cover_width	integer	The width, in pixels, of the album cover image
	CoverHeight int                 `json:"cover_height"` // cover_height	integer	The height, in pixels, of the album cover image
	ImagesCount int                 `json:"images_count"` // images_count	integer	The total number of images in the album
	Images      []imgurGalleryImage `json:"images"`       // images	Array of Images	An array of all the images in the album (only available when requesting the direct album)

	// ** Unimplemented fields **
	// account_url	string	The account username or null if it's anonymous.
	// account_id	integer	The account ID of the account that uploaded it, or null.
	// privacy	string	The privacy level of the album, you can only view public if not logged in as album owner
	// layout	string	The view layout of the album.
	// views	integer	The number of image views
	// ups	integer	Upvotes for the image
	// downs	integer	Number of downvotes for the image
	// points	integer	Upvotes minus downvotes
	// score	integer	Imgur popularity score
	// vote	string	The current user's vote on the album. null if not signed in or if the user hasn't voted on it.
	// favorite	boolean	Indicates if the current user favorited the album. Defaults to false if not signed in.
	// comment_count	int	Number of comments on the gallery album.
	// topic_id	integer	Topic ID of the gallery album.
}

type imgurSearchResponse struct {
	Data    []json.RawMessage `json:"data"`
	Success bool              `json:"success"` // Request completed successfully
	Status  int               `json:"status"`  // HTTP response code
}

// Service contains the Config fields for the Imgur service.
//
// Example request:
//   {
//			"client_id": "AIzaSyA4FD39..."
//			"client_secret": "ASdsaijwdfASD..."
//   }
type Service struct {
	types.DefaultService
	// The Imgur client ID
	ClientID string `json:"client_id"`
	// The API key to use when making HTTP requests to Imgur.
	ClientSecret string `json:"client_secret"`
}

// Commands supported:
//    !imgur some_search_query_without_quotes
// Responds with a suitable image into the same room as the command.
func (s *Service) Commands(client *gomatrix.Client) []types.Command {
	return []types.Command{
		types.Command{
			Path: []string{"imgur"},
			Command: func(roomID, userID string, args []string) (interface{}, error) {
				return s.cmdImgurImgSearch(client, roomID, userID, args)
			},
		},
		types.Command{
			Path: []string{"imgur", "help"},
			Command: func(roomID, userID string, args []string) (interface{}, error) {
				return usageMessage(), nil
			},
		},
	}
}

// usageMessage returns a matrix TextMessage representation of the service usage
func usageMessage() *gomatrix.TextMessage {
	return &gomatrix.TextMessage{"m.notice",
		`Usage: !imgur image_search_text`}
}

func (s *Service) cmdImgurImgSearch(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) {

	if len(args) < 1 {
		return usageMessage(), nil
	}

	// Get the query text to search for.
	querySentence := strings.Join(args, " ")

	searchResultImage, searchResultAlbum, err := s.text2imgImgur(querySentence)

	if err != nil {
		return nil, err
	}

	// Image returned
	if searchResultImage != nil {
		var imgURL = searchResultImage.Link
		if imgURL == "" {
			return gomatrix.TextMessage{
				MsgType: "m.notice",
				Body:    "No image found!",
			}, nil
		}

		// FIXME -- Sometimes upload fails with a cryptic error - "msg=Upload request failed code=400"
		// log.Printf("Uploading image at: %s", imgURL)
		resUpload, err := client.UploadLink(imgURL)
		if err != nil {
			return nil, fmt.Errorf("Failed to upload Imgur image (%s) to matrix: %s", imgURL, err.Error())
		}

		return gomatrix.ImageMessage{
			MsgType: "m.image",
			Body:    querySentence,
			URL:     resUpload.ContentURI,
			Info: gomatrix.ImageInfo{
				Height:   uint(searchResultImage.Height),
				Width:    uint(searchResultImage.Width),
				Mimetype: searchResultImage.Type,
			},
		}, nil
	} else if searchResultAlbum != nil {
		return gomatrix.TextMessage{
			MsgType: "m.notice",
			Body:    "Search returned an album - Not currently supported",
		}, nil
	} else {
		return gomatrix.TextMessage{
			MsgType: "m.notice",
			Body:    "No image found!",
		}, nil
	}
}

// text2imgImgur returns info about an image or an album
func (s *Service) text2imgImgur(query string) (*imgurGalleryImage, *imgurGalleryAlbum, error) {
	log.Info("Searching Imgur for an image of a ", query)

	query = url.QueryEscape(query)
	var base = "https://api.imgur.com/3/gallery/search"
	var sort = "time"  // time | viral | top
	var window = "all" // day | week | month | year | all
	var page = 1
	var urlString = fmt.Sprintf("%s/%s/%s/%d?q=%s", base, sort, window, page, query)
	// var urlString = fmt.Sprintf("%s?q=%s", base, query)

	u, err := url.Parse(urlString)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Add("Authorization", "Client-ID "+s.ClientID)
	res, err := httpClient.Do(req)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return nil, nil, err
	}
	if res.StatusCode > 200 {
		return nil, nil, fmt.Errorf("Request error: %d, %s", res.StatusCode, response2String(res))
	}

	var searchResults imgurSearchResponse
	if err := json.NewDecoder(res.Body).Decode(&searchResults); err != nil || searchResults.Data == nil {
		return nil, nil, fmt.Errorf("No images found - %s", err.Error())
	}

	log.Printf("%d results were returned from Imgur", len(searchResults.Data))
	for i := 0; i < len(searchResults.Data); i++ {
		// Return the first image result
		var image imgurGalleryImage
		if err := json.Unmarshal(searchResults.Data[i], &image); err == nil && !image.IsAlbum {
			return &image, nil, nil
		}
	}

	// Return an album
	// if dataInt["is_album"].(bool) {
	// 	var album imgurGalleryAlbum
	// 	if err := json.Unmarshal(searchResults.Data, &album); err != nil {
	// 		return nil, nil, fmt.Errorf("Failed to parse album data - %s", err.Error())
	// 	}
	//
	// 	return nil, &album, nil
	// }

	return nil, nil, fmt.Errorf("No images found")
}

// response2String returns a string representation of an HTTP response body
func response2String(res *http.Response) string {
	bs, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "Failed to decode response body"
	}
	str := string(bs)
	return str
}

// Initialise the service
func init() {
	types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
		return &Service{
			DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
		}
	})
}
<|endoftext|>


<commit_before>package fscache

import (
	"fmt"
	"path/filepath"
	"regexp"
	"time"
)

// An arbitrary object that can be stringified by fmt.Sprint().
//
// The stringification is filtered to ensure it doesn't contain characters
// that are invalid on Windows, which has the most restrictive filesystem.
// The "bad" characters (\, /, :, *, ?, ", <, >, |) are replaced with _.
//
// On a list of CacheKeys, the last component is taken to represent a file
// and all the other components represent the intermediary directories.
// This means that it's not possible to have subkeys of an existing file key.
//
// NOTE: when running on Windows, directories that start with a '.' get the
// '.' replaced by a '_'. This is because regular Windows tools can't deal
// with directories starting with a dot.
type CacheKey interface{}

// All "bad characters" that can't go in Windows paths.
// It's a superset of the "bad characters" on other OSes, so this works.
var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)

func stringify(stuff ...CacheKey) []string {
	ret := make([]string, len(stuff))
	for i := range stuff {
		s := fmt.Sprint(stuff[i])
		ret[i] = badPath.ReplaceAllLiteralString(s, "_")
	}
	return ret
}

// Each key but the last is treated as a directory.
// The last key is treated as a regular file.
//
// This also means that cache keys that are file-backed
// cannot have subkeys.
func (cd *CacheDir) cachePath(key ...CacheKey) string {
	parts := append([]string{cd.GetCacheDir()}, stringify(key...)...)
	p := filepath.Join(filterDots(parts...)...)
	return p
}

var invalidPath = []CacheKey{".invalid"}

// Returns the time the given key was marked as invalid.
// If the key is valid, then calling IsZero() on the returned
// time will return true.
func (cd *CacheDir) GetInvalid(key ...CacheKey) (ts time.Time) {
	invKey := append(invalidPath, key...)

	stat, _ := cd.Stat(invKey...)
	return stat.ModTime()
}

// Checks if the given key is not marked as invalid, or if it is,
// checks if it was marked more than maxDuration time ago.
//
// Calls UnsetInvalid if the keys are valid.
func (cd *CacheDir) IsValid(maxDuration time.Duration, key ...CacheKey) bool {
	ts := cd.GetInvalid(key...)

	switch {
	case ts.IsZero():
		return true
	case time.Now().Sub(ts) > maxDuration:
		cd.UnsetInvalid(key...)
		return true
	default:
		return false
	}
}

// Deletes the given key and caches it as invalid.
func (cd *CacheDir) SetInvalid(key ...CacheKey) error {
	invKey := append(invalidPath, key...)

	cd.Delete(key...)
	return cd.Touch(invKey...)
}

// Removes the given key from the invalid key cache.
func (cd *CacheDir) UnsetInvalid(key ...CacheKey) error {
	invKey := append(invalidPath, key...)

	return cd.Delete(invKey...)
}
<commit_msg>Fix nil access on GetInvalid<commit_after>package fscache

import (
	"fmt"
	"path/filepath"
	"regexp"
	"time"
)

// An arbitrary object that can be stringified by fmt.Sprint().
//
// The stringification is filtered to ensure it doesn't contain characters
// that are invalid on Windows, which has the most restrictive filesystem.
// The "bad" characters (\, /, :, *, ?, ", <, >, |) are replaced with _.
//
// On a list of CacheKeys, the last component is taken to represent a file
// and all the other components represent the intermediary directories.
// This means that it's not possible to have subkeys of an existing file key.
//
// NOTE: when running on Windows, directories that start with a '.' get the
// '.' replaced by a '_'. This is because regular Windows tools can't deal
// with directories starting with a dot.
type CacheKey interface{}

// All "bad characters" that can't go in Windows paths.
// It's a superset of the "bad characters" on other OSes, so this works.
var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)

func stringify(stuff ...CacheKey) []string {
	ret := make([]string, len(stuff))
	for i := range stuff {
		s := fmt.Sprint(stuff[i])
		ret[i] = badPath.ReplaceAllLiteralString(s, "_")
	}
	return ret
}

// Each key but the last is treated as a directory.
// The last key is treated as a regular file.
//
// This also means that cache keys that are file-backed
// cannot have subkeys.
func (cd *CacheDir) cachePath(key ...CacheKey) string {
	parts := append([]string{cd.GetCacheDir()}, stringify(key...)...)
	p := filepath.Join(filterDots(parts...)...)
	return p
}

var invalidPath = []CacheKey{".invalid"}

// Returns the time the given key was marked as invalid.
// If the key is valid, then calling IsZero() on the returned
// time will return true.
func (cd *CacheDir) GetInvalid(key ...CacheKey) (ts time.Time) {
	invKey := append(invalidPath, key...)

	if stat, err := cd.Stat(invKey...); err == nil {
		ts = stat.ModTime()
	}
	return
}

// Checks if the given key is not marked as invalid, or if it is,
// checks if it was marked more than maxDuration time ago.
//
// Calls UnsetInvalid if the keys are valid.
func (cd *CacheDir) IsValid(maxDuration time.Duration, key ...CacheKey) bool {
	ts := cd.GetInvalid(key...)

	switch {
	case ts.IsZero():
		return true
	case time.Now().Sub(ts) > maxDuration:
		cd.UnsetInvalid(key...)
		return true
	default:
		return false
	}
}

// Deletes the given key and caches it as invalid.
func (cd *CacheDir) SetInvalid(key ...CacheKey) error {
	invKey := append(invalidPath, key...)

	cd.Delete(key...)
	return cd.Touch(invKey...)
}

// Removes the given key from the invalid key cache.
func (cd *CacheDir) UnsetInvalid(key ...CacheKey) error {
	invKey := append(invalidPath, key...)

	return cd.Delete(invKey...)
}
<|endoftext|>