<commit_before>/*
Copyright 2018 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 v1alpha1 contains definitions of Vertical Pod Autoscaler related objects.
package v1alpha1

import (
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// VerticalPodAutoscaler is the configuration for a vertical pod
// autoscaler, which automatically manages pod resources based on historical and
// real time resource utilization.
type VerticalPodAutoscaler struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the behavior of the autoscaler.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.
	// +optional
	Spec VerticalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Current information about the autoscaler.
	// +optional
	Status VerticalPodAutoscalerStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// VerticalPodAutoscalerList is a list of VerticalPodAutoscaler objects.
type VerticalPodAutoscalerList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []VerticalPodAutoscaler `json:"items"`
}

// VerticalPodAutoscalerSpec is the specification of the behavior of the autoscaler.
type VerticalPodAutoscalerSpec struct {
	// A label query that determines the set of pods controlled by the Autoscaler.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"`

	// Describes the rules on how changes are applied to the pods.
	// +optional
	UpdatePolicy PodUpdatePolicy `json:"updatePolicy,omitempty" protobuf:"bytes,2,opt,name=updatePolicy"`

	// Controls how the autoscaler computes recommended resources.
	// +optional
	ResourcePolicy PodResourcePolicy `json:"podResourcePolicy,omitempty" protobuf:"bytes,3,opt,name=podResourcePolicy"`
}

// VerticalPodAutoscalerStatus describes the runtime state of the autoscaler.
type VerticalPodAutoscalerStatus struct {
	// The time when the status was last refreshed.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,1,opt,name=lastUpdateTime"`
	// The most recently computed amount of resources recommended by the
	// autoscaler for the controlled pods.
	// +optional
	Recommendation RecommendedPodResources `json:"recommendation,omitempty" protobuf:"bytes,2,opt,name=recommendation"`

	// Conditions is the set of conditions required for this autoscaler to scale its target,
	// and indicates whether or not those conditions are met.
	Conditions []VerticalPodAutoscalerCondition `json:"conditions" protobuf:"bytes,3,rep,name=conditions"`
}

// VerticalPodAutoscalerConditionType are the valid conditions of
// a VerticalPodAutoscaler.
type VerticalPodAutoscalerConditionType string

var (
	// RecommendationProvided indicates whether the VPA recommender was able to calculate a recommendation.
	RecommendationProvided VerticalPodAutoscalerConditionType = "RecommendationProvided"
)

// VerticalPodAutoscalerCondition describes the state of
// a VerticalPodAutoscaler at a certain point.
type VerticalPodAutoscalerCondition struct {
	// type describes the current condition
	Type VerticalPodAutoscalerConditionType `json:"type" protobuf:"bytes,1,name=type"`
	// status is the status of the condition (True, False, Unknown)
	Status apiv1.ConditionStatus `json:"status" protobuf:"bytes,2,name=status"`
	// lastTransitionTime is the last time the condition transitioned from
	// one status to another
	// +optional
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
	// reason is the reason for the condition's last transition.
	// +optional
	Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
	// message is a human-readable explanation containing details about
	// the transition
	// +optional
	Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
}

// UpdateMode controls when autoscaler applies changes to the pod resoures.
type UpdateMode string

const (
	// UpdateModeOff means that autoscaler never changes Pod resources.
	// The recommender still sets the recommended resources in the
	// VerticalPodAutoscaler object. This can be used for a "dry run".
	UpdateModeOff UpdateMode = "Off"
	// UpdateModeInitial means that autoscaler only assigns resources on pod
	// creation and does not change them during the lifetime of the pod.
	UpdateModeInitial UpdateMode = "Initial"
	// UpdateModeAuto means that autoscaler assigns resources on pod creation
	// and additionally can update them during the lifetime of the pod,
	// including evicting / rescheduling the pod.
	UpdateModeAuto UpdateMode = "Auto"
)

// PodUpdatePolicy describes the rules on how changes are applied to the pods.
type PodUpdatePolicy struct {
	// Controls when autoscaler applies changes to the pod resoures.
	// +optional
	UpdateMode UpdateMode `json:"updateMode,omitempty" protobuf:"bytes,1,opt,name=updateMode"`
}

const (
	// DefaultContainerResourcePolicy can be passed as
	// ContainerResourcePolicy.Name to specify the default policy.
	DefaultContainerResourcePolicy = "*"
)

// ContainerResourcePolicy controls how autoscaler computes the recommended
// resources for a specific container.
type ContainerResourcePolicy struct {
	// Name of the container or DefaultContainerResourcePolicy, in which
	// case the policy is used by the containers that don't have their own
	// policy specified.
	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
	// Whether autoscaler is enabled for the container. Defaults to "On".
	// +optional
	Mode ContainerScalingMode `json:"mode,omitempty" protobuf:"bytes,2,opt,name=mode"`
	// Specifies the minimal amount of resources that will be recommended
	// for the container.
	// +optional
	MinAllowed apiv1.ResourceList `json:"minAllowed,omitempty" protobuf:"bytes,3,rep,name=minAllowed,casttype=ResourceList,castkey=ResourceName"`
	// Specifies the maximum amount of resources that will be recommended
	// for the container.
	// +optional
	MaxAllowed apiv1.ResourceList `json:"maxAllowed,omitempty" protobuf:"bytes,4,rep,name=maxAllowed,casttype=ResourceList,castkey=ResourceName"`
}

// PodResourcePolicy controls how autoscaler computes the recommended resources
// for containers belonging to the pod.
type PodResourcePolicy struct {
	// Per-container resource policies.
	ContainerPolicies []ContainerResourcePolicy `json:"containerPolicies" protobuf:"bytes,1,rep,name=containerPolicies"`
}

// ContainerScalingMode controls whether autoscaler is enabled for a specific
// container.
type ContainerScalingMode string

const (
	// ContainerScalingModeOn means autoscaling is enabled for a container.
	ContainerScalingModeOn ContainerScalingMode = "On"
	// ContainerScalingModeOff means autoscaling is disabled for a container.
	ContainerScalingModeOff ContainerScalingMode = "Off"
)

// RecommendedPodResources is the recommendation of resources computed by
// autoscaler.
type RecommendedPodResources struct {
	// Resources recommended by the autoscaler for each container.
	ContainerRecommendations []RecommendedContainerResources `json:"containerRecommendations" protobuf:"bytes,1,rep,name=containerRecommendations"`
}

// RecommendedContainerResources is the recommendation of resources computed by
// autoscaler for a specific container. Respects the container resource policy
// if present in the spec.
type RecommendedContainerResources struct {
	// Name of the container.
	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
	// Recommended amount of resources.
	Target apiv1.ResourceList `json:"target,omitempty" protobuf:"bytes,2,rep,name=target,casttype=ResourceList,castkey=ResourceName"`
	// Minimum recommended amount of resources.
	// Running the application with less resources is likely to have
	// significant impact on performance/availability.
	// +optional
	MinRecommended apiv1.ResourceList `json:"minRecommended,omitempty" protobuf:"bytes,3,rep,name=minRecommended,casttype=ResourceList,castkey=ResourceName"`
	// Maximum recommended amount of resources.
	// Any resources allocated beyond this value are likely wasted.
	// +optional
	MaxRecommended apiv1.ResourceList `json:"minRecommended,omitempty" protobuf:"bytes,3,rep,name=maxRecommended,casttype=ResourceList,castkey=ResourceName"`
}
<commit_msg>Fix typo json:minRecommended for MaxRecommended field.<commit_after>/*
Copyright 2018 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 v1alpha1 contains definitions of Vertical Pod Autoscaler related objects.
package v1alpha1

import (
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// VerticalPodAutoscaler is the configuration for a vertical pod
// autoscaler, which automatically manages pod resources based on historical and
// real time resource utilization.
type VerticalPodAutoscaler struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the behavior of the autoscaler.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.
	// +optional
	Spec VerticalPodAutoscalerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Current information about the autoscaler.
	// +optional
	Status VerticalPodAutoscalerStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// VerticalPodAutoscalerList is a list of VerticalPodAutoscaler objects.
type VerticalPodAutoscalerList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []VerticalPodAutoscaler `json:"items"`
}

// VerticalPodAutoscalerSpec is the specification of the behavior of the autoscaler.
type VerticalPodAutoscalerSpec struct {
	// A label query that determines the set of pods controlled by the Autoscaler.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"`

	// Describes the rules on how changes are applied to the pods.
	// +optional
	UpdatePolicy PodUpdatePolicy `json:"updatePolicy,omitempty" protobuf:"bytes,2,opt,name=updatePolicy"`

	// Controls how the autoscaler computes recommended resources.
	// +optional
	ResourcePolicy PodResourcePolicy `json:"podResourcePolicy,omitempty" protobuf:"bytes,3,opt,name=podResourcePolicy"`
}

// VerticalPodAutoscalerStatus describes the runtime state of the autoscaler.
type VerticalPodAutoscalerStatus struct {
	// The time when the status was last refreshed.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,1,opt,name=lastUpdateTime"`
	// The most recently computed amount of resources recommended by the
	// autoscaler for the controlled pods.
	// +optional
	Recommendation RecommendedPodResources `json:"recommendation,omitempty" protobuf:"bytes,2,opt,name=recommendation"`

	// Conditions is the set of conditions required for this autoscaler to scale its target,
	// and indicates whether or not those conditions are met.
	Conditions []VerticalPodAutoscalerCondition `json:"conditions" protobuf:"bytes,3,rep,name=conditions"`
}

// VerticalPodAutoscalerConditionType are the valid conditions of
// a VerticalPodAutoscaler.
type VerticalPodAutoscalerConditionType string

var (
	// RecommendationProvided indicates whether the VPA recommender was able to calculate a recommendation.
	RecommendationProvided VerticalPodAutoscalerConditionType = "RecommendationProvided"
)

// VerticalPodAutoscalerCondition describes the state of
// a VerticalPodAutoscaler at a certain point.
type VerticalPodAutoscalerCondition struct {
	// type describes the current condition
	Type VerticalPodAutoscalerConditionType `json:"type" protobuf:"bytes,1,name=type"`
	// status is the status of the condition (True, False, Unknown)
	Status apiv1.ConditionStatus `json:"status" protobuf:"bytes,2,name=status"`
	// lastTransitionTime is the last time the condition transitioned from
	// one status to another
	// +optional
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
	// reason is the reason for the condition's last transition.
	// +optional
	Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
	// message is a human-readable explanation containing details about
	// the transition
	// +optional
	Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
}

// UpdateMode controls when autoscaler applies changes to the pod resoures.
type UpdateMode string

const (
	// UpdateModeOff means that autoscaler never changes Pod resources.
	// The recommender still sets the recommended resources in the
	// VerticalPodAutoscaler object. This can be used for a "dry run".
	UpdateModeOff UpdateMode = "Off"
	// UpdateModeInitial means that autoscaler only assigns resources on pod
	// creation and does not change them during the lifetime of the pod.
	UpdateModeInitial UpdateMode = "Initial"
	// UpdateModeAuto means that autoscaler assigns resources on pod creation
	// and additionally can update them during the lifetime of the pod,
	// including evicting / rescheduling the pod.
	UpdateModeAuto UpdateMode = "Auto"
)

// PodUpdatePolicy describes the rules on how changes are applied to the pods.
type PodUpdatePolicy struct {
	// Controls when autoscaler applies changes to the pod resoures.
	// +optional
	UpdateMode UpdateMode `json:"updateMode,omitempty" protobuf:"bytes,1,opt,name=updateMode"`
}

const (
	// DefaultContainerResourcePolicy can be passed as
	// ContainerResourcePolicy.Name to specify the default policy.
	DefaultContainerResourcePolicy = "*"
)

// ContainerResourcePolicy controls how autoscaler computes the recommended
// resources for a specific container.
type ContainerResourcePolicy struct {
	// Name of the container or DefaultContainerResourcePolicy, in which
	// case the policy is used by the containers that don't have their own
	// policy specified.
	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
	// Whether autoscaler is enabled for the container. Defaults to "On".
	// +optional
	Mode ContainerScalingMode `json:"mode,omitempty" protobuf:"bytes,2,opt,name=mode"`
	// Specifies the minimal amount of resources that will be recommended
	// for the container.
	// +optional
	MinAllowed apiv1.ResourceList `json:"minAllowed,omitempty" protobuf:"bytes,3,rep,name=minAllowed,casttype=ResourceList,castkey=ResourceName"`
	// Specifies the maximum amount of resources that will be recommended
	// for the container.
	// +optional
	MaxAllowed apiv1.ResourceList `json:"maxAllowed,omitempty" protobuf:"bytes,4,rep,name=maxAllowed,casttype=ResourceList,castkey=ResourceName"`
}

// PodResourcePolicy controls how autoscaler computes the recommended resources
// for containers belonging to the pod.
type PodResourcePolicy struct {
	// Per-container resource policies.
	ContainerPolicies []ContainerResourcePolicy `json:"containerPolicies" protobuf:"bytes,1,rep,name=containerPolicies"`
}

// ContainerScalingMode controls whether autoscaler is enabled for a specific
// container.
type ContainerScalingMode string

const (
	// ContainerScalingModeOn means autoscaling is enabled for a container.
	ContainerScalingModeOn ContainerScalingMode = "On"
	// ContainerScalingModeOff means autoscaling is disabled for a container.
	ContainerScalingModeOff ContainerScalingMode = "Off"
)

// RecommendedPodResources is the recommendation of resources computed by
// autoscaler.
type RecommendedPodResources struct {
	// Resources recommended by the autoscaler for each container.
	ContainerRecommendations []RecommendedContainerResources `json:"containerRecommendations" protobuf:"bytes,1,rep,name=containerRecommendations"`
}

// RecommendedContainerResources is the recommendation of resources computed by
// autoscaler for a specific container. Respects the container resource policy
// if present in the spec.
type RecommendedContainerResources struct {
	// Name of the container.
	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
	// Recommended amount of resources.
	Target apiv1.ResourceList `json:"target,omitempty" protobuf:"bytes,2,rep,name=target,casttype=ResourceList,castkey=ResourceName"`
	// Minimum recommended amount of resources.
	// Running the application with less resources is likely to have
	// significant impact on performance/availability.
	// +optional
	MinRecommended apiv1.ResourceList `json:"minRecommended,omitempty" protobuf:"bytes,3,rep,name=minRecommended,casttype=ResourceList,castkey=ResourceName"`
	// Maximum recommended amount of resources.
	// Any resources allocated beyond this value are likely wasted.
	// +optional
	MaxRecommended apiv1.ResourceList `json:"maxRecommended,omitempty" protobuf:"bytes,3,rep,name=maxRecommended,casttype=ResourceList,castkey=ResourceName"`
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/urfave/cli"
)

func ApCMD(ctx *cli.Context) error {
	if ctx.IsSet(enableFlag) {
		return EnableApCMD(ctx)
	}
	if ctx.IsSet(disableFlag) {
		return DisableApCMD(ctx)
	}
	if ctx.IsSet(removeFlag) {
		return RemoveApCMD(ctx)
	}
	if ctx.IsSet(configFlag) {
		return ConfigApCMD(ctx)
	}
	return nil
}

func ConfigApCMD(ctx *cli.Context) error {
	base := ctx.String("dir")
	name := ctx.String("name")
	src := ctx.String("config")
	if src == "" {
		return errors.New("fconf: missing argument")
	}
	b, err := ioutil.ReadFile(src)
	if err != nil {
		return err
	}
	e := &AccessPointConfig{}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	err = checkDir(base)
	if err != nil {
		return err
	}
	ap := DefaultAccesPoint()
	ap.Update(e)
	filename := filepath.Join(base, name)
	var buf bytes.Buffer
	_, err = ap.WriteTo(&buf)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, buf.Bytes(), 0644)
	if err != nil {
		return err
	}
	fmt.Printf("successful written access point configuration to %s \n", filename)
	data, err := json.Marshal(ap.State())
	if err != nil {
		return err
	}

	return keepState(defaultAccessPointConfig, data)
}

func accessPointState() (*AccessPointConfig, error) {
	dir := os.Getenv("FCONF_CONFIGDIR")
	if dir == "" {
		dir = fconfConfigDir
	}
	b, err := ioutil.ReadFile(filepath.Join(dir, defaultAccessPointConfig))
	if err != nil {
		return nil, err
	}
	a := &AccessPointConfig{}
	err = json.Unmarshal(b, a)
	if err != nil {
		return nil, err
	}
	return a, nil
}

func EnableApCMD(ctx *cli.Context) error {
	if ctx.IsSet(configFlag) {
		err := ConfigApCMD(ctx)
		if err != nil {
			return err
		}
	}
	state, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = startService(service)
	if err != nil {
		return err
	}
	err = enableService(service)
	if err != nil {
		return err
	}
	state.Enabled = true
	data, err := json.Marshal(state)
	if err != nil {
		return err
	}
	return keepState(defaultAccessPointConfig, data)
}

func DisableApCMD(ctx *cli.Context) error {
	state, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = stopService(service)
	if err != nil {
		return err
	}
	err = disableService(service)
	if err != nil {
		return err
	}
	data, err := json.Marshal(state)
	if err != nil {
		return err
	}
	return keepState(defaultAccessPointConfig, data)
}
func RemoveApCMD(ctx *cli.Context) error {
	_, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = stopService(service)
	if err != nil {
		return err
	}
	err = disableService(service)
	if err != nil {
		return err
	}

	// remove the state file
	stateFile := filepath.Join(stateDir(), defaultAccessPointConfig)
	return removeFile(stateFile)
}
<commit_msg>Set Disabled to false on access point diabling comand<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/urfave/cli"
)

func ApCMD(ctx *cli.Context) error {
	if ctx.IsSet(enableFlag) {
		return EnableApCMD(ctx)
	}
	if ctx.IsSet(disableFlag) {
		return DisableApCMD(ctx)
	}
	if ctx.IsSet(removeFlag) {
		return RemoveApCMD(ctx)
	}
	if ctx.IsSet(configFlag) {
		return ConfigApCMD(ctx)
	}
	return nil
}

func ConfigApCMD(ctx *cli.Context) error {
	base := ctx.String("dir")
	name := ctx.String("name")
	src := ctx.String("config")
	if src == "" {
		return errors.New("fconf: missing argument")
	}
	b, err := ioutil.ReadFile(src)
	if err != nil {
		return err
	}
	e := &AccessPointConfig{}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	err = checkDir(base)
	if err != nil {
		return err
	}
	ap := DefaultAccesPoint()
	ap.Update(e)
	filename := filepath.Join(base, name)
	var buf bytes.Buffer
	_, err = ap.WriteTo(&buf)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, buf.Bytes(), 0644)
	if err != nil {
		return err
	}
	fmt.Printf("successful written access point configuration to %s \n", filename)
	data, err := json.Marshal(ap.State())
	if err != nil {
		return err
	}

	return keepState(defaultAccessPointConfig, data)
}

func accessPointState() (*AccessPointConfig, error) {
	dir := os.Getenv("FCONF_CONFIGDIR")
	if dir == "" {
		dir = fconfConfigDir
	}
	b, err := ioutil.ReadFile(filepath.Join(dir, defaultAccessPointConfig))
	if err != nil {
		return nil, err
	}
	a := &AccessPointConfig{}
	err = json.Unmarshal(b, a)
	if err != nil {
		return nil, err
	}
	return a, nil
}

func EnableApCMD(ctx *cli.Context) error {
	if ctx.IsSet(configFlag) {
		err := ConfigApCMD(ctx)
		if err != nil {
			return err
		}
	}
	state, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = startService(service)
	if err != nil {
		return err
	}
	err = enableService(service)
	if err != nil {
		return err
	}
	state.Enabled = true
	data, err := json.Marshal(state)
	if err != nil {
		return err
	}
	return keepState(defaultAccessPointConfig, data)
}

func DisableApCMD(ctx *cli.Context) error {
	state, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = stopService(service)
	if err != nil {
		return err
	}
	err = disableService(service)
	if err != nil {
		return err
	}
	state.Enabled = false
	data, err := json.Marshal(state)
	if err != nil {
		return err
	}
	return keepState(defaultAccessPointConfig, data)
}
func RemoveApCMD(ctx *cli.Context) error {
	_, err := accessPointState()
	if err != nil {
		return err
	}
	service := "create_ap"
	err = stopService(service)
	if err != nil {
		return err
	}
	err = disableService(service)
	if err != nil {
		return err
	}

	// remove the state file
	stateFile := filepath.Join(stateDir(), defaultAccessPointConfig)
	return removeFile(stateFile)
}
<|endoftext|>


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

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

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

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

package io

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

	"github.com/golang/glog"
)

// Writer is an interface which allows to write data to a file.
type Writer interface {
	WriteFile(filename string, data []byte, perm os.FileMode) error
}

// StdWriter implements Writer interface and uses standard libraries
// for writing data to files.
type StdWriter struct {
}

func (writer *StdWriter) WriteFile(filename string, data []byte, perm os.FileMode) error {
	return ioutil.WriteFile(filename, data, perm)
}

// Alternative implementation of Writer interface that allows writing data to file
// using nsenter command.
// If a program (e.g. kubelet) runs in a container it may want to write data to
// a mounted device. Since in Docker, mount propagation mode is set to private,
// it will not see the mounted device in its own namespace. To work around this
// limitaion one has to first enter hosts namespace (by using 'nsenter') and only
// then write data.
type NsenterWriter struct {
}

func (writer *NsenterWriter) WriteFile(filename string, data []byte, perm os.FileMode) error {
	cmd := "nsenter"
	base_args := []string{
		"--mount=/rootfs/proc/1/ns/mnt",
		"--",
	}

	echo_args := append(base_args, "sh", "-c",
		fmt.Sprintf("echo %q | cat > %s", data, filename))
	glog.V(5).Infof("Command to write data to file: %v %v", cmd, echo_args)
	outputBytes, err := exec.Command(cmd, echo_args...).CombinedOutput()
	if err != nil {
		glog.Errorf("Output from writing to %q: %v", filename, string(outputBytes))
		return err
	}

	chmod_args := append(base_args, "chmod", fmt.Sprintf("%o", perm), filename)
	glog.V(5).Infof("Command to change permissions to file: %v %v", cmd, chmod_args)
	outputBytes, err = exec.Command(cmd, chmod_args...).CombinedOutput()
	if err != nil {
		glog.Errorf("Output from chmod command: %v", string(outputBytes))
		return err
	}

	return nil
}
<commit_msg>UPSTREAM: 16969: nsenter file writer mangles newlines<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package io

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

	"github.com/golang/glog"
)

// Writer is an interface which allows to write data to a file.
type Writer interface {
	WriteFile(filename string, data []byte, perm os.FileMode) error
}

// StdWriter implements Writer interface and uses standard libraries
// for writing data to files.
type StdWriter struct {
}

func (writer *StdWriter) WriteFile(filename string, data []byte, perm os.FileMode) error {
	return ioutil.WriteFile(filename, data, perm)
}

// Alternative implementation of Writer interface that allows writing data to file
// using nsenter command.
// If a program (e.g. kubelet) runs in a container it may want to write data to
// a mounted device. Since in Docker, mount propagation mode is set to private,
// it will not see the mounted device in its own namespace. To work around this
// limitaion one has to first enter hosts namespace (by using 'nsenter') and only
// then write data.
type NsenterWriter struct {
}

// TODO: should take a writer, not []byte
func (writer *NsenterWriter) WriteFile(filename string, data []byte, perm os.FileMode) error {
	cmd := "nsenter"
	base_args := []string{
		"--mount=/rootfs/proc/1/ns/mnt",
		"--",
	}

	echo_args := append(base_args, "sh", "-c", fmt.Sprintf("cat > %s", filename))
	glog.V(5).Infof("Command to write data to file: %v %v", cmd, echo_args)
	command := exec.Command(cmd, echo_args...)
	command.Stdin = bytes.NewBuffer(data)
	outputBytes, err := command.CombinedOutput()
	if err != nil {
		glog.Errorf("Output from writing to %q: %v", filename, string(outputBytes))
		return err
	}

	chmod_args := append(base_args, "chmod", fmt.Sprintf("%o", perm), filename)
	glog.V(5).Infof("Command to change permissions to file: %v %v", cmd, chmod_args)
	outputBytes, err = exec.Command(cmd, chmod_args...).CombinedOutput()
	if err != nil {
		glog.Errorf("Output from chmod command: %v", string(outputBytes))
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package reports

import (
	"time"

	"github.com/ion-channel/ionic/aliases"
	"github.com/ion-channel/ionic/analysis"
	"github.com/ion-channel/ionic/projects"
	"github.com/ion-channel/ionic/tags"
)

// ProjectReport gives the details of a project including past analyses
type ProjectReport struct {
	*projects.Project
	RulesetName       string             `json:"ruleset_name"`
	AnalysisSummaries []analysis.Summary `json:"analysis_summaries"`
}

// ProjectReports is used for getting a high level overview, returning a single
// analysis
type ProjectReports struct {
	ID              string            `json:"id"`
	TeamID          string            `json:"team_id"`
	RulesetID       string            `json:"ruleset_id"`
	Name            string            `json:"name"`
	Type            string            `json:"type"`
	Source          string            `json:"source"`
	Branch          string            `json:"branch"`
	Description     string            `json:"description"`
	Active          bool              `json:"active"`
	ChatChannel     string            `json:"chat_channel"`
	CreatedAt       time.Time         `json:"created_at"`
	UpdatedAt       time.Time         `json:"updated_at"`
	DeployKey       string            `json:"deploy_key"`
	Monitor         bool              `json:"should_monitor"`
	PocName         string            `json:"poc_name"`
	PocEmail        string            `json:"poc_email"`
	Username        string            `json:"username"`
	Password        string            `json:"password"`
	KeyFingerprint  string            `json:"key_fingerprint"`
	Aliases         []aliases.Alias   `json:"aliases"`
	Tags            []tags.Tag        `json:"tags"`
	RulesetName     string            `json:"ruleset_name"`
	AnalysisSummary *analysis.Summary `json:"analysis_summary"`
}
<commit_msg>getting rid of duplicated fields<commit_after>package reports

import (
	"github.com/ion-channel/ionic/analysis"
	"github.com/ion-channel/ionic/projects"
)

// ProjectReport gives the details of a project including past analyses
type ProjectReport struct {
	*projects.Project
	RulesetName       string             `json:"ruleset_name"`
	AnalysisSummaries []analysis.Summary `json:"analysis_summaries"`
}

// ProjectReports is used for getting a high level overview, returning a single
// analysis
type ProjectReports struct {
	*projects.Project
	RulesetName     string            `json:"ruleset_name"`
	AnalysisSummary *analysis.Summary `json:"analysis_summary"`
}
<|endoftext|>


<commit_before>package request

import (
	"encoding/base64"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"github.com/caiyeon/goldfish/vault"
	"github.com/fatih/structs"
	"github.com/gorilla/securecookie"
	"github.com/hashicorp/go-uuid"
	"github.com/hashicorp/vault/helper/xor"
	"github.com/mitchellh/hashstructure"
	"github.com/mitchellh/mapstructure"
)

// operations on the same request should not interweave,
// a map will prevent this race condition
var lockMap sync.Mutex
var lockHash = make(map[string]bool)

// only one goroutine should perform vault root generation at a time
var lockRoot sync.Mutex

type Request interface {
	IsRootOnly() bool
	Verify(*vault.AuthInfo) error
	Approve(string, string) error
	Reject(*vault.AuthInfo, string) error
}

// adds a request if user has authentication
func Add(auth *vault.AuthInfo, raw map[string]interface{}) (string, error) {
	t := ""
	if typeRaw, ok := raw["Type"]; ok {
		t, ok = typeRaw.(string)
	}
	if t == "" {
		return "", errors.New("Type field is empty")
	}

	switch strings.ToLower(t) {
	case "policy":
		// construct request fields
		req, hash, err := CreatePolicyRequest(auth, raw)
		if err != nil {
			return "", err
		}

		// lock hash in map before writing to vault cubbyhole
		lockMap.Lock()
		defer lockMap.Unlock()
		if _, locked := lockHash[hash]; locked {
			return "", errors.New("Someone else is currently editing this request")
		}
		lockHash[hash] = true
		defer delete(lockHash, hash)

		_, err = vault.WriteToCubbyhole("requests/"+hash, structs.Map(req))
		return hash, err

	case "github":
		return "", errors.New("Github requests do not need to be added")

	default:
		return "", errors.New("Unsupported request type")
	}
}

// fetches a request if it exists, and if user has authentication
func Get(auth *vault.AuthInfo, t string, hash string) (Request, error) {
	// lock hash in map before reading from vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return nil, errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	switch strings.ToLower(t) {
	case "policy":
		var req PolicyRequest
		// fetch request from cubbyhole, if it exists
		resp, err := vault.ReadFromCubbyhole("requests/" + hash)
		if err != nil {
			return nil, err
		}
		if resp == nil {
			return nil, errors.New("Change ID not found")
		}
		// decode secret into policy request
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return nil, err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return nil, errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		if err := req.Verify(auth); err != nil {
			return nil, errors.New(err.Error() + ", thus, request has been deleted")
		}
		return &req, nil

	case "github":
		var req GithubRequest
		// fetch request from cubbyhole, if it exists
		resp, err := vault.ReadFromCubbyhole("requests/" + hash)
		if err != nil {
			return nil, err
		}
		if resp == nil {
			// a nil resp could mean this github req hasn't been approved yet
			return CreateGithubRequest(auth, map[string]interface{}{
				"commithash": hash,
			})
		}
		// decode secret into github request
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return nil, err
		}
		// verify user has vault privilege to read the contained policies
		if err := req.Verify(auth); err != nil {
			return nil, err
		}
		return &req, nil

	default:
		return nil, errors.New("Invalid request type: " + t)
	}
}

// if unseal is nonempty string, approve request with current auth
// otherwise, add unseal to list of unseals to generate root token later
func Approve(auth *vault.AuthInfo, hash string, unseal string) error {
	// lock hash in map before writing to vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	// fetch request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("requests/" + hash)
	if err != nil {
		return err
	}
	if resp == nil {
		return errors.New("Change ID not found")
	}

	// decode secret to a request
	t := ""
	if raw, ok := resp.Data["Type"]; ok {
		t, ok = raw.(string)
	}
	if t == "" {
		return errors.New("Invalid request type")
	}

	switch strings.ToLower(t) {
	case "policy":
		// decode secret into policy request
		var req PolicyRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Approve(hash, unseal)

	case "github":
		// decode secret into github request
		var req GithubRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify user has vault privleges to read contained policies
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Approve(hash, unseal)

	default:
		return errors.New("Invalid request type: " + t)
	}
}

// deletes request, if user is authorized to read resource
func Reject(auth *vault.AuthInfo, hash string) error {
	// lock hash in map before writing to vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	// fetch request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("requests/" + hash)
	if err != nil {
		return err
	}
	if resp == nil {
		return errors.New("Change ID not found")
	}

	// decode secret to a request
	t := ""
	if raw, ok := resp.Data["Type"]; ok {
		t, ok = raw.(string)
	}
	if t == "" {
		return errors.New("Invalid request type")
	}

	// verify user can access resource
	switch strings.ToLower(t) {
	case "policy":
		// decode secret into policy request
		var req PolicyRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		return req.Reject(auth, hash)

	case "github":
		// decode secret into github request
		var req GithubRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify user has vault privleges to read contained policies
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Reject(auth, hash)

	default:
		return errors.New("Invalid request type: " + t)
	}
}

func IsRootOnly(req Request) bool {
	return req.IsRootOnly()
}

// attempts to generate a root token via unseal keys
// will return error if another key generation process is underway
func generateRootToken(unsealKeys []string) (string, error) {
	lockRoot.Lock()
	defer lockRoot.Unlock()

	otp := base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(16))
	status, err := vault.GenerateRootInit(otp)
	if err != nil {
		return "", err
	}

	if status.EncodedRootToken == "" {
		for _, s := range unsealKeys {
			status, err = vault.GenerateRootUpdate(s, status.Nonce)
			// an error likely means one of the unseals was not valid
			if err != nil {
				errS := "Could not generate root token: " + err.Error()
				// try to cancel the root generation
				if err := vault.GenerateRootCancel(); err != nil {
					errS += ". Attempted to cancel root generation, but: " + err.Error()
				}
				return "", errors.New(errS)
			}
		}
	}

	if status.EncodedRootToken == "" {
		return "", errors.New("Could not generate root token. Was vault re-keyed just now?")
	}

	tokenBytes, err := xor.XORBase64(status.EncodedRootToken, otp)
	if err != nil {
		return "", errors.New("Could not decode root token. Please search and revoke")
	}

	token, err := uuid.FormatUUID(tokenBytes)
	if err != nil {
		return "", errors.New("Could not decode root token. Please search and revoke")
	}

	return token, nil
}

// writes the provided unseal in and returns a slice of all unseals in hash
func appendUnseal(hash, unseal string) ([]string, error) {
	// read current request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("unseal_wrapping_tokens/" + hash)
	if err != nil {
		return nil, err
	}

	var wrappingTokens []string

	// if there are already unseals, read them and append
	if resp != nil {
		raw := ""
		if temp, ok := resp.Data["wrapping_tokens"]; ok {
			raw, _ = temp.(string)
		}
		if raw == "" {
			return nil, errors.New("Could not find key 'wrapping_tokens' in cubbyhole")
		}
		wrappingTokens = append(wrappingTokens, strings.Split(raw, ";")...)
	}

	// wrap the unseal token
	newWrappingToken, err := vault.WrapData("60m", map[string]interface{}{
		"unseal_token": unseal,
	})
	if err != nil {
		return nil, err
	}

	// add the new unseal key in
	wrappingTokens = append(wrappingTokens, newWrappingToken)

	// write the unseals back to the cubbyhole
	_, err = vault.WriteToCubbyhole("unseal_wrapping_tokens/"+hash,
		map[string]interface{}{
			"wrapping_tokens": strings.Trim(strings.Join(strings.Fields(fmt.Sprint(wrappingTokens)), ";"), "[]"),
		},
	)
	return wrappingTokens, err
}

func unwrapUnseals(wrappingTokens []string) (unseals []string, err error) {
	for _, wrappingToken := range wrappingTokens {
		data, err := vault.UnwrapData(wrappingToken)
		if err != nil {
			return nil, err
		}
		if unseal, ok := data["unseal_token"]; ok {
			unseals = append(unseals, unseal.(string))
		} else {
			return nil, errors.New("One of the wrapping tokens timed out. Progress reset.")
		}
	}
	return
}
<commit_msg>Add fallback key to requests type<commit_after>package request

import (
	"encoding/base64"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"github.com/caiyeon/goldfish/vault"
	"github.com/fatih/structs"
	"github.com/gorilla/securecookie"
	"github.com/hashicorp/go-uuid"
	"github.com/hashicorp/vault/helper/xor"
	"github.com/mitchellh/hashstructure"
	"github.com/mitchellh/mapstructure"
)

// operations on the same request should not interweave,
// a map will prevent this race condition
var lockMap sync.Mutex
var lockHash = make(map[string]bool)

// only one goroutine should perform vault root generation at a time
var lockRoot sync.Mutex

type Request interface {
	IsRootOnly() bool
	Verify(*vault.AuthInfo) error
	Approve(string, string) error
	Reject(*vault.AuthInfo, string) error
}

// adds a request if user has authentication
func Add(auth *vault.AuthInfo, raw map[string]interface{}) (string, error) {
	t := ""
	if typeRaw, ok := raw["Type"]; !ok {
		if typeRaw, ok = raw["type"]; ok {
			t, _ = typeRaw.(string)
		}
	} else {
		t, _ = typeRaw.(string)
	}
	if t == "" {
		return "", errors.New("Type field is empty")
	}

	switch strings.ToLower(t) {
	case "policy":
		// construct request fields
		req, hash, err := CreatePolicyRequest(auth, raw)
		if err != nil {
			return "", err
		}

		// lock hash in map before writing to vault cubbyhole
		lockMap.Lock()
		defer lockMap.Unlock()
		if _, locked := lockHash[hash]; locked {
			return "", errors.New("Someone else is currently editing this request")
		}
		lockHash[hash] = true
		defer delete(lockHash, hash)

		_, err = vault.WriteToCubbyhole("requests/"+hash, structs.Map(req))
		return hash, err

	case "github":
		return "", errors.New("Github requests do not need to be added")

	default:
		return "", errors.New("Unsupported request type")
	}
}

// fetches a request if it exists, and if user has authentication
func Get(auth *vault.AuthInfo, t string, hash string) (Request, error) {
	// lock hash in map before reading from vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return nil, errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	switch strings.ToLower(t) {
	case "policy":
		var req PolicyRequest
		// fetch request from cubbyhole, if it exists
		resp, err := vault.ReadFromCubbyhole("requests/" + hash)
		if err != nil {
			return nil, err
		}
		if resp == nil {
			return nil, errors.New("Change ID not found")
		}
		// decode secret into policy request
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return nil, err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return nil, errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		if err := req.Verify(auth); err != nil {
			return nil, errors.New(err.Error() + ", thus, request has been deleted")
		}
		return &req, nil

	case "github":
		var req GithubRequest
		// fetch request from cubbyhole, if it exists
		resp, err := vault.ReadFromCubbyhole("requests/" + hash)
		if err != nil {
			return nil, err
		}
		if resp == nil {
			// a nil resp could mean this github req hasn't been approved yet
			return CreateGithubRequest(auth, map[string]interface{}{
				"commithash": hash,
			})
		}
		// decode secret into github request
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return nil, err
		}
		// verify user has vault privilege to read the contained policies
		if err := req.Verify(auth); err != nil {
			return nil, err
		}
		return &req, nil

	default:
		return nil, errors.New("Invalid request type: " + t)
	}
}

// if unseal is nonempty string, approve request with current auth
// otherwise, add unseal to list of unseals to generate root token later
func Approve(auth *vault.AuthInfo, hash string, unseal string) error {
	// lock hash in map before writing to vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	// fetch request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("requests/" + hash)
	if err != nil {
		return err
	}
	if resp == nil {
		return errors.New("Change ID not found")
	}

	// decode secret to a request
	t := ""
	if typeRaw, ok := resp.Data["Type"]; !ok {
		if typeRaw, ok = resp.Data["type"]; ok {
			t, _ = typeRaw.(string)
		}
	} else {
		t, _ = typeRaw.(string)
	}
	if t == "" {
		return errors.New("Invalid request type")
	}

	switch strings.ToLower(t) {
	case "policy":
		// decode secret into policy request
		var req PolicyRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Approve(hash, unseal)

	case "github":
		// decode secret into github request
		var req GithubRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify user has vault privleges to read contained policies
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Approve(hash, unseal)

	default:
		return errors.New("Invalid request type: " + t)
	}
}

// deletes request, if user is authorized to read resource
func Reject(auth *vault.AuthInfo, hash string) error {
	// lock hash in map before writing to vault cubbyhole
	lockMap.Lock()
	defer lockMap.Unlock()
	if _, locked := lockHash[hash]; locked {
		return errors.New("Someone else is currently editing this request")
	}
	lockHash[hash] = true
	defer delete(lockHash, hash)

	// fetch request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("requests/" + hash)
	if err != nil {
		return err
	}
	if resp == nil {
		return errors.New("Change ID not found")
	}

	// decode secret to a request
	t := ""
	if typeRaw, ok := resp.Data["Type"]; !ok {
		if typeRaw, ok = resp.Data["type"]; ok {
			t, _ = typeRaw.(string)
		}
	} else {
		t, _ = typeRaw.(string)
	}
	if t == "" {
		return errors.New("Invalid request type")
	}

	// verify user can access resource
	switch strings.ToLower(t) {
	case "policy":
		// decode secret into policy request
		var req PolicyRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify hash
		hash_uint64, err := hashstructure.Hash(req, nil)
		if err != nil || strconv.FormatUint(hash_uint64, 16) != hash {
			return errors.New("Hashes do not match")
		}
		// verify policy request is still valid
		return req.Reject(auth, hash)

	case "github":
		// decode secret into github request
		var req GithubRequest
		if err := mapstructure.Decode(resp.Data, &req); err != nil {
			return err
		}
		// verify user has vault privleges to read contained policies
		if err := req.Verify(auth); err != nil {
			return err
		}
		return req.Reject(auth, hash)

	default:
		return errors.New("Invalid request type: " + t)
	}
}

func IsRootOnly(req Request) bool {
	return req.IsRootOnly()
}

// attempts to generate a root token via unseal keys
// will return error if another key generation process is underway
func generateRootToken(unsealKeys []string) (string, error) {
	lockRoot.Lock()
	defer lockRoot.Unlock()

	otp := base64.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(16))
	status, err := vault.GenerateRootInit(otp)
	if err != nil {
		return "", err
	}

	if status.EncodedRootToken == "" {
		for _, s := range unsealKeys {
			status, err = vault.GenerateRootUpdate(s, status.Nonce)
			// an error likely means one of the unseals was not valid
			if err != nil {
				errS := "Could not generate root token: " + err.Error()
				// try to cancel the root generation
				if err := vault.GenerateRootCancel(); err != nil {
					errS += ". Attempted to cancel root generation, but: " + err.Error()
				}
				return "", errors.New(errS)
			}
		}
	}

	if status.EncodedRootToken == "" {
		return "", errors.New("Could not generate root token. Was vault re-keyed just now?")
	}

	tokenBytes, err := xor.XORBase64(status.EncodedRootToken, otp)
	if err != nil {
		return "", errors.New("Could not decode root token. Please search and revoke")
	}

	token, err := uuid.FormatUUID(tokenBytes)
	if err != nil {
		return "", errors.New("Could not decode root token. Please search and revoke")
	}

	return token, nil
}

// writes the provided unseal in and returns a slice of all unseals in hash
func appendUnseal(hash, unseal string) ([]string, error) {
	// read current request from cubbyhole
	resp, err := vault.ReadFromCubbyhole("unseal_wrapping_tokens/" + hash)
	if err != nil {
		return nil, err
	}

	var wrappingTokens []string

	// if there are already unseals, read them and append
	if resp != nil {
		raw := ""
		if temp, ok := resp.Data["wrapping_tokens"]; ok {
			raw, _ = temp.(string)
		}
		if raw == "" {
			return nil, errors.New("Could not find key 'wrapping_tokens' in cubbyhole")
		}
		wrappingTokens = append(wrappingTokens, strings.Split(raw, ";")...)
	}

	// wrap the unseal token
	newWrappingToken, err := vault.WrapData("60m", map[string]interface{}{
		"unseal_token": unseal,
	})
	if err != nil {
		return nil, err
	}

	// add the new unseal key in
	wrappingTokens = append(wrappingTokens, newWrappingToken)

	// write the unseals back to the cubbyhole
	_, err = vault.WriteToCubbyhole("unseal_wrapping_tokens/"+hash,
		map[string]interface{}{
			"wrapping_tokens": strings.Trim(strings.Join(strings.Fields(fmt.Sprint(wrappingTokens)), ";"), "[]"),
		},
	)
	return wrappingTokens, err
}

func unwrapUnseals(wrappingTokens []string) (unseals []string, err error) {
	for _, wrappingToken := range wrappingTokens {
		data, err := vault.UnwrapData(wrappingToken)
		if err != nil {
			return nil, err
		}
		if unseal, ok := data["unseal_token"]; ok {
			unseals = append(unseals, unseal.(string))
		} else {
			return nil, errors.New("One of the wrapping tokens timed out. Progress reset.")
		}
	}
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package instancepoller_test

import (
	"sort"
	"sync"

	"github.com/juju/errors"
	"github.com/juju/names"
	"github.com/juju/testing"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/apiserver/instancepoller"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/state"
)

// mockState implements StateInterface and allows inspection of called
// methods.
type mockState struct {
	*testing.Stub

	mu sync.Mutex

	configWatchers   []*mockConfigWatcher
	machinesWatchers []*mockMachinesWatcher

	config   *config.Config
	machines map[string]*mockMachine
}

func NewMockState() *mockState {
	return &mockState{
		Stub:     &testing.Stub{},
		machines: make(map[string]*mockMachine),
	}
}

var _ instancepoller.StateInterface = (*mockState)(nil)

// CheckFindEntityCall is a helper wrapper aroud
// testing.Stub.CheckCall for FindEntity.
func (m *mockState) CheckFindEntityCall(c *gc.C, index int, machineId string) {
	m.CheckCall(c, index, "FindEntity", interface{}(names.NewMachineTag(machineId)))
}

// CheckSetProviderAddressesCall is a helper wrapper aroud
// testing.Stub.CheckCall for SetProviderAddresses.
func (m *mockState) CheckSetProviderAddressesCall(c *gc.C, index int, addrs []network.Address) {
	args := make([]interface{}, len(addrs))
	for i, addr := range addrs {
		args[i] = addr
	}
	m.CheckCall(c, index, "SetProviderAddresses", args...)
}

// WatchForEnvironConfigChanges implements StateInterface.
func (m *mockState) WatchForEnvironConfigChanges() state.NotifyWatcher {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "WatchForEnvironConfigChanges")

	w := NewMockConfigWatcher(m.NextErr())
	m.configWatchers = append(m.configWatchers, w)
	return w
}

// EnvironConfig implements StateInterface.
func (m *mockState) EnvironConfig() (*config.Config, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "EnvironConfig")

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	return m.config, nil
}

// SetConfig updates the environ config stored internally. Triggers a
// change event for all created config watchers.
func (m *mockState) SetConfig(c *gc.C, newConfig *config.Config) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.config = newConfig

	// Notify any watchers for the changes.
	for _, w := range m.configWatchers {
		w.incoming <- struct{}{}
	}
}

// WatchEnvironMachines implements StateInterface.
func (m *mockState) WatchEnvironMachines() state.StringsWatcher {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "WatchEnvironMachines")

	ids := make([]string, 0, len(m.machines))
	// Initial event - all machine ids, sorted.
	for id := range m.machines {
		ids = append(ids, id)
	}
	sort.Strings(ids)

	w := NewMockMachinesWatcher(ids, m.NextErr())
	m.machinesWatchers = append(m.machinesWatchers, w)
	return w
}

// FindEntity implements StateInterface.
func (m *mockState) FindEntity(tag names.Tag) (state.Entity, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "FindEntity", tag)

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	if tag == nil {
		return nil, errors.NotValidf("nil tag is") // +" not valid"
	}
	machine, found := m.machines[tag.Id()]
	if !found {
		return nil, errors.NotFoundf("machine %s", tag.Id())
	}
	return machine, nil
}

// SetMachineInfo adds a new or updates existing mockMachine info.
// Triggers any created mock machines watchers to return a change.
func (m *mockState) SetMachineInfo(c *gc.C, args machineInfo) {
	m.mu.Lock()
	defer m.mu.Unlock()

	c.Assert(args.id, gc.Not(gc.Equals), "")

	machine, found := m.machines[args.id]
	if !found {
		machine = &mockMachine{
			Stub:        m.Stub, // reuse parent stub.
			machineInfo: args,
		}
	} else {
		machine.machineInfo = args
	}
	m.machines[args.id] = machine

	// Notify any watchers for the changes.
	ids := []string{args.id}
	for _, w := range m.machinesWatchers {
		w.incoming <- ids
	}
}

// RemoveMachine removes an existing mockMachine with the given id.
// Triggers the machines watchers on success. If the id is not found
// no error occurs and no change is reported by the watchers.
func (m *mockState) RemoveMachine(c *gc.C, id string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, found := m.machines[id]; !found {
		return
	}

	delete(m.machines, id)

	// Notify any watchers for the changes.
	ids := []string{id}
	for _, w := range m.machinesWatchers {
		w.incoming <- ids
	}
}

// Machine implements StateInterface.
func (m *mockState) Machine(id string) (instancepoller.StateMachine, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Machine", id)

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	machine, found := m.machines[id]
	if !found {
		return nil, errors.NotFoundf("machine %s", id)
	}
	return machine, nil
}

// StartSync implements statetesting.SyncStarter, so mockState can be
// used with watcher helpers/checkers.
func (m *mockState) StartSync() {}

type machineInfo struct {
	id                string
	instanceId        instance.Id
	status            state.StatusInfo
	instanceStatus    string
	providerAddresses []network.Address
	life              state.Life
	isManual          bool
}

type mockMachine struct {
	*testing.Stub
	instancepoller.StateMachine

	mu sync.Mutex

	machineInfo
}

var _ instancepoller.StateMachine = (*mockMachine)(nil)

// InstanceId implements StateMachine.
func (m *mockMachine) InstanceId() (instance.Id, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "InstanceId")
	if err := m.NextErr(); err != nil {
		return "", err
	}
	return m.instanceId, nil
}

// ProviderAddresses implements StateMachine.
func (m *mockMachine) ProviderAddresses() []network.Address {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "ProviderAddresses")
	m.NextErr() // consume the unused error
	return m.providerAddresses
}

// SetProviderAddresses implements StateMachine.
func (m *mockMachine) SetProviderAddresses(addrs ...network.Address) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	args := make([]interface{}, len(addrs))
	for i, addr := range addrs {
		args[i] = addr
	}
	m.MethodCall(m, "SetProviderAddresses", args...)
	if err := m.NextErr(); err != nil {
		return err
	}
	m.providerAddresses = addrs
	return nil
}

// InstanceStatus implements StateMachine.
func (m *mockMachine) InstanceStatus() (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "InstanceStatus")
	if err := m.NextErr(); err != nil {
		return "", err
	}
	return m.instanceStatus, nil
}

// SetInstanceStatus implements StateMachine.
func (m *mockMachine) SetInstanceStatus(status string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "SetInstanceStatus", status)
	if err := m.NextErr(); err != nil {
		return err
	}
	m.instanceStatus = status
	return nil
}

// Life implements StateMachine.
func (m *mockMachine) Life() state.Life {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Life")
	m.NextErr() // consume the unused error
	return m.life
}

// IsManual implements StateMachine.
func (m *mockMachine) IsManual() (bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "IsManual")
	return m.isManual, m.NextErr()
}

// Status implements StateMachine.
func (m *mockMachine) Status() (state.StatusInfo, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Status")
	return m.status, m.NextErr()
}

type mockBaseWatcher struct {
	err error

	closeChanges func()
	done         chan struct{}
}

var _ state.Watcher = (*mockBaseWatcher)(nil)

func NewMockBaseWatcher(err error, closeChanges func()) *mockBaseWatcher {
	w := &mockBaseWatcher{
		err:          err,
		closeChanges: closeChanges,
		done:         make(chan struct{}),
	}
	if err != nil {
		// Don't start the loop if we should fail.
		w.Stop()
	}
	return w
}

// Kill implements state.Watcher.
func (m *mockBaseWatcher) Kill() {}

// Stop implements state.Watcher.
func (m *mockBaseWatcher) Stop() error {
	if m.done != nil {
		// Signal the loop we want to stop.
		close(m.done)
		// Signal the clients we've closed.
		m.closeChanges()
		m.done = nil
	}
	return m.err
}

// Wait implements state.Watcher.
func (m *mockBaseWatcher) Wait() error {
	return m.Stop()
}

// Err implements state.Watcher.
func (m *mockBaseWatcher) Err() error {
	return m.err
}

type mockConfigWatcher struct {
	*mockBaseWatcher

	incoming chan struct{}
	changes  chan struct{}
}

var _ state.NotifyWatcher = (*mockConfigWatcher)(nil)

func NewMockConfigWatcher(err error) *mockConfigWatcher {
	changes := make(chan struct{})
	w := &mockConfigWatcher{
		changes:         changes,
		incoming:        make(chan struct{}),
		mockBaseWatcher: NewMockBaseWatcher(err, func() { close(changes) }),
	}
	if err == nil {
		go w.loop()
	}
	return w
}

func (m *mockConfigWatcher) loop() {
	// Prepare initial event.
	outChanges := m.changes
	// Forward any incoming changes until stopped.
	for {
		select {
		case <-m.done:
			// We're about to quit.
			return
		case outChanges <- struct{}{}:
			outChanges = nil
		case <-m.incoming:
			outChanges = m.changes
		}
	}
}

// Changes implements state.NotifyWatcher.
func (m *mockConfigWatcher) Changes() <-chan struct{} {
	return m.changes
}

type mockMachinesWatcher struct {
	*mockBaseWatcher

	initial  []string
	incoming chan []string
	changes  chan []string
}

func NewMockMachinesWatcher(initial []string, err error) *mockMachinesWatcher {
	changes := make(chan []string)
	w := &mockMachinesWatcher{
		initial:         initial,
		changes:         changes,
		incoming:        make(chan []string),
		mockBaseWatcher: NewMockBaseWatcher(err, func() { close(changes) }),
	}
	if err == nil {
		go w.loop()
	}
	return w
}

func (m *mockMachinesWatcher) loop() {
	// Prepare initial event.
	unsent := m.initial
	outChanges := m.changes
	// Forward any incoming changes until stopped.
	for {
		select {
		case <-m.done:
			// We're about to quit.
			return
		case outChanges <- unsent:
			outChanges = nil
			unsent = nil
		case ids := <-m.incoming:
			unsent = append(unsent, ids...)
			outChanges = m.changes
		}
	}
}

// Changes implements state.StringsWatcher.
func (m *mockMachinesWatcher) Changes() <-chan []string {
	return m.changes
}

var _ state.StringsWatcher = (*mockMachinesWatcher)(nil)
<commit_msg>apiserver/instancepoller: fix data race in test<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package instancepoller_test

import (
	"sort"
	"sync"

	"github.com/juju/errors"
	"github.com/juju/names"
	"github.com/juju/testing"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/apiserver/instancepoller"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/state"
)

// mockState implements StateInterface and allows inspection of called
// methods.
type mockState struct {
	*testing.Stub

	mu sync.Mutex

	configWatchers   []*mockConfigWatcher
	machinesWatchers []*mockMachinesWatcher

	config   *config.Config
	machines map[string]*mockMachine
}

func NewMockState() *mockState {
	return &mockState{
		Stub:     &testing.Stub{},
		machines: make(map[string]*mockMachine),
	}
}

var _ instancepoller.StateInterface = (*mockState)(nil)

// CheckFindEntityCall is a helper wrapper aroud
// testing.Stub.CheckCall for FindEntity.
func (m *mockState) CheckFindEntityCall(c *gc.C, index int, machineId string) {
	m.CheckCall(c, index, "FindEntity", interface{}(names.NewMachineTag(machineId)))
}

// CheckSetProviderAddressesCall is a helper wrapper aroud
// testing.Stub.CheckCall for SetProviderAddresses.
func (m *mockState) CheckSetProviderAddressesCall(c *gc.C, index int, addrs []network.Address) {
	args := make([]interface{}, len(addrs))
	for i, addr := range addrs {
		args[i] = addr
	}
	m.CheckCall(c, index, "SetProviderAddresses", args...)
}

// WatchForEnvironConfigChanges implements StateInterface.
func (m *mockState) WatchForEnvironConfigChanges() state.NotifyWatcher {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "WatchForEnvironConfigChanges")

	w := NewMockConfigWatcher(m.NextErr())
	m.configWatchers = append(m.configWatchers, w)
	return w
}

// EnvironConfig implements StateInterface.
func (m *mockState) EnvironConfig() (*config.Config, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "EnvironConfig")

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	return m.config, nil
}

// SetConfig updates the environ config stored internally. Triggers a
// change event for all created config watchers.
func (m *mockState) SetConfig(c *gc.C, newConfig *config.Config) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.config = newConfig

	// Notify any watchers for the changes.
	for _, w := range m.configWatchers {
		w.incoming <- struct{}{}
	}
}

// WatchEnvironMachines implements StateInterface.
func (m *mockState) WatchEnvironMachines() state.StringsWatcher {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "WatchEnvironMachines")

	ids := make([]string, 0, len(m.machines))
	// Initial event - all machine ids, sorted.
	for id := range m.machines {
		ids = append(ids, id)
	}
	sort.Strings(ids)

	w := NewMockMachinesWatcher(ids, m.NextErr())
	m.machinesWatchers = append(m.machinesWatchers, w)
	return w
}

// FindEntity implements StateInterface.
func (m *mockState) FindEntity(tag names.Tag) (state.Entity, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "FindEntity", tag)

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	if tag == nil {
		return nil, errors.NotValidf("nil tag is") // +" not valid"
	}
	machine, found := m.machines[tag.Id()]
	if !found {
		return nil, errors.NotFoundf("machine %s", tag.Id())
	}
	return machine, nil
}

// SetMachineInfo adds a new or updates existing mockMachine info.
// Triggers any created mock machines watchers to return a change.
func (m *mockState) SetMachineInfo(c *gc.C, args machineInfo) {
	m.mu.Lock()
	defer m.mu.Unlock()

	c.Assert(args.id, gc.Not(gc.Equals), "")

	machine, found := m.machines[args.id]
	if !found {
		machine = &mockMachine{
			Stub:        m.Stub, // reuse parent stub.
			machineInfo: args,
		}
	} else {
		machine.machineInfo = args
	}
	m.machines[args.id] = machine

	// Notify any watchers for the changes.
	ids := []string{args.id}
	for _, w := range m.machinesWatchers {
		w.incoming <- ids
	}
}

// RemoveMachine removes an existing mockMachine with the given id.
// Triggers the machines watchers on success. If the id is not found
// no error occurs and no change is reported by the watchers.
func (m *mockState) RemoveMachine(c *gc.C, id string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, found := m.machines[id]; !found {
		return
	}

	delete(m.machines, id)

	// Notify any watchers for the changes.
	ids := []string{id}
	for _, w := range m.machinesWatchers {
		w.incoming <- ids
	}
}

// Machine implements StateInterface.
func (m *mockState) Machine(id string) (instancepoller.StateMachine, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Machine", id)

	if err := m.NextErr(); err != nil {
		return nil, err
	}
	machine, found := m.machines[id]
	if !found {
		return nil, errors.NotFoundf("machine %s", id)
	}
	return machine, nil
}

// StartSync implements statetesting.SyncStarter, so mockState can be
// used with watcher helpers/checkers.
func (m *mockState) StartSync() {}

type machineInfo struct {
	id                string
	instanceId        instance.Id
	status            state.StatusInfo
	instanceStatus    string
	providerAddresses []network.Address
	life              state.Life
	isManual          bool
}

type mockMachine struct {
	*testing.Stub
	instancepoller.StateMachine

	mu sync.Mutex

	machineInfo
}

var _ instancepoller.StateMachine = (*mockMachine)(nil)

// InstanceId implements StateMachine.
func (m *mockMachine) InstanceId() (instance.Id, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "InstanceId")
	if err := m.NextErr(); err != nil {
		return "", err
	}
	return m.instanceId, nil
}

// ProviderAddresses implements StateMachine.
func (m *mockMachine) ProviderAddresses() []network.Address {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "ProviderAddresses")
	m.NextErr() // consume the unused error
	return m.providerAddresses
}

// SetProviderAddresses implements StateMachine.
func (m *mockMachine) SetProviderAddresses(addrs ...network.Address) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	args := make([]interface{}, len(addrs))
	for i, addr := range addrs {
		args[i] = addr
	}
	m.MethodCall(m, "SetProviderAddresses", args...)
	if err := m.NextErr(); err != nil {
		return err
	}
	m.providerAddresses = addrs
	return nil
}

// InstanceStatus implements StateMachine.
func (m *mockMachine) InstanceStatus() (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "InstanceStatus")
	if err := m.NextErr(); err != nil {
		return "", err
	}
	return m.instanceStatus, nil
}

// SetInstanceStatus implements StateMachine.
func (m *mockMachine) SetInstanceStatus(status string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "SetInstanceStatus", status)
	if err := m.NextErr(); err != nil {
		return err
	}
	m.instanceStatus = status
	return nil
}

// Life implements StateMachine.
func (m *mockMachine) Life() state.Life {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Life")
	m.NextErr() // consume the unused error
	return m.life
}

// IsManual implements StateMachine.
func (m *mockMachine) IsManual() (bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "IsManual")
	return m.isManual, m.NextErr()
}

// Status implements StateMachine.
func (m *mockMachine) Status() (state.StatusInfo, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.MethodCall(m, "Status")
	return m.status, m.NextErr()
}

type mockBaseWatcher struct {
	err error

	closeChanges func()
	done         chan struct{}
}

var _ state.Watcher = (*mockBaseWatcher)(nil)

func NewMockBaseWatcher(err error, closeChanges func()) *mockBaseWatcher {
	w := &mockBaseWatcher{
		err:          err,
		closeChanges: closeChanges,
		done:         make(chan struct{}),
	}
	if err != nil {
		// Don't start the loop if we should fail.
		w.Stop()
	}
	return w
}

// Kill implements state.Watcher.
func (m *mockBaseWatcher) Kill() {}

// Stop implements state.Watcher.
func (m *mockBaseWatcher) Stop() error {
	select {
	case <-m.done:
		// already closed
	default:
		// Signal the loop we want to stop.
		close(m.done)
		// Signal the clients we've closed.
		m.closeChanges()
	}
	return m.err
}

// Wait implements state.Watcher.
func (m *mockBaseWatcher) Wait() error {
	return m.Stop()
}

// Err implements state.Watcher.
func (m *mockBaseWatcher) Err() error {
	return m.err
}

type mockConfigWatcher struct {
	*mockBaseWatcher

	incoming chan struct{}
	changes  chan struct{}
}

var _ state.NotifyWatcher = (*mockConfigWatcher)(nil)

func NewMockConfigWatcher(err error) *mockConfigWatcher {
	changes := make(chan struct{})
	w := &mockConfigWatcher{
		changes:         changes,
		incoming:        make(chan struct{}),
		mockBaseWatcher: NewMockBaseWatcher(err, func() { close(changes) }),
	}
	if err == nil {
		go w.loop()
	}
	return w
}

func (m *mockConfigWatcher) loop() {
	// Prepare initial event.
	outChanges := m.changes
	// Forward any incoming changes until stopped.
	for {
		select {
		case <-m.done:
			// We're about to quit.
			return
		case outChanges <- struct{}{}:
			outChanges = nil
		case <-m.incoming:
			outChanges = m.changes
		}
	}
}

// Changes implements state.NotifyWatcher.
func (m *mockConfigWatcher) Changes() <-chan struct{} {
	return m.changes
}

type mockMachinesWatcher struct {
	*mockBaseWatcher

	initial  []string
	incoming chan []string
	changes  chan []string
}

func NewMockMachinesWatcher(initial []string, err error) *mockMachinesWatcher {
	changes := make(chan []string)
	w := &mockMachinesWatcher{
		initial:         initial,
		changes:         changes,
		incoming:        make(chan []string),
		mockBaseWatcher: NewMockBaseWatcher(err, func() { close(changes) }),
	}
	if err == nil {
		go w.loop()
	}
	return w
}

func (m *mockMachinesWatcher) loop() {
	// Prepare initial event.
	unsent := m.initial
	outChanges := m.changes
	// Forward any incoming changes until stopped.
	for {
		select {
		case <-m.done:
			// We're about to quit.
			return
		case outChanges <- unsent:
			outChanges = nil
			unsent = nil
		case ids := <-m.incoming:
			unsent = append(unsent, ids...)
			outChanges = m.changes
		}
	}
}

// Changes implements state.StringsWatcher.
func (m *mockMachinesWatcher) Changes() <-chan []string {
	return m.changes
}

var _ state.StringsWatcher = (*mockMachinesWatcher)(nil)
<|endoftext|>


<commit_before>package proxy_test

import (
	"fmt"
	"net/http"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/cloudfoundry-incubator/cf-mysql-acceptance-tests/helpers"
)

var _ = Describe("P-MySQL Proxy", func() {
	var (
		url string
	)

	BeforeEach(func() {
		url = fmt.Sprintf(
			"https://proxy-0.%s/v0/backends",
			helpers.TestConfig.Proxy.ExternalHost,
		)
	})

	It("prompts for Basic Auth creds when they aren't provided", func() {
		resp, err := http.Get(url)
		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
	})

	It("does not accept bad Basic Auth creds", func() {
		req, err := http.NewRequest("GET", url, nil)
		req.SetBasicAuth("bad_username", "bad_password")
		resp, err := http.DefaultClient.Do(req)

		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
	})

	It("accepts valid Basic Auth creds", func() {
		req, err := http.NewRequest("GET", url, nil)
		req.SetBasicAuth(
			helpers.TestConfig.Proxy.APIUsername,
			helpers.TestConfig.Proxy.APIPassword,
		)
		resp, err := http.DefaultClient.Do(req)
		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusOK))
	})
})
<commit_msg>Revert "Remove proxy redirect test"<commit_after>package proxy_test

import (
	"fmt"
	"net/http"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/cloudfoundry-incubator/cf-mysql-acceptance-tests/helpers"
)

var _ = Describe("P-MySQL Proxy", func() {
	var (
		url string
	)

	BeforeEach(func() {
		url = fmt.Sprintf(
			"https://proxy-0.%s/v0/backends",
			helpers.TestConfig.Proxy.ExternalHost,
		)
	})

	It("prompts for Basic Auth creds when they aren't provided", func() {
		resp, err := http.Get(url)
		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
	})

	It("does not accept bad Basic Auth creds", func() {
		req, err := http.NewRequest("GET", url, nil)
		req.SetBasicAuth("bad_username", "bad_password")
		resp, err := http.DefaultClient.Do(req)

		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
	})

	It("accepts valid Basic Auth creds", func() {
		req, err := http.NewRequest("GET", url, nil)
		req.SetBasicAuth(
			helpers.TestConfig.Proxy.APIUsername,
			helpers.TestConfig.Proxy.APIPassword,
		)
		resp, err := http.DefaultClient.Do(req)
		Expect(err).NotTo(HaveOccurred())
		Expect(resp.StatusCode).To(Equal(http.StatusOK))
	})

	Context("with http scheme", func() {
		BeforeEach(func() {
			url = fmt.Sprintf(
				"http://proxy-0.%s/v0/backends",
				helpers.TestConfig.Proxy.ExternalHost,
			)
		})

		It("redirects to https if ForceHTTPS is set", func() {
			req, err := http.NewRequest("GET", url, nil)
			req.SetBasicAuth(
				helpers.TestConfig.Proxy.APIUsername,
				helpers.TestConfig.Proxy.APIPassword,
			)
			resp, err := http.DefaultClient.Transport.RoundTrip(req)
			Expect(err).NotTo(HaveOccurred())

			if helpers.TestConfig.Proxy.ForceHTTPS {
				Expect(resp.StatusCode).To(Equal(http.StatusFound))

				location, err := resp.Location()
				Expect(err).NotTo(HaveOccurred())
				Expect(location.Scheme).To(Equal("https"))
			} else {
				Expect(resp.StatusCode).To(Equal(http.StatusOK))
			}
		})
	})
})
<|endoftext|>


<commit_before>package uc_file_merge

import (
	"errors"
	"github.com/watermint/toolbox/domain/model/mo_file"
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/service/sv_file"
	"github.com/watermint/toolbox/domain/service/sv_file_relocation"
	"github.com/watermint/toolbox/domain/usecase/uc_file_relocation"
	"github.com/watermint/toolbox/infra/api/api_context"
	"github.com/watermint/toolbox/infra/recpie/app_kitchen"
	"github.com/watermint/toolbox/infra/ui/app_msg"
	"go.uber.org/zap"
	"path/filepath"
	"strings"
	"time"
)

type Merge interface {
	Merge(from, to mo_path.Path, opts ...MergeOpt) error
}

func New(ctx api_context.Context, k app_kitchen.Kitchen) Merge {
	return &mergeImpl{
		ctx: ctx,
		k:   k,
	}
}

type MergeOpt func(opts *MergeOpts) *MergeOpts
type MergeOpts struct {
	DryRun              bool
	WithinSameNamespace bool
	CleanEmptyFolder    bool
}

func DryRun() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.DryRun = true
		return opts
	}
}
func WithinSameNamespace() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.WithinSameNamespace = true
		return opts
	}
}
func ClearEmptyFolder() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.CleanEmptyFolder = true
		return opts
	}
}

type mergeImpl struct {
	ctx       api_context.Context
	k         app_kitchen.Kitchen
	from      mo_path.Path
	fromEntry mo_file.Entry
	to        mo_path.Path
	toEntry   mo_file.Entry
	opts      *MergeOpts
}

func (z *mergeImpl) doOperation(msg app_msg.Message, op func() error) error {
	l := z.k.Log()
	msgParam := make([]app_msg.P, 0)
	msgParam = append(msgParam, msg.Params()...)
	if z.opts.DryRun {
		msgParam = append(msgParam, app_msg.P{
			"DryRun": "DryRun: ",
		})
	}

	z.k.UI().Info(msg.Key(), msg.Params()...)
	if !z.opts.DryRun {
		return op()
	}
	l.Debug("Skip operation")
	return nil
}

func (z *mergeImpl) mergeFile(from, to *mo_file.File) error {
	l := z.k.Log().With(zap.String("from", from.PathDisplay()), zap.String("to", to.PathDisplay()))

	// remove same content hash file
	if from.ContentHash == to.ContentHash {
		l.Debug("Same content hash", zap.String("contentHash", from.ContentHash))
		m := app_msg.M("usecase.uc_file_merge.remove_duplicated_file",
			app_msg.P{
				"FromPath": from.PathDisplay(),
			})
		return z.doOperation(m, func() error {
			entry, err := sv_file.NewFiles(z.ctx).Remove(mo_path.NewPathDisplay(from.PathDisplay()))
			if err != nil {
				l.Debug("Unable to remove file", zap.Error(err))
				return err
			}
			l.Debug("File removed", zap.Any("removedEntry", entry.Concrete()))
			return nil
		})
	}

	fromTs, err := time.Parse(time.RFC3339, from.ServerModified)
	if err != nil {
		l.Warn("Invalid time format", zap.Error(err), zap.String("from.ServerModified", from.ServerModified))
		return err
	}
	toTs, err := time.Parse(time.RFC3339, to.ServerModified)
	if err != nil {
		l.Warn("Invalid time format", zap.Error(err), zap.String("to.ServerModified", to.ServerModified))
		return err
	}

	l = l.With(zap.String("fromServerModified", from.ServerModified), zap.String("toServerModified", to.ServerModified))

	// remove old content
	if toTs.After(fromTs) {
		l.Debug("Remove the old file at from path")
		m := app_msg.M("usecase.uc_file_merge.remove_old_content",
			app_msg.P{
				"FromPath": from.PathDisplay(),
			})
		return z.doOperation(m, func() error {
			entry, err := sv_file.NewFiles(z.ctx).Remove(mo_path.NewPathDisplay(from.PathDisplay()))
			if err != nil {
				l.Debug("Unable to remove file", zap.Error(err))
				return err
			}
			l.Debug("File removed", zap.Any("removedEntry", entry.Concrete()))
			return nil
		})
	}

	// overwrite `from file` to `to path`
	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := mo_path.NewPathDisplay(to.PathDisplay())
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   to.PathDisplay(),
		},
	)
	return z.doOperation(m, func() error {
		entry, err := sv_file_relocation.New(z.ctx, sv_file_relocation.AutoRename(false)).Move(fp, tp)
		if err != nil {
			l.Debug("Unable to move file", zap.Error(err))
			return err
		}
		l.Debug("File moved", zap.Any("movedEntry", entry.Concrete()))

		return nil
	})
}

func (z *mergeImpl) moveFile(from *mo_file.File) error {
	l := z.k.Log().With(zap.Any("from", from.Concrete()))
	l.Debug("Move file")
	p, err := filepath.Rel(z.fromEntry.PathLower(), from.PathLower())
	if err != nil {
		l.Warn("Unable to calc relative path", zap.Error(err))
		// TODO: do reporting
		return err
	}
	if strings.HasPrefix(p, "..") {
		l.Warn("Invalid relative path", zap.String("rel", p))
		// TODO; do reporting
		return errors.New("invalid relative path")
	}

	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := z.to.ChildPath(filepath.Join(filepath.Dir(p), from.Name()))
	l.Debug("move file")
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   tp.Path(),
		},
	)
	return z.doOperation(m, func() error {
		entry, err := sv_file_relocation.New(z.ctx).Move(fp, tp)
		if err != nil {
			l.Debug("Unable to move", zap.Error(err))
			return err
		}
		l.Debug("file moved", zap.Any("entry", entry.Concrete()))
		return nil
	})
}

func (z *mergeImpl) moveFolder(from *mo_file.Folder) error {
	l := z.k.Log().With(zap.Any("from", from.Concrete()))
	l.Debug("Move folder")
	p, err := filepath.Rel(z.fromEntry.PathLower(), from.PathLower())
	if err != nil {
		l.Warn("Unable to calc relative path", zap.Error(err))
		// TODO: do reporting
		return err
	}
	if strings.HasPrefix(p, "..") {
		l.Warn("Invalid relative path", zap.String("rel", p))
		// TODO; do reporting
		return errors.New("invalid relative path")
	}
	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := z.to.ChildPath(filepath.Join(filepath.Dir(p), from.Name()))

	l = l.With(zap.String("toPath", tp.Path()))

	// move
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   tp.Path(),
		},
	)
	return z.doOperation(m, func() error {
		l.Debug("move folder")
		return uc_file_relocation.New(z.ctx).Move(fp, tp)
	})
}

func (z *mergeImpl) validatePaths(from, to mo_file.Entry) error {
	l := z.k.Log()

	ff, e := from.Folder()
	if !e {
		return errors.New("`from` path is not a folder")
	}
	tf, e := to.Folder()
	if !e {
		return errors.New("`to` path is not a folder")
	}
	l = l.With(zap.Any("from", ff.Concrete()), zap.Any("to", tf.Concrete()))

	if !z.opts.WithinSameNamespace {
		l.Debug("Skip validate namespace")
		return nil
	}

	if ff.EntrySharedFolderId != tf.EntrySharedFolderId ||
		ff.EntryParentSharedFolderId != tf.EntryParentSharedFolderId {
		l.Debug("Different namespace")
		return errors.New("`from` path and `to` path are different namespace")
	}

	l.Debug("Go for merge")
	return nil
}

// Merge at relative path from `z.from`.
func (z *mergeImpl) merge(path string) error {
	fromFiles := make(map[string]*mo_file.File)
	fromFolders := make(map[string]*mo_file.Folder)
	toFiles := make(map[string]*mo_file.File)
	toFolders := make(map[string]*mo_file.Folder)

	l := z.k.Log().With(zap.String("path", path))
	l.Debug("merge")

	fromPath := z.from.ChildPath(path)
	toPath := z.to.ChildPath(path)

	// Scan from
	{
		entries, err := sv_file.NewFiles(z.ctx).List(fromPath)
		if err != nil {
			return err
		}
		for _, entry := range entries {
			if f, e := entry.File(); e {
				fromFiles[strings.ToLower(f.Name())] = f
			}
			if f, e := entry.Folder(); e {
				fromFolders[strings.ToLower(f.Name())] = f
			}
		}
	}

	// Scan to
	{
		entries, err := sv_file.NewFiles(z.ctx).List(toPath)
		if err != nil {
			return err
		}
		for _, entry := range entries {
			if f, e := entry.File(); e {
				toFiles[strings.ToLower(f.Name())] = f
			}
			if f, e := entry.Folder(); e {
				toFolders[strings.ToLower(f.Name())] = f
			}
		}
	}

	var lastErr error

	// move or merge files
	for ffn, ff := range fromFiles {
		ll := l.With(zap.String("from", ff.EntryPathDisplay))
		if tf, e := toFiles[ffn]; e {
			ll = ll.With(zap.String("to", tf.EntryPathDisplay))
			if err := z.mergeFile(ff, tf); err != nil {
				ll.Debug("Unable to merge", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("File merged")
			}
		} else {
			if err := z.moveFile(ff); err != nil {
				ll.Debug("Unable to move", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("File moved")
			}
		}
	}

	// move or merge folders
	for ffn, ff := range fromFolders {
		ll := l.With(zap.String("from", ff.EntryPathDisplay))
		if _, e := toFolders[ffn]; e {
			ll.Debug("Proceed into descendants")
			p, err := filepath.Rel(z.fromEntry.PathLower(), ff.PathLower())
			if err != nil {
				ll.Warn("Unable to calc relative path", zap.Error(err))
				// TODO: do reporting
				continue
			}
			if strings.HasPrefix(p, "..") {
				ll.Warn("Invalid relative path", zap.String("rel", p))
				// TODO; do reporting
				continue
			}

			if err := z.merge(p); err != nil {
				ll.Debug("One or more error in descendant", zap.Error(err))
				lastErr = err
			}

		} else {
			if err := z.moveFolder(ff); err != nil {
				ll.Debug("Unable to move", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("Folder moved")
			}
		}
	}

	// remove if the folder empty
	if z.opts.CleanEmptyFolder {
		entries, err := sv_file.NewFiles(z.ctx).List(fromPath)
		if err != nil {
			l.Debug("Unable to list", zap.Error(err))
			return lastErr
		}
		if len(entries) < 1 {
			l.Debug("Try clean up folder")
			m := app_msg.M("usecase.uc_file_merge.remove_empty_folder",
				app_msg.P{
					"FromPath": fromPath.Path(),
				},
			)
			z.doOperation(m, func() error {
				entry, err := sv_file.NewFiles(z.ctx).Remove(fromPath)
				if err != nil {
					l.Debug("Unable to remove folder", zap.Error(err))
					return err
				}
				l.Debug("Removed", zap.Any("removed", entry.Concrete()))
				return nil
			})
		} else {
			l.Debug("Remaining entries", zap.Int("entries", len(entries)))
		}
	}

	return lastErr
}

func (z *mergeImpl) Merge(from, to mo_path.Path, opts ...MergeOpt) (err error) {
	z.opts = &MergeOpts{}
	for _, o := range opts {
		o(z.opts)
	}
	z.from = from
	z.to = to
	l := z.k.Log().With(zap.String("from", from.Path()), zap.String("to", to.Path()))

	z.fromEntry, err = sv_file.NewFiles(z.ctx).Resolve(from)
	if err != nil {
		l.Debug("Unable to resolve fromFolder", zap.Error(err))
		return err
	}

	z.toEntry, err = sv_file.NewFiles(z.ctx).Resolve(to)
	if err != nil {
		l.Debug("Unable to resolve toFolder", zap.Error(err))
		return err
	}

	if err := z.validatePaths(z.fromEntry, z.toEntry); err != nil {
		return err
	}

	return z.merge("")
}
<commit_msg>fix #184 : file merge command didn't handle output of dry-run<commit_after>package uc_file_merge

import (
	"errors"
	"github.com/watermint/toolbox/domain/model/mo_file"
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/service/sv_file"
	"github.com/watermint/toolbox/domain/service/sv_file_relocation"
	"github.com/watermint/toolbox/domain/usecase/uc_file_relocation"
	"github.com/watermint/toolbox/infra/api/api_context"
	"github.com/watermint/toolbox/infra/recpie/app_kitchen"
	"github.com/watermint/toolbox/infra/ui/app_msg"
	"go.uber.org/zap"
	"path/filepath"
	"strings"
	"time"
)

type Merge interface {
	Merge(from, to mo_path.Path, opts ...MergeOpt) error
}

func New(ctx api_context.Context, k app_kitchen.Kitchen) Merge {
	return &mergeImpl{
		ctx: ctx,
		k:   k,
	}
}

type MergeOpt func(opts *MergeOpts) *MergeOpts
type MergeOpts struct {
	DryRun              bool
	WithinSameNamespace bool
	CleanEmptyFolder    bool
}

func DryRun() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.DryRun = true
		return opts
	}
}
func WithinSameNamespace() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.WithinSameNamespace = true
		return opts
	}
}
func ClearEmptyFolder() MergeOpt {
	return func(opts *MergeOpts) *MergeOpts {
		opts.CleanEmptyFolder = true
		return opts
	}
}

type mergeImpl struct {
	ctx       api_context.Context
	k         app_kitchen.Kitchen
	from      mo_path.Path
	fromEntry mo_file.Entry
	to        mo_path.Path
	toEntry   mo_file.Entry
	opts      *MergeOpts
}

func (z *mergeImpl) doOperation(msg app_msg.Message, op func() error) error {
	l := z.k.Log()
	msgParam := make([]app_msg.P, 0)
	msgParam = append(msgParam, msg.Params()...)
	if z.opts.DryRun {
		msgParam = append(msgParam, app_msg.P{
			"DryRun": "DryRun: ",
		})
	}

	z.k.UI().Info(msg.Key(), msgParam...)
	if !z.opts.DryRun {
		return op()
	}
	l.Debug("Skip operation")
	return nil
}

func (z *mergeImpl) mergeFile(from, to *mo_file.File) error {
	l := z.k.Log().With(zap.String("from", from.PathDisplay()), zap.String("to", to.PathDisplay()))

	// remove same content hash file
	if from.ContentHash == to.ContentHash {
		l.Debug("Same content hash", zap.String("contentHash", from.ContentHash))
		m := app_msg.M("usecase.uc_file_merge.remove_duplicated_file",
			app_msg.P{
				"FromPath": from.PathDisplay(),
			})
		return z.doOperation(m, func() error {
			entry, err := sv_file.NewFiles(z.ctx).Remove(mo_path.NewPathDisplay(from.PathDisplay()))
			if err != nil {
				l.Debug("Unable to remove file", zap.Error(err))
				return err
			}
			l.Debug("File removed", zap.Any("removedEntry", entry.Concrete()))
			return nil
		})
	}

	fromTs, err := time.Parse(time.RFC3339, from.ServerModified)
	if err != nil {
		l.Warn("Invalid time format", zap.Error(err), zap.String("from.ServerModified", from.ServerModified))
		return err
	}
	toTs, err := time.Parse(time.RFC3339, to.ServerModified)
	if err != nil {
		l.Warn("Invalid time format", zap.Error(err), zap.String("to.ServerModified", to.ServerModified))
		return err
	}

	l = l.With(zap.String("fromServerModified", from.ServerModified), zap.String("toServerModified", to.ServerModified))

	// remove old content
	if toTs.After(fromTs) {
		l.Debug("Remove the old file at from path")
		m := app_msg.M("usecase.uc_file_merge.remove_old_content",
			app_msg.P{
				"FromPath": from.PathDisplay(),
			})
		return z.doOperation(m, func() error {
			entry, err := sv_file.NewFiles(z.ctx).Remove(mo_path.NewPathDisplay(from.PathDisplay()))
			if err != nil {
				l.Debug("Unable to remove file", zap.Error(err))
				return err
			}
			l.Debug("File removed", zap.Any("removedEntry", entry.Concrete()))
			return nil
		})
	}

	// overwrite `from file` to `to path`
	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := mo_path.NewPathDisplay(to.PathDisplay())
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   to.PathDisplay(),
		},
	)
	return z.doOperation(m, func() error {
		entry, err := sv_file_relocation.New(z.ctx, sv_file_relocation.AutoRename(false)).Move(fp, tp)
		if err != nil {
			l.Debug("Unable to move file", zap.Error(err))
			return err
		}
		l.Debug("File moved", zap.Any("movedEntry", entry.Concrete()))

		return nil
	})
}

func (z *mergeImpl) moveFile(from *mo_file.File) error {
	l := z.k.Log().With(zap.Any("from", from.Concrete()))
	l.Debug("Move file")
	p, err := filepath.Rel(z.fromEntry.PathLower(), from.PathLower())
	if err != nil {
		l.Warn("Unable to calc relative path", zap.Error(err))
		// TODO: do reporting
		return err
	}
	if strings.HasPrefix(p, "..") {
		l.Warn("Invalid relative path", zap.String("rel", p))
		// TODO; do reporting
		return errors.New("invalid relative path")
	}

	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := z.to.ChildPath(filepath.Join(filepath.Dir(p), from.Name()))
	l.Debug("move file")
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   tp.Path(),
		},
	)
	return z.doOperation(m, func() error {
		entry, err := sv_file_relocation.New(z.ctx).Move(fp, tp)
		if err != nil {
			l.Debug("Unable to move", zap.Error(err))
			return err
		}
		l.Debug("file moved", zap.Any("entry", entry.Concrete()))
		return nil
	})
}

func (z *mergeImpl) moveFolder(from *mo_file.Folder) error {
	l := z.k.Log().With(zap.Any("from", from.Concrete()))
	l.Debug("Move folder")
	p, err := filepath.Rel(z.fromEntry.PathLower(), from.PathLower())
	if err != nil {
		l.Warn("Unable to calc relative path", zap.Error(err))
		// TODO: do reporting
		return err
	}
	if strings.HasPrefix(p, "..") {
		l.Warn("Invalid relative path", zap.String("rel", p))
		// TODO; do reporting
		return errors.New("invalid relative path")
	}
	fp := mo_path.NewPathDisplay(from.PathDisplay())
	tp := z.to.ChildPath(filepath.Join(filepath.Dir(p), from.Name()))

	l = l.With(zap.String("toPath", tp.Path()))

	// move
	m := app_msg.M("usecase.uc_file_merge.move_file",
		app_msg.P{
			"FromPath": from.PathDisplay(),
			"ToPath":   tp.Path(),
		},
	)
	return z.doOperation(m, func() error {
		l.Debug("move folder")
		return uc_file_relocation.New(z.ctx).Move(fp, tp)
	})
}

func (z *mergeImpl) validatePaths(from, to mo_file.Entry) error {
	l := z.k.Log()

	ff, e := from.Folder()
	if !e {
		return errors.New("`from` path is not a folder")
	}
	tf, e := to.Folder()
	if !e {
		return errors.New("`to` path is not a folder")
	}
	l = l.With(zap.Any("from", ff.Concrete()), zap.Any("to", tf.Concrete()))

	if !z.opts.WithinSameNamespace {
		l.Debug("Skip validate namespace")
		return nil
	}

	if ff.EntrySharedFolderId != tf.EntrySharedFolderId ||
		ff.EntryParentSharedFolderId != tf.EntryParentSharedFolderId {
		l.Debug("Different namespace")
		return errors.New("`from` path and `to` path are different namespace")
	}

	l.Debug("Go for merge")
	return nil
}

// Merge at relative path from `z.from`.
func (z *mergeImpl) merge(path string) error {
	fromFiles := make(map[string]*mo_file.File)
	fromFolders := make(map[string]*mo_file.Folder)
	toFiles := make(map[string]*mo_file.File)
	toFolders := make(map[string]*mo_file.Folder)

	l := z.k.Log().With(zap.String("path", path))
	l.Debug("merge")

	fromPath := z.from.ChildPath(path)
	toPath := z.to.ChildPath(path)

	// Scan from
	{
		entries, err := sv_file.NewFiles(z.ctx).List(fromPath)
		if err != nil {
			return err
		}
		for _, entry := range entries {
			if f, e := entry.File(); e {
				fromFiles[strings.ToLower(f.Name())] = f
			}
			if f, e := entry.Folder(); e {
				fromFolders[strings.ToLower(f.Name())] = f
			}
		}
	}

	// Scan to
	{
		entries, err := sv_file.NewFiles(z.ctx).List(toPath)
		if err != nil {
			return err
		}
		for _, entry := range entries {
			if f, e := entry.File(); e {
				toFiles[strings.ToLower(f.Name())] = f
			}
			if f, e := entry.Folder(); e {
				toFolders[strings.ToLower(f.Name())] = f
			}
		}
	}

	var lastErr error

	// move or merge files
	for ffn, ff := range fromFiles {
		ll := l.With(zap.String("from", ff.EntryPathDisplay))
		if tf, e := toFiles[ffn]; e {
			ll = ll.With(zap.String("to", tf.EntryPathDisplay))
			if err := z.mergeFile(ff, tf); err != nil {
				ll.Debug("Unable to merge", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("File merged")
			}
		} else {
			if err := z.moveFile(ff); err != nil {
				ll.Debug("Unable to move", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("File moved")
			}
		}
	}

	// move or merge folders
	for ffn, ff := range fromFolders {
		ll := l.With(zap.String("from", ff.EntryPathDisplay))
		if _, e := toFolders[ffn]; e {
			ll.Debug("Proceed into descendants")
			p, err := filepath.Rel(z.fromEntry.PathLower(), ff.PathLower())
			if err != nil {
				ll.Warn("Unable to calc relative path", zap.Error(err))
				// TODO: do reporting
				continue
			}
			if strings.HasPrefix(p, "..") {
				ll.Warn("Invalid relative path", zap.String("rel", p))
				// TODO; do reporting
				continue
			}

			if err := z.merge(p); err != nil {
				ll.Debug("One or more error in descendant", zap.Error(err))
				lastErr = err
			}

		} else {
			if err := z.moveFolder(ff); err != nil {
				ll.Debug("Unable to move", zap.Error(err))
				lastErr = err
			} else {
				ll.Debug("Folder moved")
			}
		}
	}

	// remove if the folder empty
	if z.opts.CleanEmptyFolder {
		entries, err := sv_file.NewFiles(z.ctx).List(fromPath)
		if err != nil {
			l.Debug("Unable to list", zap.Error(err))
			return lastErr
		}
		if len(entries) < 1 {
			l.Debug("Try clean up folder")
			m := app_msg.M("usecase.uc_file_merge.remove_empty_folder",
				app_msg.P{
					"FromPath": fromPath.Path(),
				},
			)
			z.doOperation(m, func() error {
				entry, err := sv_file.NewFiles(z.ctx).Remove(fromPath)
				if err != nil {
					l.Debug("Unable to remove folder", zap.Error(err))
					return err
				}
				l.Debug("Removed", zap.Any("removed", entry.Concrete()))
				return nil
			})
		} else {
			l.Debug("Remaining entries", zap.Int("entries", len(entries)))
		}
	}

	return lastErr
}

func (z *mergeImpl) Merge(from, to mo_path.Path, opts ...MergeOpt) (err error) {
	z.opts = &MergeOpts{}
	for _, o := range opts {
		o(z.opts)
	}
	z.from = from
	z.to = to
	l := z.k.Log().With(zap.String("from", from.Path()), zap.String("to", to.Path()))

	z.fromEntry, err = sv_file.NewFiles(z.ctx).Resolve(from)
	if err != nil {
		l.Debug("Unable to resolve fromFolder", zap.Error(err))
		return err
	}

	z.toEntry, err = sv_file.NewFiles(z.ctx).Resolve(to)
	if err != nil {
		l.Debug("Unable to resolve toFolder", zap.Error(err))
		return err
	}

	if err := z.validatePaths(z.fromEntry, z.toEntry); err != nil {
		return err
	}

	return z.merge("")
}
<|endoftext|>