<commit_before>/*
 * Copyright 2018 The original author or 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 core

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const (
	istioNamespace  = "istio-system"
	istioCrds       = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-crds.yaml"
	istioRelease    = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-main.yaml"
	servingRelease  = "https://storage.googleapis.com/knative-releases/serving/previous/v20180809-6b01d8e/release-no-mon.yaml"
	eventingRelease = "https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release.yaml"
	stubBusRelease  = "https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release-clusterbus-stub.yaml"
)

type SystemInstallOptions struct {
	NodePort bool
	Force    bool
}

type SystemUninstallOptions struct {
	Istio bool
	Force bool
}

var (
	knativeNamespaces = []string{"knative-eventing", "knative-serving", "knative-build"}
	allNameSpaces     = append(knativeNamespaces, istioNamespace)
)

func (kc *kubectlClient) SystemInstall(options SystemInstallOptions) (bool, error) {

	err := ensureNotTerminating(kc, allNameSpaces, "Please try again later.")
	if err != nil {
		return false, err
	}

	istioStatus, err := getNamespaceStatus(kc, istioNamespace)
	if istioStatus == "'NotFound'" {
		fmt.Print("Installing Istio components\n")
		applyResources(kc, istioCrds)
		time.Sleep(5 * time.Second) // wait for them to get created
		istioYaml, err := loadRelease(istioRelease)
		if err != nil {
			return false, err
		}
		if options.NodePort {
			istioYaml = bytes.Replace(istioYaml, []byte("LoadBalancer"), []byte("NodePort"), -1)
		}
		fmt.Printf("Applying resources defined in: %s\n", istioRelease)
		istioLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &istioYaml)
		if err != nil {
			fmt.Printf("%s\n", istioLog)
			return false, err
		}

		fmt.Print("Istio for riff installed\n\n")
	} else {
		if !options.Force {
			answer, err := confirm("Istio is already installed, do you want to install the Knative components for riff?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
	}

	err = waitForIstioComponents(kc)
	if err != nil {
		return false, err
	}

	fmt.Print("Installing Knative components\n")

	servingYaml, err := loadRelease(servingRelease)
	if err != nil {
		return false, err
	}
	if options.NodePort {
		servingYaml = bytes.Replace(servingYaml, []byte("LoadBalancer"), []byte("NodePort"), -1)
	}
	fmt.Printf("Applying resources defined in: %s\n", servingRelease)
	servingLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &servingYaml)
	if err != nil {
		fmt.Printf("%s\n", servingLog)
		return false, err
	}

	applyResources(kc, eventingRelease)

	applyResources(kc, stubBusRelease)

	fmt.Print("Knative for riff installed\n\n")
	return true, nil
}

func (kc *kubectlClient) SystemUninstall(options SystemUninstallOptions) (bool, error) {

	err := ensureNotTerminating(kc, allNameSpaces, "This would indicate that the system was already uninstalled.")
	if err != nil {
		return false, err
	}
	knativeNsCount, err := checkNamespacesExists(kc, knativeNamespaces)
	istioNsCount, err := checkNamespacesExists(kc, []string{istioNamespace})
	if err != nil {
		return false, err
	}
	if knativeNsCount == 0 {
		fmt.Print("No Knative components for riff found\n")
	} else {
		if !options.Force {
			answer, err := confirm("Are you sure you want to uninstall the riff system?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
		fmt.Print("Removing Knative for riff components\n")
		err = deleteCrds(kc, "knative.dev")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "knative-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "build-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "eventing-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "clusterbus-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrole", "knative-")
		if err != nil {
			return false, err
		}
		err = deleteNamespaces(kc, knativeNamespaces)
		if err != nil {
			return false, err
		}
	}
	if istioNsCount == 0 {
		fmt.Print("No Istio components found\n")
	} else {
		if !options.Istio {
			if options.Force {
				return true, nil
			}
			answer, err := confirm("Do you also want to uninstall Istio components?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
		fmt.Print("Removing Istio components\n")
		err = deleteCrds(kc, "istio.io")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "istio-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrole", "istio-")
		if err != nil {
			return false, err
		}
		err = deleteNamespaces(kc, []string{istioNamespace})
		if err != nil {
			return false, err
		}
	}
	return true, nil
}

func resolveReleaseURLs(filename string) (url.URL, error) {
	u, err := url.Parse(filename)
	if err != nil {
		return url.URL{}, err
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return *u, nil
	}
	return *u, fmt.Errorf("filename must be file, http or https, got %s", u.Scheme)
}

func loadRelease(release string) ([]byte, error) {
	releaseUrl, err := resolveReleaseURLs(release)
	if err != nil {
		return nil, err
	}
	resp, err := http.Get(releaseUrl.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

func waitForIstioComponents(kc *kubectlClient) error {
	fmt.Print("Waiting for the Istio components to start ")
	for i := 0; i < 36; i++ {
		fmt.Print(".")
		pods := kc.kubeClient.CoreV1().Pods(istioNamespace)
		podList, err := pods.List(metav1.ListOptions{})
		if err != nil {
			return err
		}
		waitLonger := false
		for _, pod := range podList.Items {
			if !strings.HasPrefix(pod.Name, "istio-") {
				continue
			}
			if pod.Status.Phase != "Running" && pod.Status.Phase != "Succeeded" {
				waitLonger = true
				break
			} else {
				if pod.Status.Phase == "Running" {
					containers := pod.Status.ContainerStatuses
					for _, cont := range containers {
						if !cont.Ready {
							waitLonger = true
							break
						}
					}
				}
			}
		}
		if !waitLonger {
			fmt.Print(" all components are 'Running'\n\n")
			return nil
		}
		time.Sleep(10 * time.Second) // wait for them to start
	}
	return errors.New("the Istio components did not start in time")
}

func applyResources(kc *kubectlClient, release string) error {
	releaseUrl, err := resolveReleaseURLs(release)
	if err != nil {
		return err
	}
	fmt.Printf("Applying resources defined in: %s\n", releaseUrl.String())
	releaseLog, err := kc.kubeCtl.Exec([]string{"apply", "-f", releaseUrl.String()})
	if err != nil {
		fmt.Printf("%s", releaseLog)
	}
	return nil
}

func deleteNamespaces(kc *kubectlClient, namespaces []string) error {
	for _, namespace := range namespaces {
		fmt.Printf("Deleting resources defined in: %s\n", namespace)
		deleteLog, err := kc.kubeCtl.Exec([]string{"delete", "namespace", namespace})
		if err != nil {
			fmt.Printf("%s", deleteLog)
		}
	}
	return nil
}

func deleteClusterResources(kc *kubectlClient, resourceType string, prefix string) error {
	fmt.Printf("Deleting %ss prefixed with %s\n", resourceType, prefix)
	resourceList, err := kc.kubeCtl.Exec([]string{"get", resourceType, "-ocustom-columns=name:metadata.name"})
	if err != nil {
		return err
	}
	resource := strings.Split(string(resourceList), "\n")
	var resourcesToDelete []string
	for _, resource := range resource {
		if strings.HasPrefix(resource, prefix) {
			resourcesToDelete = append(resourcesToDelete, resource)
		}
	}
	if len(resourcesToDelete) > 0 {
		resourceLog, err := kc.kubeCtl.Exec(append([]string{"delete", resourceType}, resourcesToDelete...))
		if err != nil {
			fmt.Printf("%s", resourceLog)
			return err
		}
	}
	return nil
}

func deleteCrds(kc *kubectlClient, suffix string) error {
	fmt.Printf("Deleting CRDs for %s\n", suffix)
	crdList, err := kc.kubeCtl.Exec([]string{"get", "customresourcedefinitions", "-ocustom-columns=name:metadata.name"})
	if err != nil {
		return err
	}
	crds := strings.Split(string(crdList), "\n")
	var crdsToDelete []string
	for _, crd := range crds {
		if strings.HasSuffix(crd, suffix) {
			crdsToDelete = append(crdsToDelete, crd)
		}
	}
	if len(crdsToDelete) > 0 {
		crdLog, err := kc.kubeCtl.Exec(append([]string{"delete", "customresourcedefinition"}, crdsToDelete...))
		if err != nil {
			fmt.Printf("%s", crdLog)
			return err
		}
	}
	return nil
}

func checkNamespacesExists(kc *kubectlClient, names []string) (int, error) {
	count := 0
	for _, name := range names {
		status, err := getNamespaceStatus(kc, name)
		if err != nil {
			return count, err
		}
		if status != "'NotFound'" {
			count = +1
		}
	}
	return count, nil
}

func ensureNotTerminating(kc *kubectlClient, names []string, message string) error {
	for _, name := range names {
		status, err := getNamespaceStatus(kc, name)
		if err != nil {
			return err
		}
		if status == "'Terminating'" {
			return errors.New(fmt.Sprintf("The %s namespace is currently 'Terminating'. %s", name, message))
		}
	}
	return nil
}

func getNamespaceStatus(kc *kubectlClient, name string) (string, error) {
	nsLog, err := kc.kubeCtl.Exec([]string{"get", "namespace", name, "-o", "jsonpath='{.status.phase}'"})
	if err != nil {
		if strings.Contains(nsLog, "NotFound") {
			return "'NotFound'", nil
		}
		return "", err
	}
	return nsLog, nil
}

func confirm(s string) (bool, error) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Printf("%s [y/N]: ", s)
	res, err := reader.ReadString('\n')
	if err != nil {
		return false, err
	}
	if len(res) < 2 {
		return false, nil
	}
	answer := strings.ToLower(strings.TrimSpace(res))[0] == 'y'
	return answer, nil
}
<commit_msg>Add message about cluster-admin permissions when install fails with forbidden error<commit_after>/*
 * Copyright 2018 The original author or 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 core

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const (
	istioNamespace  = "istio-system"
	istioCrds       = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-crds.yaml"
	istioRelease    = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-main.yaml"
	servingRelease  = "https://storage.googleapis.com/knative-releases/serving/previous/v20180809-6b01d8e/release-no-mon.yaml"
	eventingRelease = "https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release.yaml"
	stubBusRelease  = "https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release-clusterbus-stub.yaml"
)

type SystemInstallOptions struct {
	NodePort bool
	Force    bool
}

type SystemUninstallOptions struct {
	Istio bool
	Force bool
}

var (
	knativeNamespaces = []string{"knative-eventing", "knative-serving", "knative-build"}
	allNameSpaces     = append(knativeNamespaces, istioNamespace)
)

func (kc *kubectlClient) SystemInstall(options SystemInstallOptions) (bool, error) {

	err := ensureNotTerminating(kc, allNameSpaces, "Please try again later.")
	if err != nil {
		return false, err
	}

	istioStatus, err := getNamespaceStatus(kc, istioNamespace)
	if istioStatus == "'NotFound'" {
		fmt.Print("Installing Istio components\n")
		err = applyResources(kc, istioCrds)
		if err != nil {
			return false, err
		}
		time.Sleep(5 * time.Second) // wait for them to get created
		istioYaml, err := loadRelease(istioRelease)
		if err != nil {
			return false, err
		}
		if options.NodePort {
			istioYaml = bytes.Replace(istioYaml, []byte("LoadBalancer"), []byte("NodePort"), -1)
		}
		fmt.Printf("Applying resources defined in: %s\n", istioRelease)
		istioLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &istioYaml)
		if err != nil {
			fmt.Printf("%s\n", istioLog)
			if strings.Contains(istioLog, "forbidden") {
				fmt.Print("It looks like you don't have cluster-admin permissions.\n\n")
			}
			return false, err
		}

		fmt.Print("Istio for riff installed\n\n")
	} else {
		if !options.Force {
			answer, err := confirm("Istio is already installed, do you want to install the Knative components for riff?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
	}

	err = waitForIstioComponents(kc)
	if err != nil {
		return false, err
	}

	fmt.Print("Installing Knative components\n")

	servingYaml, err := loadRelease(servingRelease)
	if err != nil {
		return false, err
	}
	if options.NodePort {
		servingYaml = bytes.Replace(servingYaml, []byte("LoadBalancer"), []byte("NodePort"), -1)
	}
	fmt.Printf("Applying resources defined in: %s\n", servingRelease)
	servingLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &servingYaml)
	if err != nil {
		fmt.Printf("%s\n", servingLog)
		return false, err
	}

	applyResources(kc, eventingRelease)

	applyResources(kc, stubBusRelease)

	fmt.Print("Knative for riff installed\n\n")
	return true, nil
}

func (kc *kubectlClient) SystemUninstall(options SystemUninstallOptions) (bool, error) {

	err := ensureNotTerminating(kc, allNameSpaces, "This would indicate that the system was already uninstalled.")
	if err != nil {
		return false, err
	}
	knativeNsCount, err := checkNamespacesExists(kc, knativeNamespaces)
	istioNsCount, err := checkNamespacesExists(kc, []string{istioNamespace})
	if err != nil {
		return false, err
	}
	if knativeNsCount == 0 {
		fmt.Print("No Knative components for riff found\n")
	} else {
		if !options.Force {
			answer, err := confirm("Are you sure you want to uninstall the riff system?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
		fmt.Print("Removing Knative for riff components\n")
		err = deleteCrds(kc, "knative.dev")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "knative-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "build-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "eventing-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "clusterbus-controller-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrole", "knative-")
		if err != nil {
			return false, err
		}
		err = deleteNamespaces(kc, knativeNamespaces)
		if err != nil {
			return false, err
		}
	}
	if istioNsCount == 0 {
		fmt.Print("No Istio components found\n")
	} else {
		if !options.Istio {
			if options.Force {
				return true, nil
			}
			answer, err := confirm("Do you also want to uninstall Istio components?")
			if err != nil {
				return false, err
			}
			if !answer {
				return false, nil
			}
		}
		fmt.Print("Removing Istio components\n")
		err = deleteCrds(kc, "istio.io")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrolebinding", "istio-")
		if err != nil {
			return false, err
		}
		err = deleteClusterResources(kc, "clusterrole", "istio-")
		if err != nil {
			return false, err
		}
		err = deleteNamespaces(kc, []string{istioNamespace})
		if err != nil {
			return false, err
		}
	}
	return true, nil
}

func resolveReleaseURLs(filename string) (url.URL, error) {
	u, err := url.Parse(filename)
	if err != nil {
		return url.URL{}, err
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return *u, nil
	}
	return *u, fmt.Errorf("filename must be file, http or https, got %s", u.Scheme)
}

func loadRelease(release string) ([]byte, error) {
	releaseUrl, err := resolveReleaseURLs(release)
	if err != nil {
		return nil, err
	}
	resp, err := http.Get(releaseUrl.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

func waitForIstioComponents(kc *kubectlClient) error {
	fmt.Print("Waiting for the Istio components to start ")
	for i := 0; i < 36; i++ {
		fmt.Print(".")
		pods := kc.kubeClient.CoreV1().Pods(istioNamespace)
		podList, err := pods.List(metav1.ListOptions{})
		if err != nil {
			return err
		}
		waitLonger := false
		for _, pod := range podList.Items {
			if !strings.HasPrefix(pod.Name, "istio-") {
				continue
			}
			if pod.Status.Phase != "Running" && pod.Status.Phase != "Succeeded" {
				waitLonger = true
				break
			} else {
				if pod.Status.Phase == "Running" {
					containers := pod.Status.ContainerStatuses
					for _, cont := range containers {
						if !cont.Ready {
							waitLonger = true
							break
						}
					}
				}
			}
		}
		if !waitLonger {
			fmt.Print(" all components are 'Running'\n\n")
			return nil
		}
		time.Sleep(10 * time.Second) // wait for them to start
	}
	return errors.New("the Istio components did not start in time")
}

func applyResources(kc *kubectlClient, release string) error {
	releaseUrl, err := resolveReleaseURLs(release)
	if err != nil {
		return err
	}
	fmt.Printf("Applying resources defined in: %s\n", releaseUrl.String())
	releaseLog, err := kc.kubeCtl.Exec([]string{"apply", "-f", releaseUrl.String()})
	if err != nil {
		fmt.Printf("%s", releaseLog)
	}
	return nil
}

func deleteNamespaces(kc *kubectlClient, namespaces []string) error {
	for _, namespace := range namespaces {
		fmt.Printf("Deleting resources defined in: %s\n", namespace)
		deleteLog, err := kc.kubeCtl.Exec([]string{"delete", "namespace", namespace})
		if err != nil {
			fmt.Printf("%s", deleteLog)
		}
	}
	return nil
}

func deleteClusterResources(kc *kubectlClient, resourceType string, prefix string) error {
	fmt.Printf("Deleting %ss prefixed with %s\n", resourceType, prefix)
	resourceList, err := kc.kubeCtl.Exec([]string{"get", resourceType, "-ocustom-columns=name:metadata.name"})
	if err != nil {
		return err
	}
	resource := strings.Split(string(resourceList), "\n")
	var resourcesToDelete []string
	for _, resource := range resource {
		if strings.HasPrefix(resource, prefix) {
			resourcesToDelete = append(resourcesToDelete, resource)
		}
	}
	if len(resourcesToDelete) > 0 {
		resourceLog, err := kc.kubeCtl.Exec(append([]string{"delete", resourceType}, resourcesToDelete...))
		if err != nil {
			fmt.Printf("%s", resourceLog)
			return err
		}
	}
	return nil
}

func deleteCrds(kc *kubectlClient, suffix string) error {
	fmt.Printf("Deleting CRDs for %s\n", suffix)
	crdList, err := kc.kubeCtl.Exec([]string{"get", "customresourcedefinitions", "-ocustom-columns=name:metadata.name"})
	if err != nil {
		return err
	}
	crds := strings.Split(string(crdList), "\n")
	var crdsToDelete []string
	for _, crd := range crds {
		if strings.HasSuffix(crd, suffix) {
			crdsToDelete = append(crdsToDelete, crd)
		}
	}
	if len(crdsToDelete) > 0 {
		crdLog, err := kc.kubeCtl.Exec(append([]string{"delete", "customresourcedefinition"}, crdsToDelete...))
		if err != nil {
			fmt.Printf("%s", crdLog)
			return err
		}
	}
	return nil
}

func checkNamespacesExists(kc *kubectlClient, names []string) (int, error) {
	count := 0
	for _, name := range names {
		status, err := getNamespaceStatus(kc, name)
		if err != nil {
			return count, err
		}
		if status != "'NotFound'" {
			count = +1
		}
	}
	return count, nil
}

func ensureNotTerminating(kc *kubectlClient, names []string, message string) error {
	for _, name := range names {
		status, err := getNamespaceStatus(kc, name)
		if err != nil {
			return err
		}
		if status == "'Terminating'" {
			return errors.New(fmt.Sprintf("The %s namespace is currently 'Terminating'. %s", name, message))
		}
	}
	return nil
}

func getNamespaceStatus(kc *kubectlClient, name string) (string, error) {
	nsLog, err := kc.kubeCtl.Exec([]string{"get", "namespace", name, "-o", "jsonpath='{.status.phase}'"})
	if err != nil {
		if strings.Contains(nsLog, "NotFound") {
			return "'NotFound'", nil
		}
		return "", err
	}
	return nsLog, nil
}

func confirm(s string) (bool, error) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Printf("%s [y/N]: ", s)
	res, err := reader.ReadString('\n')
	if err != nil {
		return false, err
	}
	if len(res) < 2 {
		return false, nil
	}
	answer := strings.ToLower(strings.TrimSpace(res))[0] == 'y'
	return answer, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 sources

import (
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
	"github.com/google/go-cmp/cmp"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestPodTemplate(t *testing.T) {
	tests := []struct {
		description string
		initial     *latest.ClusterDetails
		artifact    *latest.KanikoArtifact
		args        []string
		expected    *v1.Pod
	}{
		{
			description: "basic pod",
			initial:     &latest.ClusterDetails{},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{
						{
							Name:  "kaniko",
							Image: "kaniko-latest",
							Env: []v1.EnvVar{{
								Name:  "GOOGLE_APPLICATION_CREDENTIALS",
								Value: "/secret/kaniko-secret",
							}},
							VolumeMounts: []v1.VolumeMount{{
								Name:      "kaniko-secret",
								MountPath: "/secret",
							}},
							ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
						},
					},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						}},
					},
				},
			},
		},
		{
			description: "with docker config",
			initial: &latest.ClusterDetails{
				PullSecretName: "pull-secret",
				DockerConfig: &latest.DockerConfig{
					SecretName: "docker-cfg",
					Path:       "/kaniko/.docker",
				},
			},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "kaniko-secret",
								MountPath: "/secret",
							},
							{
								Name:      "docker-cfg",
								MountPath: "/kaniko/.docker",
							},
						},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
					}},
					Volumes: []v1.Volume{
						{
							Name: "kaniko-secret",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "pull-secret",
								},
							},
						},
						{
							Name: "docker-cfg",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "docker-cfg",
								},
							},
						},
					},
				},
			},
		},
		{
			description: "with resource constraints",
			initial: &latest.ClusterDetails{
				Resources: &latest.ResourceRequirements{
					Requests: &latest.ResourceRequirement{
						CPU:    "0.5",
						Memory: "1000",
					},
					Limits: &latest.ResourceRequirement{
						CPU:    "1.0",
						Memory: "2000",
					},
				},
			},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{{
							Name:      "kaniko-secret",
							MountPath: "/secret",
						}},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
						Resources: createResourceRequirements(
							resource.MustParse("1.0"),
							resource.MustParse("2000"),
							resource.MustParse("0.5"),
							resource.MustParse("1000")),
					}},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						},
					}},
				},
			},
		},
		{
			description: "with cache",
			initial:     &latest.ClusterDetails{},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
				Cache: &latest.KanikoCache{
					HostPath: "/cache-path",
				},
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{{
							Name:      "kaniko-secret",
							MountPath: "/secret",
						}, {
							Name:      "kaniko-cache",
							MountPath: "/cache",
						}},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
					}},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						},
					}, {
						Name: "kaniko-cache",
						VolumeSource: v1.VolumeSource{
							HostPath: &v1.HostPathVolumeSource{
								Path: "/cache-path",
							},
						},
					}},
				},
			},
		},
	}

	opt := cmp.Comparer(func(x, y resource.Quantity) bool {
		return x.String() == y.String()
	})

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			actual := podTemplate(test.initial, test.artifact, test.args)

			t.CheckDeepEqual(test.expected, actual, opt)
		})
	}
}

func TestSetProxy(t *testing.T) {
	tests := []struct {
		description    string
		clusterDetails *latest.ClusterDetails
		env            []v1.EnvVar
		expectedArgs   []v1.EnvVar
	}{
		{
			description:    "no http and https proxy",
			clusterDetails: &latest.ClusterDetails{},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{},
		}, {
			description:    "set http proxy",

			clusterDetails: &latest.ClusterDetails{HTTPProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTP_PROXY", Value: "proxy.com"}},
		}, {
			description:    "set https proxy",
			clusterDetails: &latest.ClusterDetails{HTTPSProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTP_PROXY", Value: "proxy.com"}},
		}, {
			description:    "set http and https proxy",
			clusterDetails: &latest.ClusterDetails{HTTPProxy: "proxy.com", HTTPSProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTP_PROXY", Value: "proxy.com"}, {Name: "HTTPS_PROXY", Value: "proxy.com"}},
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			actual := setProxy(test.clusterDetails, test.env)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expectedArgs, actual)
		})
	}
}

func createResourceRequirements(cpuLimit resource.Quantity, memoryLimit resource.Quantity, cpuRequest resource.Quantity, memoryRequest resource.Quantity) v1.ResourceRequirements {
	return v1.ResourceRequirements{
		Limits: v1.ResourceList{
			v1.ResourceCPU:    cpuLimit,
			v1.ResourceMemory: memoryLimit,
		},
		Requests: v1.ResourceList{
			v1.ResourceCPU:    cpuRequest,
			v1.ResourceMemory: memoryRequest,
		},
	}
}
<commit_msg>fixing<commit_after>/*
Copyright 2019 The Skaffold 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 sources

import (
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
	"github.com/google/go-cmp/cmp"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestPodTemplate(t *testing.T) {
	tests := []struct {
		description string
		initial     *latest.ClusterDetails
		artifact    *latest.KanikoArtifact
		args        []string
		expected    *v1.Pod
	}{
		{
			description: "basic pod",
			initial:     &latest.ClusterDetails{},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{
						{
							Name:  "kaniko",
							Image: "kaniko-latest",
							Env: []v1.EnvVar{{
								Name:  "GOOGLE_APPLICATION_CREDENTIALS",
								Value: "/secret/kaniko-secret",
							}},
							VolumeMounts: []v1.VolumeMount{{
								Name:      "kaniko-secret",
								MountPath: "/secret",
							}},
							ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
						},
					},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						}},
					},
				},
			},
		},
		{
			description: "with docker config",
			initial: &latest.ClusterDetails{
				PullSecretName: "pull-secret",
				DockerConfig: &latest.DockerConfig{
					SecretName: "docker-cfg",
					Path:       "/kaniko/.docker",
				},
			},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "kaniko-secret",
								MountPath: "/secret",
							},
							{
								Name:      "docker-cfg",
								MountPath: "/kaniko/.docker",
							},
						},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
					}},
					Volumes: []v1.Volume{
						{
							Name: "kaniko-secret",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "pull-secret",
								},
							},
						},
						{
							Name: "docker-cfg",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "docker-cfg",
								},
							},
						},
					},
				},
			},
		},
		{
			description: "with resource constraints",
			initial: &latest.ClusterDetails{
				Resources: &latest.ResourceRequirements{
					Requests: &latest.ResourceRequirement{
						CPU:    "0.5",
						Memory: "1000",
					},
					Limits: &latest.ResourceRequirement{
						CPU:    "1.0",
						Memory: "2000",
					},
				},
			},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{{
							Name:      "kaniko-secret",
							MountPath: "/secret",
						}},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
						Resources: createResourceRequirements(
							resource.MustParse("1.0"),
							resource.MustParse("2000"),
							resource.MustParse("0.5"),
							resource.MustParse("1000")),
					}},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						},
					}},
				},
			},
		},
		{
			description: "with cache",
			initial:     &latest.ClusterDetails{},
			artifact: &latest.KanikoArtifact{
				Image: "kaniko-latest",
				Cache: &latest.KanikoCache{
					HostPath: "/cache-path",
				},
			},
			expected: &v1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					GenerateName: "kaniko-",
					Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
				},
				Spec: v1.PodSpec{
					RestartPolicy: "Never",
					Containers: []v1.Container{{
						Name:  "kaniko",
						Image: "kaniko-latest",
						Env: []v1.EnvVar{{
							Name:  "GOOGLE_APPLICATION_CREDENTIALS",
							Value: "/secret/kaniko-secret",
						}},
						VolumeMounts: []v1.VolumeMount{{
							Name:      "kaniko-secret",
							MountPath: "/secret",
						}, {
							Name:      "kaniko-cache",
							MountPath: "/cache",
						}},
						ImagePullPolicy: v1.PullPolicy("IfNotPresent"),
					}},
					Volumes: []v1.Volume{{
						Name: "kaniko-secret",
						VolumeSource: v1.VolumeSource{
							Secret: &v1.SecretVolumeSource{
								SecretName: "",
							},
						},
					}, {
						Name: "kaniko-cache",
						VolumeSource: v1.VolumeSource{
							HostPath: &v1.HostPathVolumeSource{
								Path: "/cache-path",
							},
						},
					}},
				},
			},
		},
	}

	opt := cmp.Comparer(func(x, y resource.Quantity) bool {
		return x.String() == y.String()
	})

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			actual := podTemplate(test.initial, test.artifact, test.args)

			t.CheckDeepEqual(test.expected, actual, opt)
		})
	}
}

func TestSetProxy(t *testing.T) {
	tests := []struct {
		description    string
		clusterDetails *latest.ClusterDetails
		env            []v1.EnvVar
		expectedArgs   []v1.EnvVar
	}{
		{
			description:    "no http and https proxy",
			clusterDetails: &latest.ClusterDetails{},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{},
		}, {
			description:    "set http proxy",

			clusterDetails: &latest.ClusterDetails{HTTPProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTP_PROXY", Value: "proxy.com"}},
		}, {
			description:    "set https proxy",
			clusterDetails: &latest.ClusterDetails{HTTPSProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTPS_PROXY", Value: "proxy.com"}},
		}, {
			description:    "set http and https proxy",
			clusterDetails: &latest.ClusterDetails{HTTPProxy: "proxy.com", HTTPSProxy: "proxy.com"},
			env:            []v1.EnvVar{},
			expectedArgs:   []v1.EnvVar{{Name: "HTTP_PROXY", Value: "proxy.com"}, {Name: "HTTPS_PROXY", Value: "proxy.com"}},
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			actual := setProxy(test.clusterDetails, test.env)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.expectedArgs, actual)
		})
	}
}

func createResourceRequirements(cpuLimit resource.Quantity, memoryLimit resource.Quantity, cpuRequest resource.Quantity, memoryRequest resource.Quantity) v1.ResourceRequirements {
	return v1.ResourceRequirements{
		Limits: v1.ResourceList{
			v1.ResourceCPU:    cpuLimit,
			v1.ResourceMemory: memoryLimit,
		},
		Requests: v1.ResourceList{
			v1.ResourceCPU:    cpuRequest,
			v1.ResourceMemory: memoryRequest,
		},
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package drain

import (
	"context"
	"fmt"
	"io"
	"math"
	"time"

	corev1 "k8s.io/api/core/v1"
	policyv1beta1 "k8s.io/api/policy/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
)

const (
	// EvictionKind represents the kind of evictions object
	EvictionKind = "Eviction"
	// EvictionSubresource represents the kind of evictions object as pod's subresource
	EvictionSubresource = "pods/eviction"
	podSkipMsgTemplate  = "pod %q has DeletionTimestamp older than %v seconds, skipping\n"
)

// Helper contains the parameters to control the behaviour of drainer
type Helper struct {
	Ctx                 context.Context
	Client              kubernetes.Interface
	Force               bool
	GracePeriodSeconds  int
	IgnoreAllDaemonSets bool
	Timeout             time.Duration
	DeleteLocalData     bool
	Selector            string
	PodSelector         string

	// DisableEviction forces drain to use delete rather than evict
	DisableEviction bool

	// SkipWaitForDeleteTimeoutSeconds ignores pods that have a
	// DeletionTimeStamp > N seconds. It's up to the user to decide when this
	// option is appropriate; examples include the Node is unready and the pods
	// won't drain otherwise
	SkipWaitForDeleteTimeoutSeconds int

	Out    io.Writer
	ErrOut io.Writer

	DryRunStrategy cmdutil.DryRunStrategy
	DryRunVerifier *resource.DryRunVerifier

	// OnPodDeletedOrEvicted is called when a pod is evicted/deleted; for printing progress output
	OnPodDeletedOrEvicted func(pod *corev1.Pod, usingEviction bool)
}

type waitForDeleteParams struct {
	ctx                             context.Context
	pods                            []corev1.Pod
	interval                        time.Duration
	timeout                         time.Duration
	usingEviction                   bool
	getPodFn                        func(string, string) (*corev1.Pod, error)
	onDoneFn                        func(pod *corev1.Pod, usingEviction bool)
	globalTimeout                   time.Duration
	skipWaitForDeleteTimeoutSeconds int
	out                             io.Writer
}

// CheckEvictionSupport uses Discovery API to find out if the server support
// eviction subresource If support, it will return its groupVersion; Otherwise,
// it will return an empty string
func CheckEvictionSupport(clientset kubernetes.Interface) (string, error) {
	discoveryClient := clientset.Discovery()
	groupList, err := discoveryClient.ServerGroups()
	if err != nil {
		return "", err
	}
	foundPolicyGroup := false
	var policyGroupVersion string
	for _, group := range groupList.Groups {
		if group.Name == "policy" {
			foundPolicyGroup = true
			policyGroupVersion = group.PreferredVersion.GroupVersion
			break
		}
	}
	if !foundPolicyGroup {
		return "", nil
	}
	resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1")
	if err != nil {
		return "", err
	}
	for _, resource := range resourceList.APIResources {
		if resource.Name == EvictionSubresource && resource.Kind == EvictionKind {
			return policyGroupVersion, nil
		}
	}
	return "", nil
}

func (d *Helper) makeDeleteOptions() metav1.DeleteOptions {
	deleteOptions := metav1.DeleteOptions{}
	if d.GracePeriodSeconds >= 0 {
		gracePeriodSeconds := int64(d.GracePeriodSeconds)
		deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
	}
	if d.DryRunStrategy == cmdutil.DryRunServer {
		deleteOptions.DryRun = []string{metav1.DryRunAll}
	}
	return deleteOptions
}

// DeletePod will delete the given pod, or return an error if it couldn't
func (d *Helper) DeletePod(pod corev1.Pod) error {
	if d.DryRunStrategy == cmdutil.DryRunServer {
		if err := d.DryRunVerifier.HasSupport(pod.GroupVersionKind()); err != nil {
			return err
		}
	}
	return d.Client.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, d.makeDeleteOptions())
}

// EvictPod will evict the give pod, or return an error if it couldn't
func (d *Helper) EvictPod(pod corev1.Pod, policyGroupVersion string) error {
	if d.DryRunStrategy == cmdutil.DryRunServer {
		if err := d.DryRunVerifier.HasSupport(pod.GroupVersionKind()); err != nil {
			return err
		}
	}

	delOpts := d.makeDeleteOptions()
	eviction := &policyv1beta1.Eviction{
		TypeMeta: metav1.TypeMeta{
			APIVersion: policyGroupVersion,
			Kind:       EvictionKind,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      pod.Name,
			Namespace: pod.Namespace,
		},
		DeleteOptions: &delOpts,
	}

	// Remember to change change the URL manipulation func when Eviction's version change
	return d.Client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction)
}

// GetPodsForDeletion receives resource info for a node, and returns those pods as PodDeleteList,
// or error if it cannot list pods. All pods that are ready to be deleted can be obtained with .Pods(),
// and string with all warning can be obtained with .Warnings(), and .Errors() for all errors that
// occurred during deletion.
func (d *Helper) GetPodsForDeletion(nodeName string) (*podDeleteList, []error) {
	labelSelector, err := labels.Parse(d.PodSelector)
	if err != nil {
		return nil, []error{err}
	}

	podList, err := d.Client.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
		LabelSelector: labelSelector.String(),
		FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}).String()})
	if err != nil {
		return nil, []error{err}
	}

	pods := []podDelete{}

	for _, pod := range podList.Items {
		var status podDeleteStatus
		for _, filter := range d.makeFilters() {
			status = filter(pod)
			if !status.delete {
				// short-circuit as soon as pod is filtered out
				// at that point, there is no reason to run pod
				// through any additional filters
				break
			}
		}
		// Add the pod to podDeleteList no matter what podDeleteStatus is,
		// those pods whose podDeleteStatus is false like DaemonSet will
		// be catched by list.errors()
		pods = append(pods, podDelete{
			pod:    pod,
			status: status,
		})
	}

	list := &podDeleteList{items: pods}

	if errs := list.errors(); len(errs) > 0 {
		return list, errs
	}

	return list, nil
}

// DeleteOrEvictPods deletes or evicts the pods on the api server
func (d *Helper) DeleteOrEvictPods(pods []corev1.Pod) error {
	if len(pods) == 0 {
		return nil
	}

	// TODO(justinsb): unnecessary?
	getPodFn := func(namespace, name string) (*corev1.Pod, error) {
		return d.Client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	}

	if !d.DisableEviction {
		policyGroupVersion, err := CheckEvictionSupport(d.Client)
		if err != nil {
			return err
		}

		if len(policyGroupVersion) > 0 {
			return d.evictPods(pods, policyGroupVersion, getPodFn)
		}
	}

	return d.deletePods(pods, getPodFn)
}

func (d *Helper) evictPods(pods []corev1.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
	returnCh := make(chan error, 1)
	// 0 timeout means infinite, we use MaxInt64 to represent it.
	var globalTimeout time.Duration
	if d.Timeout == 0 {
		globalTimeout = time.Duration(math.MaxInt64)
	} else {
		globalTimeout = d.Timeout
	}
	ctx, cancel := context.WithTimeout(d.getContext(), globalTimeout)
	defer cancel()
	for _, pod := range pods {
		go func(pod corev1.Pod, returnCh chan error) {
			for {
				switch d.DryRunStrategy {
				case cmdutil.DryRunServer:
					fmt.Fprintf(d.Out, "evicting pod %s/%s (server dry run)\n", pod.Namespace, pod.Name)
				default:
					fmt.Fprintf(d.Out, "evicting pod %s/%s\n", pod.Namespace, pod.Name)
				}
				select {
				case <-ctx.Done():
					// return here or we'll leak a goroutine.
					returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: global timeout reached: %v", pod.Name, pod.Namespace, globalTimeout)
					return
				default:
				}
				err := d.EvictPod(pod, policyGroupVersion)
				if err == nil {
					break
				} else if apierrors.IsNotFound(err) {
					returnCh <- nil
					return
				} else if apierrors.IsTooManyRequests(err) {
					fmt.Fprintf(d.ErrOut, "error when evicting pods/%q -n %q (will retry after 5s): %v\n", pod.Name, pod.Namespace, err)
					time.Sleep(5 * time.Second)
				} else {
					returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: %v", pod.Name, pod.Namespace, err)
					return
				}
			}
			if d.DryRunStrategy == cmdutil.DryRunServer {
				returnCh <- nil
				return
			}
			params := waitForDeleteParams{
				ctx:                             ctx,
				pods:                            []corev1.Pod{pod},
				interval:                        1 * time.Second,
				timeout:                         time.Duration(math.MaxInt64),
				usingEviction:                   true,
				getPodFn:                        getPodFn,
				onDoneFn:                        d.OnPodDeletedOrEvicted,
				globalTimeout:                   globalTimeout,
				skipWaitForDeleteTimeoutSeconds: d.SkipWaitForDeleteTimeoutSeconds,
				out:                             d.Out,
			}
			_, err := waitForDelete(params)
			if err == nil {
				returnCh <- nil
			} else {
				returnCh <- fmt.Errorf("error when waiting for pod %q terminating: %v", pod.Name, err)
			}
		}(pod, returnCh)
	}

	doneCount := 0
	var errors []error

	numPods := len(pods)
	for doneCount < numPods {
		select {
		case err := <-returnCh:
			doneCount++
			if err != nil {
				errors = append(errors, err)
			}
		default:
		}
	}

	return utilerrors.NewAggregate(errors)
}

func (d *Helper) deletePods(pods []corev1.Pod, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
	// 0 timeout means infinite, we use MaxInt64 to represent it.
	var globalTimeout time.Duration
	if d.Timeout == 0 {
		globalTimeout = time.Duration(math.MaxInt64)
	} else {
		globalTimeout = d.Timeout
	}
	for _, pod := range pods {
		err := d.DeletePod(pod)
		if err != nil && !apierrors.IsNotFound(err) {
			return err
		}
	}
	ctx := d.getContext()
	params := waitForDeleteParams{
		ctx:                             ctx,
		pods:                            pods,
		interval:                        1 * time.Second,
		timeout:                         globalTimeout,
		usingEviction:                   false,
		getPodFn:                        getPodFn,
		onDoneFn:                        d.OnPodDeletedOrEvicted,
		globalTimeout:                   globalTimeout,
		skipWaitForDeleteTimeoutSeconds: d.SkipWaitForDeleteTimeoutSeconds,
		out:                             d.Out,
	}
	_, err := waitForDelete(params)
	return err
}

func waitForDelete(params waitForDeleteParams) ([]corev1.Pod, error) {
	pods := params.pods
	err := wait.PollImmediate(params.interval, params.timeout, func() (bool, error) {
		pendingPods := []corev1.Pod{}
		for i, pod := range pods {
			p, err := params.getPodFn(pod.Namespace, pod.Name)
			if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) {
				if params.onDoneFn != nil {
					params.onDoneFn(&pod, params.usingEviction)
				}
				continue
			} else if err != nil {
				return false, err
			} else {
				if shouldSkipPod(*p, params.skipWaitForDeleteTimeoutSeconds) {
					fmt.Fprintf(params.out, podSkipMsgTemplate, pod.Name, params.skipWaitForDeleteTimeoutSeconds)
					continue
				}
				pendingPods = append(pendingPods, pods[i])
			}
		}
		pods = pendingPods
		if len(pendingPods) > 0 {
			select {
			case <-params.ctx.Done():
				return false, fmt.Errorf("global timeout reached: %v", params.globalTimeout)
			default:
				return false, nil
			}
		}
		return true, nil
	})
	return pods, err
}

// Since Helper does not have a constructor, we can't enforce Helper.Ctx != nil
// Multiple public methods prevent us from initializing the context in a single
// place as well.
func (d *Helper) getContext() context.Context {
	if d.Ctx != nil {
		return d.Ctx
	}
	return context.Background()
}
<commit_msg>drain: eviction creates in a deleting namespace will throw a forbidden error<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package drain

import (
	"context"
	"fmt"
	"io"
	"math"
	"time"

	corev1 "k8s.io/api/core/v1"
	policyv1beta1 "k8s.io/api/policy/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
)

const (
	// EvictionKind represents the kind of evictions object
	EvictionKind = "Eviction"
	// EvictionSubresource represents the kind of evictions object as pod's subresource
	EvictionSubresource = "pods/eviction"
	podSkipMsgTemplate  = "pod %q has DeletionTimestamp older than %v seconds, skipping\n"
)

// Helper contains the parameters to control the behaviour of drainer
type Helper struct {
	Ctx                 context.Context
	Client              kubernetes.Interface
	Force               bool
	GracePeriodSeconds  int
	IgnoreAllDaemonSets bool
	Timeout             time.Duration
	DeleteLocalData     bool
	Selector            string
	PodSelector         string

	// DisableEviction forces drain to use delete rather than evict
	DisableEviction bool

	// SkipWaitForDeleteTimeoutSeconds ignores pods that have a
	// DeletionTimeStamp > N seconds. It's up to the user to decide when this
	// option is appropriate; examples include the Node is unready and the pods
	// won't drain otherwise
	SkipWaitForDeleteTimeoutSeconds int

	Out    io.Writer
	ErrOut io.Writer

	DryRunStrategy cmdutil.DryRunStrategy
	DryRunVerifier *resource.DryRunVerifier

	// OnPodDeletedOrEvicted is called when a pod is evicted/deleted; for printing progress output
	OnPodDeletedOrEvicted func(pod *corev1.Pod, usingEviction bool)
}

type waitForDeleteParams struct {
	ctx                             context.Context
	pods                            []corev1.Pod
	interval                        time.Duration
	timeout                         time.Duration
	usingEviction                   bool
	getPodFn                        func(string, string) (*corev1.Pod, error)
	onDoneFn                        func(pod *corev1.Pod, usingEviction bool)
	globalTimeout                   time.Duration
	skipWaitForDeleteTimeoutSeconds int
	out                             io.Writer
}

// CheckEvictionSupport uses Discovery API to find out if the server support
// eviction subresource If support, it will return its groupVersion; Otherwise,
// it will return an empty string
func CheckEvictionSupport(clientset kubernetes.Interface) (string, error) {
	discoveryClient := clientset.Discovery()
	groupList, err := discoveryClient.ServerGroups()
	if err != nil {
		return "", err
	}
	foundPolicyGroup := false
	var policyGroupVersion string
	for _, group := range groupList.Groups {
		if group.Name == "policy" {
			foundPolicyGroup = true
			policyGroupVersion = group.PreferredVersion.GroupVersion
			break
		}
	}
	if !foundPolicyGroup {
		return "", nil
	}
	resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1")
	if err != nil {
		return "", err
	}
	for _, resource := range resourceList.APIResources {
		if resource.Name == EvictionSubresource && resource.Kind == EvictionKind {
			return policyGroupVersion, nil
		}
	}
	return "", nil
}

func (d *Helper) makeDeleteOptions() metav1.DeleteOptions {
	deleteOptions := metav1.DeleteOptions{}
	if d.GracePeriodSeconds >= 0 {
		gracePeriodSeconds := int64(d.GracePeriodSeconds)
		deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
	}
	if d.DryRunStrategy == cmdutil.DryRunServer {
		deleteOptions.DryRun = []string{metav1.DryRunAll}
	}
	return deleteOptions
}

// DeletePod will delete the given pod, or return an error if it couldn't
func (d *Helper) DeletePod(pod corev1.Pod) error {
	if d.DryRunStrategy == cmdutil.DryRunServer {
		if err := d.DryRunVerifier.HasSupport(pod.GroupVersionKind()); err != nil {
			return err
		}
	}
	return d.Client.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, d.makeDeleteOptions())
}

// EvictPod will evict the give pod, or return an error if it couldn't
func (d *Helper) EvictPod(pod corev1.Pod, policyGroupVersion string) error {
	if d.DryRunStrategy == cmdutil.DryRunServer {
		if err := d.DryRunVerifier.HasSupport(pod.GroupVersionKind()); err != nil {
			return err
		}
	}

	delOpts := d.makeDeleteOptions()
	eviction := &policyv1beta1.Eviction{
		TypeMeta: metav1.TypeMeta{
			APIVersion: policyGroupVersion,
			Kind:       EvictionKind,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      pod.Name,
			Namespace: pod.Namespace,
		},
		DeleteOptions: &delOpts,
	}

	// Remember to change change the URL manipulation func when Eviction's version change
	return d.Client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction)
}

// GetPodsForDeletion receives resource info for a node, and returns those pods as PodDeleteList,
// or error if it cannot list pods. All pods that are ready to be deleted can be obtained with .Pods(),
// and string with all warning can be obtained with .Warnings(), and .Errors() for all errors that
// occurred during deletion.
func (d *Helper) GetPodsForDeletion(nodeName string) (*podDeleteList, []error) {
	labelSelector, err := labels.Parse(d.PodSelector)
	if err != nil {
		return nil, []error{err}
	}

	podList, err := d.Client.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{
		LabelSelector: labelSelector.String(),
		FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}).String()})
	if err != nil {
		return nil, []error{err}
	}

	pods := []podDelete{}

	for _, pod := range podList.Items {
		var status podDeleteStatus
		for _, filter := range d.makeFilters() {
			status = filter(pod)
			if !status.delete {
				// short-circuit as soon as pod is filtered out
				// at that point, there is no reason to run pod
				// through any additional filters
				break
			}
		}
		// Add the pod to podDeleteList no matter what podDeleteStatus is,
		// those pods whose podDeleteStatus is false like DaemonSet will
		// be catched by list.errors()
		pods = append(pods, podDelete{
			pod:    pod,
			status: status,
		})
	}

	list := &podDeleteList{items: pods}

	if errs := list.errors(); len(errs) > 0 {
		return list, errs
	}

	return list, nil
}

// DeleteOrEvictPods deletes or evicts the pods on the api server
func (d *Helper) DeleteOrEvictPods(pods []corev1.Pod) error {
	if len(pods) == 0 {
		return nil
	}

	// TODO(justinsb): unnecessary?
	getPodFn := func(namespace, name string) (*corev1.Pod, error) {
		return d.Client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	}

	if !d.DisableEviction {
		policyGroupVersion, err := CheckEvictionSupport(d.Client)
		if err != nil {
			return err
		}

		if len(policyGroupVersion) > 0 {
			return d.evictPods(pods, policyGroupVersion, getPodFn)
		}
	}

	return d.deletePods(pods, getPodFn)
}

func (d *Helper) evictPods(pods []corev1.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
	returnCh := make(chan error, 1)
	// 0 timeout means infinite, we use MaxInt64 to represent it.
	var globalTimeout time.Duration
	if d.Timeout == 0 {
		globalTimeout = time.Duration(math.MaxInt64)
	} else {
		globalTimeout = d.Timeout
	}
	ctx, cancel := context.WithTimeout(d.getContext(), globalTimeout)
	defer cancel()
	for _, pod := range pods {
		go func(pod corev1.Pod, returnCh chan error) {
			for {
				switch d.DryRunStrategy {
				case cmdutil.DryRunServer:
					fmt.Fprintf(d.Out, "evicting pod %s/%s (server dry run)\n", pod.Namespace, pod.Name)
				default:
					fmt.Fprintf(d.Out, "evicting pod %s/%s\n", pod.Namespace, pod.Name)
				}
				select {
				case <-ctx.Done():
					// return here or we'll leak a goroutine.
					returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: global timeout reached: %v", pod.Name, pod.Namespace, globalTimeout)
					return
				default:
				}
				err := d.EvictPod(pod, policyGroupVersion)
				if err == nil {
					break
				} else if apierrors.IsNotFound(err) {
					returnCh <- nil
					return
				} else if apierrors.IsTooManyRequests(err) {
					fmt.Fprintf(d.ErrOut, "error when evicting pods/%q -n %q (will retry after 5s): %v\n", pod.Name, pod.Namespace, err)
					time.Sleep(5 * time.Second)
				} else if apierrors.IsForbidden(err) {
					// an eviction request in a deleting namespace will throw a forbidden error
					break
				} else {
					returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: %v", pod.Name, pod.Namespace, err)
					return
				}
			}
			if d.DryRunStrategy == cmdutil.DryRunServer {
				returnCh <- nil
				return
			}
			params := waitForDeleteParams{
				ctx:                             ctx,
				pods:                            []corev1.Pod{pod},
				interval:                        1 * time.Second,
				timeout:                         time.Duration(math.MaxInt64),
				usingEviction:                   true,
				getPodFn:                        getPodFn,
				onDoneFn:                        d.OnPodDeletedOrEvicted,
				globalTimeout:                   globalTimeout,
				skipWaitForDeleteTimeoutSeconds: d.SkipWaitForDeleteTimeoutSeconds,
				out:                             d.Out,
			}
			_, err := waitForDelete(params)
			if err == nil {
				returnCh <- nil
			} else {
				returnCh <- fmt.Errorf("error when waiting for pod %q terminating: %v", pod.Name, err)
			}
		}(pod, returnCh)
	}

	doneCount := 0
	var errors []error

	numPods := len(pods)
	for doneCount < numPods {
		select {
		case err := <-returnCh:
			doneCount++
			if err != nil {
				errors = append(errors, err)
			}
		default:
		}
	}

	return utilerrors.NewAggregate(errors)
}

func (d *Helper) deletePods(pods []corev1.Pod, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
	// 0 timeout means infinite, we use MaxInt64 to represent it.
	var globalTimeout time.Duration
	if d.Timeout == 0 {
		globalTimeout = time.Duration(math.MaxInt64)
	} else {
		globalTimeout = d.Timeout
	}
	for _, pod := range pods {
		err := d.DeletePod(pod)
		if err != nil && !apierrors.IsNotFound(err) {
			return err
		}
	}
	ctx := d.getContext()
	params := waitForDeleteParams{
		ctx:                             ctx,
		pods:                            pods,
		interval:                        1 * time.Second,
		timeout:                         globalTimeout,
		usingEviction:                   false,
		getPodFn:                        getPodFn,
		onDoneFn:                        d.OnPodDeletedOrEvicted,
		globalTimeout:                   globalTimeout,
		skipWaitForDeleteTimeoutSeconds: d.SkipWaitForDeleteTimeoutSeconds,
		out:                             d.Out,
	}
	_, err := waitForDelete(params)
	return err
}

func waitForDelete(params waitForDeleteParams) ([]corev1.Pod, error) {
	pods := params.pods
	err := wait.PollImmediate(params.interval, params.timeout, func() (bool, error) {
		pendingPods := []corev1.Pod{}
		for i, pod := range pods {
			p, err := params.getPodFn(pod.Namespace, pod.Name)
			if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) {
				if params.onDoneFn != nil {
					params.onDoneFn(&pod, params.usingEviction)
				}
				continue
			} else if err != nil {
				return false, err
			} else {
				if shouldSkipPod(*p, params.skipWaitForDeleteTimeoutSeconds) {
					fmt.Fprintf(params.out, podSkipMsgTemplate, pod.Name, params.skipWaitForDeleteTimeoutSeconds)
					continue
				}
				pendingPods = append(pendingPods, pods[i])
			}
		}
		pods = pendingPods
		if len(pendingPods) > 0 {
			select {
			case <-params.ctx.Done():
				return false, fmt.Errorf("global timeout reached: %v", params.globalTimeout)
			default:
				return false, nil
			}
		}
		return true, nil
	})
	return pods, err
}

// Since Helper does not have a constructor, we can't enforce Helper.Ctx != nil
// Multiple public methods prevent us from initializing the context in a single
// place as well.
func (d *Helper) getContext() context.Context {
	if d.Ctx != nil {
		return d.Ctx
	}
	return context.Background()
}
<|endoftext|>


<commit_before>// Copyright 2015 Cutehacks AS. All rights reserved.
// License can be found in the LICENSE file.

package commands

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/howeyc/gopass"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"qpm.io/common"
	"qpm.io/qpm/core"
	"qpm.io/qpm/vcs"
	"qpm.io/qpm/license"
)

func Prompt(prompt string, def string) chan string {
	replyChannel := make(chan string, 1)

	if def == "" {
		fmt.Printf(prompt + " ")
	} else {
		fmt.Printf(prompt+" [%s] ", def)
	}

	in := bufio.NewReader(os.Stdin)
	answer, _ := in.ReadString('\n')

	answer = strings.TrimSpace(answer)

	if len(answer) > 0 {
		replyChannel <- answer
	} else {
		replyChannel <- def
	}

	return replyChannel
}

func PromptPassword(prompt string) chan string {
	replyChannel := make(chan string, 1)
	fmt.Printf(prompt + " ")
	replyChannel <- string(gopass.GetPasswd())
	return replyChannel
}

func extractReverseDomain(email string) string {
	emailParts := strings.Split(email, "@")
	if len(emailParts) != 2 {
		return ""
	}
	domainParts := strings.Split(emailParts[1], ".")
	for i, j := 0, len(domainParts)-1; i < j; i, j = i+1, j-1 {
		domainParts[i], domainParts[j] = domainParts[j], domainParts[i]
	}
	return strings.Join(domainParts, ".")
}

type InitCommand struct {
	BaseCommand
	Pkg *common.PackageWrapper
}

func NewInitCommand(ctx core.Context) *InitCommand {
	return &InitCommand{
		BaseCommand: BaseCommand{
			Ctx: ctx,
		},
	}
}

func (ic InitCommand) Description() string {
	return "Initializes a new module in the current directory"
}

func (ic *InitCommand) RegisterFlags(flag *flag.FlagSet) {

}

func (ic *InitCommand) Run() error {

	ic.Pkg = &common.PackageWrapper{Package: common.NewPackage()}

	ic.Pkg.Author.Name, _ = vcs.LastCommitAuthorName()
	ic.Pkg.Author.Name, _ = <-Prompt("Your name:", ic.Pkg.Author.Name)

	ic.Pkg.Author.Email, _ = vcs.LastCommitEmail()
	ic.Pkg.Author.Email = <-Prompt("Your email:", ic.Pkg.Author.Email)

	cwd, err := os.Getwd()
	if err != nil {
		ic.Error(err)
		cwd = ""
	} else {
		cwd = filepath.Base(cwd)
	}

	suggestedName := extractReverseDomain(ic.Pkg.Author.Email) + "." + cwd

	ic.Pkg.Name = <-Prompt("Unique package name:", suggestedName)
	ic.Pkg.Version.Label = <-Prompt("Initial version:", ic.Pkg.Version.Label)

	ic.Pkg.Repository.Url, err = vcs.RepositoryURL()
	if err != nil {
		fmt.Println("WARNING: Could not auto-detect repository URL.")
	}

	ic.Pkg.Repository.Url = <-Prompt("Repository:", ic.Pkg.Repository.Url)

	filename, _ := ic.findPriFile()
	if len(filename) == 0 {
		filename = ic.Pkg.PriFile()
	}
	ic.Pkg.PriFilename = <-Prompt("Package .pri file:", filename)

	if err := ic.Pkg.Save(); err != nil {
		ic.Error(err)
		return err
	}

	bootstrap := <-Prompt("Generate boilerplate:", "Y/n")
	if len(bootstrap) == 0 || strings.ToLower(string(bootstrap[0])) == "y" {
		if err := ic.GenerateBoilerplate(); err != nil {
			return err
		}
		ic.license("mit") // FIXME: add support for more licenses
	}
	return nil
}

var (
	modulePri = template.Must(template.New("modulePri").Parse(`
RESOURCES += \
    $$PWD/{{.QrcFile}}
`))
	moduleQrc = template.Must(template.New("moduleQrc").Parse(`
<RCC>
    <qresource prefix="{{.QrcPrefix}}">
        <file>qmldir</file>
    </qresource>
</RCC>
`))
	qmldir = template.Must(template.New("qmldir").Parse(`
module {{.Package.Name}}
`))
)

func (ic InitCommand) GenerateBoilerplate() error {

	module := struct {
		Package   *common.PackageWrapper
		PriFile   string
		QrcFile   string
		QrcPrefix string
	}{
		Package:   ic.Pkg,
		PriFile:   ic.Pkg.PriFile(),
		QrcFile:   ic.Pkg.QrcFile(),
		QrcPrefix: ic.Pkg.QrcPrefix(),
	}

	if err := core.WriteTemplate(module.PriFile, modulePri, module); err != nil {
		return err
	}
	if err := core.WriteTemplate(module.QrcFile, moduleQrc, module); err != nil {
		return err
	}
	if err := core.WriteTemplate("qmldir", qmldir, module); err != nil {
		return err
	}
	return nil
}

func (ic *InitCommand) license(identifier string) error {

	println("LICENSE FETCH")
	info, err := license.GetLicense(identifier, ic.Pkg)

	var file *os.File
	file, err = os.Create(core.LicenseFile)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.WriteString(info.Body)
	if err != nil {
		return err
	}

	return nil
}

func (ic *InitCommand) findPriFile() (string, error) {
	dirname := "." + string(filepath.Separator)

	d, err := os.Open(dirname)
	if err != nil {
		return "", err
	}
	defer d.Close()

	files, err := d.Readdir(-1)
	if err != nil {
		return "", err
	}

	for _, file := range files {
		if file.Mode().IsRegular() {
			if filepath.Ext(file.Name()) == ".pri" {
				return file.Name(), nil
			}
		}
	}

	return "", nil
}<commit_msg>Remove a printf that should not be there.<commit_after>// Copyright 2015 Cutehacks AS. All rights reserved.
// License can be found in the LICENSE file.

package commands

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/howeyc/gopass"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"qpm.io/common"
	"qpm.io/qpm/core"
	"qpm.io/qpm/vcs"
	"qpm.io/qpm/license"
)

func Prompt(prompt string, def string) chan string {
	replyChannel := make(chan string, 1)

	if def == "" {
		fmt.Printf(prompt + " ")
	} else {
		fmt.Printf(prompt+" [%s] ", def)
	}

	in := bufio.NewReader(os.Stdin)
	answer, _ := in.ReadString('\n')

	answer = strings.TrimSpace(answer)

	if len(answer) > 0 {
		replyChannel <- answer
	} else {
		replyChannel <- def
	}

	return replyChannel
}

func PromptPassword(prompt string) chan string {
	replyChannel := make(chan string, 1)
	fmt.Printf(prompt + " ")
	replyChannel <- string(gopass.GetPasswd())
	return replyChannel
}

func extractReverseDomain(email string) string {
	emailParts := strings.Split(email, "@")
	if len(emailParts) != 2 {
		return ""
	}
	domainParts := strings.Split(emailParts[1], ".")
	for i, j := 0, len(domainParts)-1; i < j; i, j = i+1, j-1 {
		domainParts[i], domainParts[j] = domainParts[j], domainParts[i]
	}
	return strings.Join(domainParts, ".")
}

type InitCommand struct {
	BaseCommand
	Pkg *common.PackageWrapper
}

func NewInitCommand(ctx core.Context) *InitCommand {
	return &InitCommand{
		BaseCommand: BaseCommand{
			Ctx: ctx,
		},
	}
}

func (ic InitCommand) Description() string {
	return "Initializes a new module in the current directory"
}

func (ic *InitCommand) RegisterFlags(flag *flag.FlagSet) {

}

func (ic *InitCommand) Run() error {

	ic.Pkg = &common.PackageWrapper{Package: common.NewPackage()}

	ic.Pkg.Author.Name, _ = vcs.LastCommitAuthorName()
	ic.Pkg.Author.Name, _ = <-Prompt("Your name:", ic.Pkg.Author.Name)

	ic.Pkg.Author.Email, _ = vcs.LastCommitEmail()
	ic.Pkg.Author.Email = <-Prompt("Your email:", ic.Pkg.Author.Email)

	cwd, err := os.Getwd()
	if err != nil {
		ic.Error(err)
		cwd = ""
	} else {
		cwd = filepath.Base(cwd)
	}

	suggestedName := extractReverseDomain(ic.Pkg.Author.Email) + "." + cwd

	ic.Pkg.Name = <-Prompt("Unique package name:", suggestedName)
	ic.Pkg.Version.Label = <-Prompt("Initial version:", ic.Pkg.Version.Label)

	ic.Pkg.Repository.Url, err = vcs.RepositoryURL()
	if err != nil {
		fmt.Println("WARNING: Could not auto-detect repository URL.")
	}

	ic.Pkg.Repository.Url = <-Prompt("Repository:", ic.Pkg.Repository.Url)

	filename, _ := ic.findPriFile()
	if len(filename) == 0 {
		filename = ic.Pkg.PriFile()
	}
	ic.Pkg.PriFilename = <-Prompt("Package .pri file:", filename)

	if err := ic.Pkg.Save(); err != nil {
		ic.Error(err)
		return err
	}

	bootstrap := <-Prompt("Generate boilerplate:", "Y/n")
	if len(bootstrap) == 0 || strings.ToLower(string(bootstrap[0])) == "y" {
		if err := ic.GenerateBoilerplate(); err != nil {
			return err
		}
		ic.license("mit") // FIXME: add support for more licenses
	}
	return nil
}

var (
	modulePri = template.Must(template.New("modulePri").Parse(`
RESOURCES += \
    $$PWD/{{.QrcFile}}
`))
	moduleQrc = template.Must(template.New("moduleQrc").Parse(`
<RCC>
    <qresource prefix="{{.QrcPrefix}}">
        <file>qmldir</file>
    </qresource>
</RCC>
`))
	qmldir = template.Must(template.New("qmldir").Parse(`
module {{.Package.Name}}
`))
)

func (ic InitCommand) GenerateBoilerplate() error {

	module := struct {
		Package   *common.PackageWrapper
		PriFile   string
		QrcFile   string
		QrcPrefix string
	}{
		Package:   ic.Pkg,
		PriFile:   ic.Pkg.PriFile(),
		QrcFile:   ic.Pkg.QrcFile(),
		QrcPrefix: ic.Pkg.QrcPrefix(),
	}

	if err := core.WriteTemplate(module.PriFile, modulePri, module); err != nil {
		return err
	}
	if err := core.WriteTemplate(module.QrcFile, moduleQrc, module); err != nil {
		return err
	}
	if err := core.WriteTemplate("qmldir", qmldir, module); err != nil {
		return err
	}
	return nil
}

func (ic *InitCommand) license(identifier string) error {

	info, err := license.GetLicense(identifier, ic.Pkg)

	var file *os.File
	file, err = os.Create(core.LicenseFile)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.WriteString(info.Body)
	if err != nil {
		return err
	}

	return nil
}

func (ic *InitCommand) findPriFile() (string, error) {
	dirname := "." + string(filepath.Separator)

	d, err := os.Open(dirname)
	if err != nil {
		return "", err
	}
	defer d.Close()

	files, err := d.Readdir(-1)
	if err != nil {
		return "", err
	}

	for _, file := range files {
		if file.Mode().IsRegular() {
			if filepath.Ext(file.Name()) == ".pri" {
				return file.Name(), nil
			}
		}
	}

	return "", nil
}<|endoftext|>


<commit_before>package recipes

import (
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
	"reflect"
	"time"

	"github.com/elvtechnology/gocqltable"

	r "github.com/elvtechnology/gocqltable/reflect"
)

type CRUD struct {
	gocqltable.TableInterface
}

func (t CRUD) Insert(row interface{}) error {
	return t.insert(row, nil)
}

func (t CRUD) InsertWithTTL(row interface{}, ttl *time.Time) error {
	return t.insert(row, ttl)
}

func (t CRUD) insert(row interface{}, ttl *time.Time) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	// TODO: Test and remove
	// where := []string{}
	// for _, key := range append(rowKeys, rangeKeys...) {
	// 	where = append(where, key+" = ?")
	// }

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	fields := []string{}
	placeholders := []string{}
	vals := []interface{}{}
	for key, value := range m {
		// Check for empty row- or range keys
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				if value == nil {
					return errors.New(fmt.Sprintf("Inserting row failed due to missing key value (for key %q)", rowKey))
				}
				break
			}
		}
		// Append to insertion slices
		fields = append(fields, strings.ToLower(fmt.Sprintf("%q", key)))
		placeholders = append(placeholders, "?")
		vals = append(vals, value)
	}

	options := ""
	if ttl != nil {
		options = "USING TTL ?"
		vals = append(vals, int(ttl.Sub(time.Now().UTC()).Seconds()+.5))
	}

	err := t.Query(fmt.Sprintf(`INSERT INTO %q.%q (%s) VALUES (%s) %s`, t.Keyspace().Name(), t.Name(), strings.Join(fields, ", "), strings.Join(placeholders, ", "), options), vals...).Exec()
	if err != nil {
		for _, v := range vals {
			log.Printf("%T %v", v, v)
		}
		return err
	}

	return nil

}

func (t CRUD) Get(ids ...interface{}) (interface{}, error) {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return nil, errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	row, err := t.Query(fmt.Sprintf(`SELECT * FROM %q.%q WHERE %s LIMIT 1`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).FetchRow()
	if err != nil {
		return nil, err
	}

	return row, nil

}

func (t CRUD) List(ids ...interface{}) (interface{}, error) {
	return t.Range(ids...).Fetch()
}

func (t CRUD) Update(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	set := []string{}
	vals := []interface{}{}

	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	for key, value := range m {
		isAKey := false
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				isAKey = true
				break
			}
		}
		if !isAKey {
			set = append(set, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
			vals = append(vals, value)
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to update row (%d of the required minimum of %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`UPDATE %q.%q SET %s WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(set, ", "), strings.Join(where, " AND ")), append(vals, ids...)...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Delete(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to delete row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`DELETE FROM %q.%q WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Range(ids ...interface{}) Range {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	rangeObj := Range{
		table: t,
	}

	numAppended := 0
	for idx, key := range append(rowKeys, rangeKeys...) {
		if len(ids) == numAppended {
			break
		}
		rangeObj = rangeObj.EqualTo(key, ids[idx])
		numAppended += 1
	}

	return rangeObj
}

type Range struct {
	table gocqltable.TableInterface

	where     []string
	whereVals []interface{}
	order     string
	limit     *int
	filtering bool
}

func (r Range) LessThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" < ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) LessThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" <= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThan(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" > ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThanOrEqual(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" >= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) EqualTo(rangeKey string, value interface{}) Range {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" = ?")
	r.whereVals = append(r.whereVals, value)
	return r
}

func (r Range) OrderBy(fieldAndDirection string) Range {
	r.order = fieldAndDirection
	return r
}

func (r Range) Limit(l int) Range {
	r.limit = &l
	return r
}

func (r Range) Fetch() (interface{}, error) {

	where := r.where
	whereVals := r.whereVals
	order := r.order
	limit := r.limit
	filtering := r.filtering

	whereString := ""
	if len(where) > 0 {
		whereString = "WHERE " + strings.Join(where, " AND ")
	}

	orderString := ""
	if order != "" {
		orderString = fmt.Sprintf("ORDER BY %v", order)
	}

	limitString := ""
	if limit != nil {
		limitString = "LIMIT " + strconv.Itoa(*limit)
	}

	filteringString := ""
	if filtering {
		filteringString = "ALLOW FILTERING"
	}

	iter := r.table.Query(fmt.Sprintf(`SELECT * FROM %q.%q %s %s %s %s`, r.table.Keyspace().Name(), r.table.Name(), whereString, orderString, limitString, filteringString), whereVals...).Fetch()

	result := reflect.Zero(reflect.SliceOf(reflect.PtrTo(reflect.TypeOf(r.table.Row())))) // Create a zero-value slice of pointers to our model type
	for row := range iter.Range() {
		result = reflect.Append(result, reflect.ValueOf(row)) // Append the rows to our slice
	}

	if err := iter.Close(); err != nil {
		return nil, err
	}

	return result.Interface(), nil

}
<commit_msg>Added `recipes.RangeInterface` to make the Range object exchangeable.<commit_after>package recipes

import (
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
	"reflect"
	"time"

	"github.com/elvtechnology/gocqltable"

	r "github.com/elvtechnology/gocqltable/reflect"
)

type RangeInterface interface {
	LessThan(rangeKey string, value interface{}) RangeInterface
	LessThanOrEqual(rangeKey string, value interface{}) RangeInterface
	MoreThan(rangeKey string, value interface{}) RangeInterface
	MoreThanOrEqual(rangeKey string, value interface{}) RangeInterface
	EqualTo(rangeKey string, value interface{}) RangeInterface
	OrderBy(fieldAndDirection string) RangeInterface
	Limit(l int) RangeInterface
	Fetch() (interface{}, error)
}

type CRUD struct {
	gocqltable.TableInterface
}

func (t CRUD) Insert(row interface{}) error {
	return t.insert(row, nil)
}

func (t CRUD) InsertWithTTL(row interface{}, ttl *time.Time) error {
	return t.insert(row, ttl)
}

func (t CRUD) insert(row interface{}, ttl *time.Time) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	// TODO: Test and remove
	// where := []string{}
	// for _, key := range append(rowKeys, rangeKeys...) {
	// 	where = append(where, key+" = ?")
	// }

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	fields := []string{}
	placeholders := []string{}
	vals := []interface{}{}
	for key, value := range m {
		// Check for empty row- or range keys
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				if value == nil {
					return errors.New(fmt.Sprintf("Inserting row failed due to missing key value (for key %q)", rowKey))
				}
				break
			}
		}
		// Append to insertion slices
		fields = append(fields, strings.ToLower(fmt.Sprintf("%q", key)))
		placeholders = append(placeholders, "?")
		vals = append(vals, value)
	}

	options := ""
	if ttl != nil {
		options = "USING TTL ?"
		vals = append(vals, int(ttl.Sub(time.Now().UTC()).Seconds()+.5))
	}

	err := t.Query(fmt.Sprintf(`INSERT INTO %q.%q (%s) VALUES (%s) %s`, t.Keyspace().Name(), t.Name(), strings.Join(fields, ", "), strings.Join(placeholders, ", "), options), vals...).Exec()
	if err != nil {
		for _, v := range vals {
			log.Printf("%T %v", v, v)
		}
		return err
	}

	return nil

}

func (t CRUD) Get(ids ...interface{}) (interface{}, error) {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return nil, errors.New(fmt.Sprintf("To few key-values to query for row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	row, err := t.Query(fmt.Sprintf(`SELECT * FROM %q.%q WHERE %s LIMIT 1`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).FetchRow()
	if err != nil {
		return nil, err
	}

	return row, nil

}

func (t CRUD) List(ids ...interface{}) (interface{}, error) {
	return t.Range(ids...).Fetch()
}

func (t CRUD) Update(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	set := []string{}
	vals := []interface{}{}

	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	for key, value := range m {
		isAKey := false
		for _, rowKey := range append(rowKeys, rangeKeys...) {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				isAKey = true
				break
			}
		}
		if !isAKey {
			set = append(set, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
			vals = append(vals, value)
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to update row (%d of the required minimum of %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`UPDATE %q.%q SET %s WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(set, ", "), strings.Join(where, " AND ")), append(vals, ids...)...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Delete(row interface{}) error {

	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	where := []string{}
	for _, key := range append(rowKeys, rangeKeys...) {
		where = append(where, strings.ToLower(fmt.Sprintf("%q", key))+" = ?")
	}

	m, ok := r.StructToMap(row)
	if !ok {
		panic("Unable to get map from struct during update")
	}

	ids := []interface{}{}
	for _, rowKey := range append(rowKeys, rangeKeys...) {
		for key, value := range m {
			if strings.ToLower(key) == strings.ToLower(rowKey) {
				ids = append(ids, value)
				break
			}
		}
	}

	if len(ids) < len(rowKeys)+len(rangeKeys) {
		return errors.New(fmt.Sprintf("To few key-values to delete row (%d of the required %d)", len(ids), len(rowKeys)+len(rangeKeys)))
	}

	err := t.Query(fmt.Sprintf(`DELETE FROM %q.%q WHERE %s`, t.Keyspace().Name(), t.Name(), strings.Join(where, " AND ")), ids...).Exec()
	if err != nil {
		return err
	}
	return nil

}

func (t CRUD) Range(ids ...interface{}) RangeInterface {
	rowKeys := t.RowKeys()
	rangeKeys := t.RangeKeys()

	rangeObj := Range{
		table: t,
	}

	numAppended := 0
	for idx, key := range append(rowKeys, rangeKeys...) {
		if len(ids) == numAppended {
			break
		}
		rangeObj = rangeObj.EqualTo(key, ids[idx]).(Range)
		numAppended++
	}

	return rangeObj
}

type Range struct {
	table gocqltable.TableInterface

	where     []string
	whereVals []interface{}
	order     string
	limit     *int
	filtering bool
}

func (r Range) LessThan(rangeKey string, value interface{}) RangeInterface {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" < ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) LessThanOrEqual(rangeKey string, value interface{}) RangeInterface {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" <= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThan(rangeKey string, value interface{}) RangeInterface {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" > ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) MoreThanOrEqual(rangeKey string, value interface{}) RangeInterface {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" >= ?")
	r.whereVals = append(r.whereVals, value)
	r.filtering = true
	return r
}

func (r Range) EqualTo(rangeKey string, value interface{}) RangeInterface {
	r.where = append(r.where, fmt.Sprintf("%q", strings.ToLower(rangeKey))+" = ?")
	r.whereVals = append(r.whereVals, value)
	return r
}

func (r Range) OrderBy(fieldAndDirection string) RangeInterface {
	r.order = fieldAndDirection
	return r
}

func (r Range) Limit(l int) RangeInterface {
	r.limit = &l
	return r
}

func (r Range) Fetch() (interface{}, error) {

	where := r.where
	whereVals := r.whereVals
	order := r.order
	limit := r.limit
	filtering := r.filtering

	whereString := ""
	if len(where) > 0 {
		whereString = "WHERE " + strings.Join(where, " AND ")
	}

	orderString := ""
	if order != "" {
		orderString = fmt.Sprintf("ORDER BY %v", order)
	}

	limitString := ""
	if limit != nil {
		limitString = "LIMIT " + strconv.Itoa(*limit)
	}

	filteringString := ""
	if filtering {
		filteringString = "ALLOW FILTERING"
	}

	iter := r.table.Query(fmt.Sprintf(`SELECT * FROM %q.%q %s %s %s %s`, r.table.Keyspace().Name(), r.table.Name(), whereString, orderString, limitString, filteringString), whereVals...).Fetch()

	result := reflect.Zero(reflect.SliceOf(reflect.PtrTo(reflect.TypeOf(r.table.Row())))) // Create a zero-value slice of pointers to our model type
	for row := range iter.Range() {
		result = reflect.Append(result, reflect.ValueOf(row)) // Append the rows to our slice
	}

	if err := iter.Close(); err != nil {
		return nil, err
	}

	return result.Interface(), nil

}
<|endoftext|>


<commit_before>package pitch

import (
	"fmt"
	"github.com/brettbuddin/mt/pkg/interval"
	mt_math "github.com/brettbuddin/mt/pkg/math"
)

const (
	Semitone = 1
	Tone     = 2
	Ditone   = 3
	Tritone  = 6

	DoubleFlat  = -2
	Flat        = -1
	Natural     = 0
	Sharp       = 1
	DoubleSharp = 2
)

const (
	C int = iota + 1
	D
	E
	F
	G
	A
	B
)

var (
	UseFancyAccidentals  = false
	AccidentalNames      = [5]string{"bb", "b", "", "#", "x"}
	FancyAccidentalNames = [5]string{"♭♭", "♭", "", "♯", "𝄪"}
	PitchNames           = [7]string{"C", "D", "E", "F", "G", "A", "B"}
	namesForFlats        = [12]int{0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6}
	namesForSharps       = [12]int{0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6}

	FlatNames  = NameStrategyFunc(func(i int) int { return namesForFlats[int(mt_math.Mod(float64(i), 12))] })
	SharpNames = NameStrategyFunc(func(i int) int { return namesForSharps[int(mt_math.Mod(float64(i), 12))] })
)

type NameStrategy interface {
	GetMappedIndex(int) int
}

type NameStrategyFunc func(int) int

func (f NameStrategyFunc) GetMappedIndex(i int) int {
	return f(i)
}

func New(diatonic, octaves, accidental int) Pitch {
	return Pitch{interval.New(diatonic, octaves, accidental)}
}

type Pitch struct {
	interval interval.Interval
}

func (p Pitch) Name(s NameStrategy) string {
	semitones := int(mt_math.Mod(float64(p.interval.Semitones()), 12.0))
	nameIndex := s.GetMappedIndex(semitones)
	delta := semitones - interval.DiatonicToChromatic(nameIndex)

	if delta == 0 {
		return fmt.Sprintf("%s%d", PitchNames[nameIndex], p.interval.Octaves())
	}
	return fmt.Sprintf("%s%s%d", PitchNames[nameIndex], accidentalName(delta+2), p.interval.Octaves())
}

func (p Pitch) AddInterval(i interval.Interval) Pitch {
	return Pitch{p.interval.AddInterval(i)}
}

func accidentalName(i int) string {
	if UseFancyAccidentals {
		return FancyAccidentalNames[int(mt_math.Mod(float64(i), float64(len(AccidentalNames))))]
	}
	return AccidentalNames[int(mt_math.Mod(float64(i), float64(len(AccidentalNames))))]
}
<commit_msg>Embed interval.<commit_after>package pitch

import (
	"fmt"
	"github.com/brettbuddin/mt/pkg/interval"
	mt_math "github.com/brettbuddin/mt/pkg/math"
)

const (
	Semitone = 1
	Tone     = 2
	Ditone   = 3
	Tritone  = 6

	DoubleFlat  = -2
	Flat        = -1
	Natural     = 0
	Sharp       = 1
	DoubleSharp = 2
)

const (
	C int = iota + 1
	D
	E
	F
	G
	A
	B
)

var (
	UseFancyAccidentals  = false
	AccidentalNames      = [5]string{"bb", "b", "", "#", "x"}
	FancyAccidentalNames = [5]string{"♭♭", "♭", "", "♯", "𝄪"}
	PitchNames           = [7]string{"C", "D", "E", "F", "G", "A", "B"}
	namesForFlats        = [12]int{0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6}
	namesForSharps       = [12]int{0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6}

	FlatNames  = NameStrategyFunc(func(i int) int { return namesForFlats[int(mt_math.Mod(float64(i), 12))] })
	SharpNames = NameStrategyFunc(func(i int) int { return namesForSharps[int(mt_math.Mod(float64(i), 12))] })
)

type NameStrategy interface {
	GetMappedIndex(int) int
}

type NameStrategyFunc func(int) int

func (f NameStrategyFunc) GetMappedIndex(i int) int {
	return f(i)
}

func New(diatonic, octaves, accidental int) Pitch {
	return Pitch{interval.New(diatonic, octaves, accidental)}
}

type Pitch struct {
	interval.Interval
}

func (p Pitch) Name(s NameStrategy) string {
	semitones := int(mt_math.Mod(float64(p.Semitones()), 12.0))
	nameIndex := s.GetMappedIndex(semitones)
	delta := semitones - interval.DiatonicToChromatic(nameIndex)

	if delta == 0 {
		return fmt.Sprintf("%s%d", PitchNames[nameIndex], p.Octaves())
	}
	return fmt.Sprintf("%s%s%d", PitchNames[nameIndex], accidentalName(delta+2), p.Octaves())
}

func (p Pitch) AddInterval(i interval.Interval) Pitch {
	return Pitch{p.Interval.AddInterval(i)}
}

func accidentalName(i int) string {
	if UseFancyAccidentals {
		return FancyAccidentalNames[int(mt_math.Mod(float64(i), float64(len(AccidentalNames))))]
	}
	return AccidentalNames[int(mt_math.Mod(float64(i), float64(len(AccidentalNames))))]
}
<|endoftext|>