<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package certificates

import (
	"context"
	"fmt"
	"io"

	"github.com/spf13/cobra"

	certificatesv1 "k8s.io/api/certificates/v1"
	certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	clientset "k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	cmd := &cobra.Command{
		Use:                   "certificate SUBCOMMAND",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Modify certificate resources."),
		Long:                  i18n.T("Modify certificate resources."),
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
		},
	}

	cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams))
	cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams))

	return cmd
}

type CertificateOptions struct {
	resource.FilenameOptions

	PrintFlags *genericclioptions.PrintFlags
	PrintObj   printers.ResourcePrinterFunc

	csrNames    []string
	outputStyle string

	clientSet clientset.Interface
	builder   *resource.Builder

	genericclioptions.IOStreams
}

// NewCertificateOptions creates the options for certificate
func NewCertificateOptions(ioStreams genericclioptions.IOStreams, operation string) *CertificateOptions {
	return &CertificateOptions{
		PrintFlags: genericclioptions.NewPrintFlags(operation).WithTypeSetter(scheme.Scheme),
		IOStreams:  ioStreams,
	}
}

func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	o.csrNames = args
	o.outputStyle = cmdutil.GetFlagString(cmd, "output")

	printer, err := o.PrintFlags.ToPrinter()
	if err != nil {
		return err
	}

	o.PrintObj = func(obj runtime.Object, out io.Writer) error {
		return printer.PrintObj(obj, out)
	}

	o.builder = f.NewBuilder()

	o.clientSet, err = f.KubernetesClientSet()
	if err != nil {
		return err
	}

	return nil
}

func (o *CertificateOptions) Validate() error {
	if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
		return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
	}
	return nil
}

func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewCertificateOptions(ioStreams, "approved")

	cmd := &cobra.Command{
		Use:                   "approve (-f FILENAME | NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Approve a certificate signing request"),
		Long: templates.LongDesc(i18n.T(`
		Approve a certificate signing request.

		kubectl certificate approve allows a cluster admin to approve a certificate
		signing request (CSR). This action tells a certificate signing controller to
		issue a certificate to the requestor with the attributes requested in the CSR.

		SECURITY NOTICE: Depending on the requested attributes, the issued certificate
		can potentially grant a requester access to cluster resources or to authenticate
		as a requested identity. Before approving a CSR, ensure you understand what the
		signed certificate can do.
		`)),
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
		},
	}

	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")

	return cmd
}

func (o *CertificateOptions) RunCertificateApprove(force bool) error {
	return o.modifyCertificateCondition(
		o.builder,
		o.clientSet,
		force,
		addConditionIfNeeded(string(certificatesv1.CertificateDenied), string(certificatesv1.CertificateApproved), "KubectlApprove", "This CSR was approved by kubectl certificate approve."),
	)
}

func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewCertificateOptions(ioStreams, "denied")

	cmd := &cobra.Command{
		Use:                   "deny (-f FILENAME | NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Deny a certificate signing request"),
		Long: templates.LongDesc(i18n.T(`
		Deny a certificate signing request.

		kubectl certificate deny allows a cluster admin to deny a certificate
		signing request (CSR). This action tells a certificate signing controller to
		not to issue a certificate to the requestor.
		`)),
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
		},
	}

	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")

	return cmd
}

func (o *CertificateOptions) RunCertificateDeny(force bool) error {
	return o.modifyCertificateCondition(
		o.builder,
		o.clientSet,
		force,
		addConditionIfNeeded(string(certificatesv1.CertificateApproved), string(certificatesv1.CertificateDenied), "KubectlDeny", "This CSR was denied by kubectl certificate deny."),
	)
}

func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet clientset.Interface, force bool, modify func(csr runtime.Object) (runtime.Object, bool, error)) error {
	var found int
	r := builder.
		Unstructured().
		ContinueOnError().
		FilenameParam(false, &o.FilenameOptions).
		ResourceNames("certificatesigningrequests", o.csrNames...).
		RequireObject(true).
		Flatten().
		Latest().
		Do()
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		for i := 0; ; i++ {
			obj, ok := info.Object.(*unstructured.Unstructured)
			if !ok {
				return fmt.Errorf("expected *unstructured.Unstructured, got %T", obj)
			}
			if want, got := certificatesv1.Kind("CertificateSigningRequest"), obj.GetObjectKind().GroupVersionKind().GroupKind(); want != got {
				return fmt.Errorf("can only handle %s objects, got %s", want.String(), got.String())
			}
			var csr runtime.Object
			// get a typed object
			// first try v1
			csr, err = clientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
			if apierrors.IsNotFound(err) {
				// fall back to v1beta1
				csr, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
			}
			if apierrors.IsNotFound(err) {
				return fmt.Errorf("could not find v1 or v1beta1 version of %s: %v", obj.GetName(), err)
			}
			if err != nil {
				return err
			}

			modifiedCSR, hasCondition, err := modify(csr)
			if err != nil {
				return err
			}
			if !hasCondition || force {
				switch modifiedCSR := modifiedCSR.(type) {
				case *certificatesv1.CertificateSigningRequest:
					_, err = clientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR.Name, modifiedCSR, metav1.UpdateOptions{})
				case *certificatesv1beta1.CertificateSigningRequest:
					_, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR, metav1.UpdateOptions{})
				default:
					return fmt.Errorf("can only handle certificates.k8s.io CertificateSigningRequest objects, got %T", modifiedCSR)
				}
				if errors.IsConflict(err) && i < 10 {
					if err := info.Get(); err != nil {
						return err
					}
					continue
				}
				if err != nil {
					return err
				}
			}
			break
		}
		found++

		return o.PrintObj(info.Object, o.Out)
	})
	if found == 0 && err == nil {
		fmt.Fprintf(o.Out, "No resources found\n")
	}
	return err
}

func addConditionIfNeeded(mustNotHaveConditionType, conditionType, reason, message string) func(runtime.Object) (runtime.Object, bool, error) {
	return func(csr runtime.Object) (runtime.Object, bool, error) {
		switch csr := csr.(type) {
		case *certificatesv1.CertificateSigningRequest:
			var alreadyHasCondition bool
			for _, c := range csr.Status.Conditions {
				if string(c.Type) == mustNotHaveConditionType {
					return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
				}
				if string(c.Type) == conditionType {
					alreadyHasCondition = true
				}
			}
			if alreadyHasCondition {
				return csr, true, nil
			}
			csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{
				Type:           certificatesv1.RequestConditionType(conditionType),
				Status:         corev1.ConditionTrue,
				Reason:         reason,
				Message:        message,
				LastUpdateTime: metav1.Now(),
			})
			return csr, false, nil

		case *certificatesv1beta1.CertificateSigningRequest:
			var alreadyHasCondition bool
			for _, c := range csr.Status.Conditions {
				if string(c.Type) == mustNotHaveConditionType {
					return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
				}
				if string(c.Type) == conditionType {
					alreadyHasCondition = true
				}
			}
			if alreadyHasCondition {
				return csr, true, nil
			}
			csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
				Type:           certificatesv1beta1.RequestConditionType(conditionType),
				Status:         corev1.ConditionTrue,
				Reason:         reason,
				Message:        message,
				LastUpdateTime: metav1.Now(),
			})
			return csr, false, nil

		default:
			return csr, false, nil
		}
	}
}
<commit_msg>Fix golint errors for kubectl/pkg/cmd/certificates/certificates.go<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package certificates

import (
	"context"
	"fmt"
	"io"

	"github.com/spf13/cobra"

	certificatesv1 "k8s.io/api/certificates/v1"
	certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	clientset "k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

// NewCmdCertificate returns `certificate` Cobra command
func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	cmd := &cobra.Command{
		Use:                   "certificate SUBCOMMAND",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Modify certificate resources."),
		Long:                  i18n.T("Modify certificate resources."),
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
		},
	}

	cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams))
	cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams))

	return cmd
}

// CertificateOptions declares the arguments accepted by the certificate command
type CertificateOptions struct {
	resource.FilenameOptions

	PrintFlags *genericclioptions.PrintFlags
	PrintObj   printers.ResourcePrinterFunc

	csrNames    []string
	outputStyle string

	clientSet clientset.Interface
	builder   *resource.Builder

	genericclioptions.IOStreams
}

// NewCertificateOptions creates CertificateOptions struct for `certificate` command
func NewCertificateOptions(ioStreams genericclioptions.IOStreams, operation string) *CertificateOptions {
	return &CertificateOptions{
		PrintFlags: genericclioptions.NewPrintFlags(operation).WithTypeSetter(scheme.Scheme),
		IOStreams:  ioStreams,
	}
}

// Complete loads data from the command environment
func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	o.csrNames = args
	o.outputStyle = cmdutil.GetFlagString(cmd, "output")

	printer, err := o.PrintFlags.ToPrinter()
	if err != nil {
		return err
	}

	o.PrintObj = func(obj runtime.Object, out io.Writer) error {
		return printer.PrintObj(obj, out)
	}

	o.builder = f.NewBuilder()

	o.clientSet, err = f.KubernetesClientSet()
	if err != nil {
		return err
	}

	return nil
}

// Validate checks if the provided `certificate` arguments are valid
func (o *CertificateOptions) Validate() error {
	if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
		return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
	}
	return nil
}

// NewCmdCertificateApprove returns the `certificate approve` Cobra command
func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewCertificateOptions(ioStreams, "approved")

	cmd := &cobra.Command{
		Use:                   "approve (-f FILENAME | NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Approve a certificate signing request"),
		Long: templates.LongDesc(i18n.T(`
		Approve a certificate signing request.

		kubectl certificate approve allows a cluster admin to approve a certificate
		signing request (CSR). This action tells a certificate signing controller to
		issue a certificate to the requestor with the attributes requested in the CSR.

		SECURITY NOTICE: Depending on the requested attributes, the issued certificate
		can potentially grant a requester access to cluster resources or to authenticate
		as a requested identity. Before approving a CSR, ensure you understand what the
		signed certificate can do.
		`)),
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
		},
	}

	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")

	return cmd
}

// RunCertificateApprove approves a certificate signing request
func (o *CertificateOptions) RunCertificateApprove(force bool) error {
	return o.modifyCertificateCondition(
		o.builder,
		o.clientSet,
		force,
		addConditionIfNeeded(string(certificatesv1.CertificateDenied), string(certificatesv1.CertificateApproved), "KubectlApprove", "This CSR was approved by kubectl certificate approve."),
	)
}

// NewCmdCertificateDeny returns the `certificate deny` Cobra command
func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewCertificateOptions(ioStreams, "denied")

	cmd := &cobra.Command{
		Use:                   "deny (-f FILENAME | NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Deny a certificate signing request"),
		Long: templates.LongDesc(i18n.T(`
		Deny a certificate signing request.

		kubectl certificate deny allows a cluster admin to deny a certificate
		signing request (CSR). This action tells a certificate signing controller to
		not to issue a certificate to the requestor.
		`)),
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
		},
	}

	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")

	return cmd
}

// RunCertificateDeny denies a certificate signing request
func (o *CertificateOptions) RunCertificateDeny(force bool) error {
	return o.modifyCertificateCondition(
		o.builder,
		o.clientSet,
		force,
		addConditionIfNeeded(string(certificatesv1.CertificateApproved), string(certificatesv1.CertificateDenied), "KubectlDeny", "This CSR was denied by kubectl certificate deny."),
	)
}

func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet clientset.Interface, force bool, modify func(csr runtime.Object) (runtime.Object, bool, error)) error {
	var found int
	r := builder.
		Unstructured().
		ContinueOnError().
		FilenameParam(false, &o.FilenameOptions).
		ResourceNames("certificatesigningrequests", o.csrNames...).
		RequireObject(true).
		Flatten().
		Latest().
		Do()
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		for i := 0; ; i++ {
			obj, ok := info.Object.(*unstructured.Unstructured)
			if !ok {
				return fmt.Errorf("expected *unstructured.Unstructured, got %T", obj)
			}
			if want, got := certificatesv1.Kind("CertificateSigningRequest"), obj.GetObjectKind().GroupVersionKind().GroupKind(); want != got {
				return fmt.Errorf("can only handle %s objects, got %s", want.String(), got.String())
			}
			var csr runtime.Object
			// get a typed object
			// first try v1
			csr, err = clientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
			if apierrors.IsNotFound(err) {
				// fall back to v1beta1
				csr, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
			}
			if apierrors.IsNotFound(err) {
				return fmt.Errorf("could not find v1 or v1beta1 version of %s: %v", obj.GetName(), err)
			}
			if err != nil {
				return err
			}

			modifiedCSR, hasCondition, err := modify(csr)
			if err != nil {
				return err
			}
			if !hasCondition || force {
				switch modifiedCSR := modifiedCSR.(type) {
				case *certificatesv1.CertificateSigningRequest:
					_, err = clientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR.Name, modifiedCSR, metav1.UpdateOptions{})
				case *certificatesv1beta1.CertificateSigningRequest:
					_, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR, metav1.UpdateOptions{})
				default:
					return fmt.Errorf("can only handle certificates.k8s.io CertificateSigningRequest objects, got %T", modifiedCSR)
				}
				if errors.IsConflict(err) && i < 10 {
					if err := info.Get(); err != nil {
						return err
					}
					continue
				}
				if err != nil {
					return err
				}
			}
			break
		}
		found++

		return o.PrintObj(info.Object, o.Out)
	})
	if found == 0 && err == nil {
		fmt.Fprintf(o.Out, "No resources found\n")
	}
	return err
}

func addConditionIfNeeded(mustNotHaveConditionType, conditionType, reason, message string) func(runtime.Object) (runtime.Object, bool, error) {
	return func(csr runtime.Object) (runtime.Object, bool, error) {
		switch csr := csr.(type) {
		case *certificatesv1.CertificateSigningRequest:
			var alreadyHasCondition bool
			for _, c := range csr.Status.Conditions {
				if string(c.Type) == mustNotHaveConditionType {
					return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
				}
				if string(c.Type) == conditionType {
					alreadyHasCondition = true
				}
			}
			if alreadyHasCondition {
				return csr, true, nil
			}
			csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{
				Type:           certificatesv1.RequestConditionType(conditionType),
				Status:         corev1.ConditionTrue,
				Reason:         reason,
				Message:        message,
				LastUpdateTime: metav1.Now(),
			})
			return csr, false, nil

		case *certificatesv1beta1.CertificateSigningRequest:
			var alreadyHasCondition bool
			for _, c := range csr.Status.Conditions {
				if string(c.Type) == mustNotHaveConditionType {
					return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
				}
				if string(c.Type) == conditionType {
					alreadyHasCondition = true
				}
			}
			if alreadyHasCondition {
				return csr, true, nil
			}
			csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
				Type:           certificatesv1beta1.RequestConditionType(conditionType),
				Status:         corev1.ConditionTrue,
				Reason:         reason,
				Message:        message,
				LastUpdateTime: metav1.Now(),
			})
			return csr, false, nil

		default:
			return csr, false, nil
		}
	}
}
<|endoftext|>


<commit_before>package start

import (
	"github.com/spf13/pflag"
)

// NetworkArgs is a struct that the command stores flag values into.
type NetworkArgs struct {
	// NetworkPluginName is the name of the networking plugin to be used for networking.
	NetworkPluginName string
	// ClusterNetworkCIDR is the CIDR string representing the network that all containers
	// should belong to.
	ClusterNetworkCIDR string
	// HostSubnetLength is the length of subnet each host is given from the network-cidr.
	HostSubnetLength uint
	// ServiceNetworkCIDR is the CIDR string representing the network that service IP
	// addresses will be allocated from
	ServiceNetworkCIDR string
}

// BindNetworkArgs binds values to the given arguments by using flags
func BindNetworkArgs(args *NetworkArgs, flags *pflag.FlagSet, prefix string) {
	flags.StringVar(&args.NetworkPluginName, prefix+"network-plugin", args.NetworkPluginName, "The name of the networking plugin to be used for networking.")
	flags.StringVar(&args.ClusterNetworkCIDR, prefix+"network-cidr", args.ClusterNetworkCIDR, "The CIDR string representing the network that all containers should belong to.")
	flags.UintVar(&args.HostSubnetLength, prefix+"host-subnet-length", args.HostSubnetLength, "The length of subnet each host is given from the network-cidr.")
	flags.StringVar(&args.ServiceNetworkCIDR, prefix+"portal-net", args.ServiceNetworkCIDR, "The CIDR string representing the network that portal/service IPs will be assigned from. This must not overlap with any IP ranges assigned to nodes for pods.")
}

// NewDefaultNetworkArgs returns a new set of network arguments
func NewDefaultNetworkArgs() *NetworkArgs {
	config := &NetworkArgs{
		NetworkPluginName:  "",
		ClusterNetworkCIDR: "10.1.0.0/16",
		HostSubnetLength:   8,
		ServiceNetworkCIDR: "172.30.0.0/16",
	}

	return config
}
<commit_msg>Change default ClusterNetworkCIDR and HostSubnetLength<commit_after>package start

import (
	"github.com/spf13/pflag"
)

// NetworkArgs is a struct that the command stores flag values into.
type NetworkArgs struct {
	// NetworkPluginName is the name of the networking plugin to be used for networking.
	NetworkPluginName string
	// ClusterNetworkCIDR is the CIDR string representing the network that all containers
	// should belong to.
	ClusterNetworkCIDR string
	// HostSubnetLength is the length of subnet each host is given from the network-cidr.
	HostSubnetLength uint
	// ServiceNetworkCIDR is the CIDR string representing the network that service IP
	// addresses will be allocated from
	ServiceNetworkCIDR string
}

// BindNetworkArgs binds values to the given arguments by using flags
func BindNetworkArgs(args *NetworkArgs, flags *pflag.FlagSet, prefix string) {
	flags.StringVar(&args.NetworkPluginName, prefix+"network-plugin", args.NetworkPluginName, "The name of the networking plugin to be used for networking.")
	flags.StringVar(&args.ClusterNetworkCIDR, prefix+"network-cidr", args.ClusterNetworkCIDR, "The CIDR string representing the network that all containers should belong to.")
	flags.UintVar(&args.HostSubnetLength, prefix+"host-subnet-length", args.HostSubnetLength, "The length of subnet each host is given from the network-cidr.")
	flags.StringVar(&args.ServiceNetworkCIDR, prefix+"portal-net", args.ServiceNetworkCIDR, "The CIDR string representing the network that portal/service IPs will be assigned from. This must not overlap with any IP ranges assigned to nodes for pods.")
}

// NewDefaultNetworkArgs returns a new set of network arguments
func NewDefaultNetworkArgs() *NetworkArgs {
	config := &NetworkArgs{
		NetworkPluginName:  "",
		ClusterNetworkCIDR: "10.128.0.0/14",
		HostSubnetLength:   9,
		ServiceNetworkCIDR: "172.30.0.0/16",
	}

	return config
}
<|endoftext|>


<commit_before>/*
   LeagueLoader is an application that loads data from the Riot
   API into a database for specific users. The data can then be used
   to track their progression for any number of things: tournaments,
   personal statistics, or achievements
*/

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/TrevorSStone/goriot"
	"github.com/coopernurse/gorp"
	_ "github.com/ziutek/mymysql/godrv"
	"log"
	"os"
	"time"
)

/* Config elements */
type Configuration struct {
	ApiKey   string      `json:"apiKey"`
	DbConfig MysqlConfig `json:"mysqlConfig"`
}

type MysqlConfig struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Host     string `json:"host"`
	Database string `json:"database"`
}

/* Database tables */
// so far, unused
type Summoner struct {
	Id       int64
	Name     string
	RealName string `db:"real_name"`
	TeamId   int    `db:"neta_team"`
	Level    int
}

// Streamlines checking for errors
func checkErr(e error, message string) {
	if e != nil {
		log.Fatalln(message, e)
	}
}

func main() {

	var config Configuration = openAndReadConfig("config.json")
	var dbConfig MysqlConfig = config.DbConfig

	// Goriot setup
	goriot.SetAPIKey(config.ApiKey)
	goriot.SetSmallRateLimit(10, 10*time.Second)
	goriot.SetLongRateLimit(500, 10*time.Minute)

	// Make connection
	dbmap := initDb(dbConfig.Database, dbConfig.Username, dbConfig.Password)
	defer dbmap.Db.Close()

	// get list of available summoners
	var summoners []int64 = getSummoners(dbmap)

	// update summoner information
	updateSummoners(summoners, dbmap)
	fmt.Println("Summoners updated")

	updateGames(summoners, dbmap)

	return
}

/***
 * Opens configuration files and
 * implements associated structs
 */
func openAndReadConfig(configFileName string) (config Configuration) {

	// load config file
	configFile, err := os.Open(configFileName)
	checkErr(err, "Unable to open config file")

	// parse config file
	jsonParser := json.NewDecoder(configFile)
	err = jsonParser.Decode(&config)
	checkErr(err, "Unable to decode json")

	return config
}

/***
 * Sets up the "ORM" by connecting to db and mapping
 * structs to tables
 */
func initDb(database string, username string, password string) *gorp.DbMap {
	db, err := sql.Open("mymysql", database+"/"+username+"/"+password)
	checkErr(err, "Connection failed")

	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "utf-8"}}

	dbmap.AddTableWithName(Summoner{}, "summoners").SetKeys(false, "Id")

	return dbmap
}

/***
 * Checks a slice of int64 for a given
 * value
 */
func existsInSlice(search int64, values []int64) (exists bool) {

	for _, value := range values {
		if value == search {
			return true
		}
	}

	return false
}
<commit_msg>Added New Relic monitor<commit_after>/*
   LeagueLoader is an application that loads data from the Riot
   API into a database for specific users. The data can then be used
   to track their progression for any number of things: tournaments,
   personal statistics, or achievements
*/

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/TrevorSStone/goriot"
	"github.com/coopernurse/gorp"
	"github.com/yvasiyarov/gorelic"
	_ "github.com/ziutek/mymysql/godrv"
	"log"
	"os"
	"time"
)

/* Config elements */
type Configuration struct {
	ApiKey   string      `json:"apiKey"`
	NewRelic string      `json:"newRelicKey"`
	DbConfig MysqlConfig `json:"mysqlConfig"`
}

type MysqlConfig struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Host     string `json:"host"`
	Database string `json:"database"`
}

/* Database tables */
// so far, unused
type Summoner struct {
	Id       int64
	Name     string
	RealName string `db:"real_name"`
	TeamId   int    `db:"neta_team"`
	Level    int
}

// Streamlines checking for errors
func checkErr(e error, message string) {
	if e != nil {
		log.Fatalln(message, e)
	}
}

func main() {

	var config Configuration = openAndReadConfig("config.json")
	var dbConfig MysqlConfig = config.DbConfig

	// Goriot setup
	goriot.SetAPIKey(config.ApiKey)
	goriot.SetSmallRateLimit(10, 10*time.Second)
	goriot.SetLongRateLimit(500, 10*time.Minute)

	// New Relic setup
	agent := gorelic.NewAgent()
	agent.NewrelicLicense = config.NewRelic
	agent.NewrelicName = "League Loader"
	agent.Run()

	// Make connection
	dbmap := initDb(dbConfig.Database, dbConfig.Username, dbConfig.Password)
	defer dbmap.Db.Close()

	// get list of available summoners
	var summoners []int64 = getSummoners(dbmap)

	// update summoner information
	updateSummoners(summoners, dbmap)
	fmt.Println("Summoners updated")

	// update game information
	updateGames(summoners, dbmap)
	fmt.Println("Games updated")

	return
}

/***
 * Opens configuration files and
 * implements associated structs
 */
func openAndReadConfig(configFileName string) (config Configuration) {

	// load config file
	configFile, err := os.Open(configFileName)
	checkErr(err, "Unable to open config file")

	// parse config file
	jsonParser := json.NewDecoder(configFile)
	err = jsonParser.Decode(&config)
	checkErr(err, "Unable to decode json")

	return config
}

/***
 * Sets up the "ORM" by connecting to db and mapping
 * structs to tables
 */
func initDb(database string, username string, password string) *gorp.DbMap {
	db, err := sql.Open("mymysql", database+"/"+username+"/"+password)
	checkErr(err, "Connection failed")

	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "utf-8"}}

	dbmap.AddTableWithName(Summoner{}, "summoners").SetKeys(false, "Id")

	return dbmap
}

/***
 * Checks a slice of int64 for a given
 * value
 */
func existsInSlice(search int64, values []int64) (exists bool) {

	for _, value := range values {
		if value == search {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The CDI 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 controller

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/go-logr/logr"

	routev1 "github.com/openshift/api/route/v1"
	secv1 "github.com/openshift/api/security/v1"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"

	"sigs.k8s.io/controller-runtime/pkg/client"

	"kubevirt.io/containerized-data-importer/pkg/common"
	"kubevirt.io/containerized-data-importer/pkg/operator/resources/utils"
)

const (
	// ReconcileStatePreCreate is the state before a resource is created
	ReconcileStatePreCreate ReconcileState = "PRE_CREATE"
	// ReconcileStatePostCreate is the state sfter a resource is created
	ReconcileStatePostCreate ReconcileState = "POST_CREATE"

	// ReconcileStatePostRead is the state sfter a resource is read
	ReconcileStatePostRead ReconcileState = "POST_READ"

	// ReconcileStatePreUpdate is the state before a resource is updated
	ReconcileStatePreUpdate ReconcileState = "PRE_UPDATE"
	// ReconcileStatePostUpdate is the state after a resource is updated
	ReconcileStatePostUpdate ReconcileState = "POST_UPDATE"

	// ReconcileStatePreDelete is the state before a resource is explicitly deleted (probably during upgrade)
	// don't count on this always being called for your resource
	// ideally we just let garbage collection do it's thing
	ReconcileStatePreDelete ReconcileState = "PRE_DELETE"

	// ReconcileStatePostDelete is the state after a resource is explicitly deleted (probably during upgrade)
	// don't count on this always being called for your resource
	// ideally we just let garbage collection do it's thing
	ReconcileStatePostDelete ReconcileState = "POST_DELETE"

	// ReconcileStateCDIDelete is called during CDI finalizer
	ReconcileStateCDIDelete ReconcileState = "CDI_DELETE"
)

// ReconcileState is the current state of the reconcile for a particuar resource
type ReconcileState string

// ReconcileCallbackArgs contains the data of a ReconcileCallback
type ReconcileCallbackArgs struct {
	Logger logr.Logger
	Client client.Client
	Scheme *runtime.Scheme

	State ReconcileState

	DesiredObject runtime.Object
	CurrentObject runtime.Object
}

// ReconcileCallback is the callback function
type ReconcileCallback func(args *ReconcileCallbackArgs) error

func getExplicitWatchTypes() []runtime.Object {
	return []runtime.Object{&routev1.Route{}}
}

func addReconcileCallbacks(r *ReconcileCDI) {
	r.addCallback(&appsv1.Deployment{}, reconcileDeleteControllerDeployment)
	r.addCallback(&corev1.ServiceAccount{}, reconcileServiceAccountRead)
	r.addCallback(&corev1.ServiceAccount{}, reconcileServiceAccounts)
	r.addCallback(&appsv1.Deployment{}, reconcileCreateRoute)
}

func isControllerDeployment(d *appsv1.Deployment) bool {
	if d.Name == "cdi-deployment" {
		return true
	}
	return false
}

func reconcileDeleteControllerDeployment(args *ReconcileCallbackArgs) error {
	switch args.State {
	case ReconcileStatePostDelete, ReconcileStateCDIDelete:
	default:
		return nil
	}

	deployment := args.DesiredObject.(*appsv1.Deployment)
	if !isControllerDeployment(deployment) {
		return nil
	}

	args.Logger.Info("Deleting CDI deployment and all import/upload/clone pods/services")

	err := args.Client.Delete(context.TODO(), deployment, func(opts *client.DeleteOptions) {
		p := metav1.DeletePropagationForeground
		opts.PropagationPolicy = &p
	})
	if err != nil && !errors.IsNotFound(err) {
		args.Logger.Error(err, "Error deleting cdi controller deployment")
		return err
	}

	if err = deleteWorkerResources(args.Logger, args.Client); err != nil {
		args.Logger.Error(err, "Error deleting worker resources")
		return err
	}

	return nil
}

func reconcileCreateRoute(args *ReconcileCallbackArgs) error {
	if args.State != ReconcileStatePostRead {
		return nil
	}

	deployment := args.CurrentObject.(*appsv1.Deployment)
	if !isControllerDeployment(deployment) || !checkDeploymentReady(deployment) {
		return nil
	}

	if err := ensureUploadProxyRouteExists(args.Logger, args.Client, args.Scheme, deployment); err != nil {
		return err
	}

	return nil
}

func reconcileServiceAccountRead(args *ReconcileCallbackArgs) error {
	if args.State != ReconcileStatePostRead {
		return nil
	}

	do := args.DesiredObject.(*corev1.ServiceAccount)
	co := args.CurrentObject.(*corev1.ServiceAccount)

	val, exists := do.Annotations[utils.SCCAnnotation]
	if exists {
		if co.Annotations == nil {
			co.Annotations = make(map[string]string)
		}
		co.Annotations[utils.SCCAnnotation] = val
	}

	return nil
}

func reconcileServiceAccounts(args *ReconcileCallbackArgs) error {
	switch args.State {
	case ReconcileStatePreCreate, ReconcileStatePreUpdate, ReconcileStatePostDelete, ReconcileStateCDIDelete:
	default:
		return nil
	}

	desiredSA := args.DesiredObject.(*corev1.ServiceAccount)

	desiredSCCs := []string{}
	saName := fmt.Sprintf("system:serviceaccount:%s:%s", desiredSA.Namespace, desiredSA.Name)

	switch args.State {
	case ReconcileStatePreCreate, ReconcileStatePreUpdate:
		val, exists := desiredSA.Annotations[utils.SCCAnnotation]
		if exists {
			if err := json.Unmarshal([]byte(val), &desiredSCCs); err != nil {
				args.Logger.Error(err, "Error unmarshalling data")
				return err
			}
		}
	default:
		// want desiredSCCs empty because deleting resource/CDI
	}

	listObj := &secv1.SecurityContextConstraintsList{}
	if err := args.Client.List(context.TODO(), &client.ListOptions{}, listObj); err != nil {
		if meta.IsNoMatchError(err) {
			// not openshift
			return nil
		}
		args.Logger.Error(err, "Error listing SCCs")
		return err
	}

	for _, scc := range listObj.Items {
		desiredUsers := []string{}
		add := containsValue(desiredSCCs, scc.Name)
		seenUser := false

		for _, u := range scc.Users {
			if u == saName {
				seenUser = true
				if !add {
					continue
				}
			}
			desiredUsers = append(desiredUsers, u)
		}

		if add && !seenUser {
			desiredUsers = append(desiredUsers, saName)
		}

		if !reflect.DeepEqual(desiredUsers, scc.Users) {
			args.Logger.Info("Doing SCC update", "name", scc.Name, "desired", desiredUsers, "current", scc.Users)
			scc.Users = desiredUsers
			if err := args.Client.Update(context.TODO(), &scc); err != nil {
				args.Logger.Error(err, "Error updating SCC")
				return err
			}
		}
	}

	return nil
}

func deleteWorkerResources(l logr.Logger, c client.Client) error {
	listTypes := []runtime.Object{&corev1.PodList{}, &corev1.ServiceList{}}

	for _, lt := range listTypes {
		lo := &client.ListOptions{}
		lo.SetLabelSelector(fmt.Sprintf("cdi.kubevirt.io in (%s, %s, %s)",
			common.ImporterPodName, common.UploadServerCDILabel, common.ClonerSourcePodName))

		if err := c.List(context.TODO(), lo, lt); err != nil {
			l.Error(err, "Error listing resources")
			return err
		}

		sv := reflect.ValueOf(lt).Elem()
		iv := sv.FieldByName("Items")

		for i := 0; i < iv.Len(); i++ {
			obj := iv.Index(i).Addr().Interface().(runtime.Object)
			l.Info("Deleting", "type", reflect.TypeOf(obj), "obj", obj)
			if err := c.Delete(context.TODO(), obj); err != nil {
				l.Error(err, "Error deleting a resource")
				return err
			}
		}
	}

	return nil
}

func containsValue(values []string, value string) bool {
	for _, v := range values {
		if v == value {
			return true
		}
	}
	return false
}
<commit_msg>suppport upgrade delete of SA<commit_after>/*
Copyright 2018 The CDI 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 controller

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/go-logr/logr"

	routev1 "github.com/openshift/api/route/v1"
	secv1 "github.com/openshift/api/security/v1"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"

	"sigs.k8s.io/controller-runtime/pkg/client"

	"kubevirt.io/containerized-data-importer/pkg/common"
	"kubevirt.io/containerized-data-importer/pkg/operator/resources/utils"
)

const (
	// ReconcileStatePreCreate is the state before a resource is created
	ReconcileStatePreCreate ReconcileState = "PRE_CREATE"
	// ReconcileStatePostCreate is the state sfter a resource is created
	ReconcileStatePostCreate ReconcileState = "POST_CREATE"

	// ReconcileStatePostRead is the state sfter a resource is read
	ReconcileStatePostRead ReconcileState = "POST_READ"

	// ReconcileStatePreUpdate is the state before a resource is updated
	ReconcileStatePreUpdate ReconcileState = "PRE_UPDATE"
	// ReconcileStatePostUpdate is the state after a resource is updated
	ReconcileStatePostUpdate ReconcileState = "POST_UPDATE"

	// ReconcileStatePreDelete is the state before a resource is explicitly deleted (probably during upgrade)
	// don't count on this always being called for your resource
	// ideally we just let garbage collection do it's thing
	ReconcileStatePreDelete ReconcileState = "PRE_DELETE"

	// ReconcileStatePostDelete is the state after a resource is explicitly deleted (probably during upgrade)
	// don't count on this always being called for your resource
	// ideally we just let garbage collection do it's thing
	ReconcileStatePostDelete ReconcileState = "POST_DELETE"

	// ReconcileStateCDIDelete is called during CDI finalizer
	ReconcileStateCDIDelete ReconcileState = "CDI_DELETE"
)

// ReconcileState is the current state of the reconcile for a particuar resource
type ReconcileState string

// ReconcileCallbackArgs contains the data of a ReconcileCallback
type ReconcileCallbackArgs struct {
	Logger logr.Logger
	Client client.Client
	Scheme *runtime.Scheme

	State ReconcileState

	DesiredObject runtime.Object
	CurrentObject runtime.Object
}

// ReconcileCallback is the callback function
type ReconcileCallback func(args *ReconcileCallbackArgs) error

func getExplicitWatchTypes() []runtime.Object {
	return []runtime.Object{&routev1.Route{}}
}

func addReconcileCallbacks(r *ReconcileCDI) {
	r.addCallback(&appsv1.Deployment{}, reconcileDeleteControllerDeployment)
	r.addCallback(&corev1.ServiceAccount{}, reconcileServiceAccountRead)
	r.addCallback(&corev1.ServiceAccount{}, reconcileServiceAccounts)
	r.addCallback(&appsv1.Deployment{}, reconcileCreateRoute)
}

func isControllerDeployment(d *appsv1.Deployment) bool {
	if d.Name == "cdi-deployment" {
		return true
	}
	return false
}

func reconcileDeleteControllerDeployment(args *ReconcileCallbackArgs) error {
	switch args.State {
	case ReconcileStatePostDelete, ReconcileStateCDIDelete:
	default:
		return nil
	}

	deployment := args.DesiredObject.(*appsv1.Deployment)
	if !isControllerDeployment(deployment) {
		return nil
	}

	args.Logger.Info("Deleting CDI deployment and all import/upload/clone pods/services")

	err := args.Client.Delete(context.TODO(), deployment, func(opts *client.DeleteOptions) {
		p := metav1.DeletePropagationForeground
		opts.PropagationPolicy = &p
	})
	if err != nil && !errors.IsNotFound(err) {
		args.Logger.Error(err, "Error deleting cdi controller deployment")
		return err
	}

	if err = deleteWorkerResources(args.Logger, args.Client); err != nil {
		args.Logger.Error(err, "Error deleting worker resources")
		return err
	}

	return nil
}

func reconcileCreateRoute(args *ReconcileCallbackArgs) error {
	if args.State != ReconcileStatePostRead {
		return nil
	}

	deployment := args.CurrentObject.(*appsv1.Deployment)
	if !isControllerDeployment(deployment) || !checkDeploymentReady(deployment) {
		return nil
	}

	if err := ensureUploadProxyRouteExists(args.Logger, args.Client, args.Scheme, deployment); err != nil {
		return err
	}

	return nil
}

func reconcileServiceAccountRead(args *ReconcileCallbackArgs) error {
	if args.State != ReconcileStatePostRead {
		return nil
	}

	do := args.DesiredObject.(*corev1.ServiceAccount)
	co := args.CurrentObject.(*corev1.ServiceAccount)

	val, exists := do.Annotations[utils.SCCAnnotation]
	if exists {
		if co.Annotations == nil {
			co.Annotations = make(map[string]string)
		}
		co.Annotations[utils.SCCAnnotation] = val
	}

	return nil
}

func reconcileServiceAccounts(args *ReconcileCallbackArgs) error {
	switch args.State {
	case ReconcileStatePreCreate, ReconcileStatePreUpdate, ReconcileStatePostDelete, ReconcileStateCDIDelete:
	default:
		return nil
	}

	var sa *corev1.ServiceAccount
	if args.State == ReconcileStatePostDelete {
		sa = args.CurrentObject.(*corev1.ServiceAccount)
	} else {
		sa = args.DesiredObject.(*corev1.ServiceAccount)
	}

	desiredSCCs := []string{}
	saName := fmt.Sprintf("system:serviceaccount:%s:%s", sa.Namespace, sa.Name)

	switch args.State {
	case ReconcileStatePreCreate, ReconcileStatePreUpdate:
		val, exists := sa.Annotations[utils.SCCAnnotation]
		if exists {
			if err := json.Unmarshal([]byte(val), &desiredSCCs); err != nil {
				args.Logger.Error(err, "Error unmarshalling data")
				return err
			}
		}
	default:
		// want desiredSCCs empty because deleting resource/CDI
	}

	listObj := &secv1.SecurityContextConstraintsList{}
	if err := args.Client.List(context.TODO(), &client.ListOptions{}, listObj); err != nil {
		if meta.IsNoMatchError(err) {
			// not openshift
			return nil
		}
		args.Logger.Error(err, "Error listing SCCs")
		return err
	}

	for _, scc := range listObj.Items {
		desiredUsers := []string{}
		add := containsValue(desiredSCCs, scc.Name)
		seenUser := false

		for _, u := range scc.Users {
			if u == saName {
				seenUser = true
				if !add {
					continue
				}
			}
			desiredUsers = append(desiredUsers, u)
		}

		if add && !seenUser {
			desiredUsers = append(desiredUsers, saName)
		}

		if !reflect.DeepEqual(desiredUsers, scc.Users) {
			args.Logger.Info("Doing SCC update", "name", scc.Name, "desired", desiredUsers, "current", scc.Users)
			scc.Users = desiredUsers
			if err := args.Client.Update(context.TODO(), &scc); err != nil {
				args.Logger.Error(err, "Error updating SCC")
				return err
			}
		}
	}

	return nil
}

func deleteWorkerResources(l logr.Logger, c client.Client) error {
	listTypes := []runtime.Object{&corev1.PodList{}, &corev1.ServiceList{}}

	for _, lt := range listTypes {
		lo := &client.ListOptions{}
		lo.SetLabelSelector(fmt.Sprintf("cdi.kubevirt.io in (%s, %s, %s)",
			common.ImporterPodName, common.UploadServerCDILabel, common.ClonerSourcePodName))

		if err := c.List(context.TODO(), lo, lt); err != nil {
			l.Error(err, "Error listing resources")
			return err
		}

		sv := reflect.ValueOf(lt).Elem()
		iv := sv.FieldByName("Items")

		for i := 0; i < iv.Len(); i++ {
			obj := iv.Index(i).Addr().Interface().(runtime.Object)
			l.Info("Deleting", "type", reflect.TypeOf(obj), "obj", obj)
			if err := c.Delete(context.TODO(), obj); err != nil {
				l.Error(err, "Error deleting a resource")
				return err
			}
		}
	}

	return nil
}

func containsValue(values []string, value string) bool {
	for _, v := range values {
		if v == value {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>/*******************************************************************************
*
* Copyright 2018 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, you may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*******************************************************************************/

package plugins

import (
	"encoding/json"
	"regexp"
	"strconv"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	flavorsmodule "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/sapcc/go-api-declarations/limes"
	"github.com/sapcc/go-bits/logg"

	"github.com/sapcc/limes/pkg/core"
)

type capacitySapccIronicPlugin struct {
	cfg                 core.CapacitorConfiguration
	ftt                 novaFlavorTranslationTable
	reportSubcapacities bool
}

type capacitySapccIronicSerializedMetrics struct {
	UnmatchedNodeCount uint64 `json:"unmatched_nodes"`
}

func init() {
	core.RegisterCapacityPlugin(func(c core.CapacitorConfiguration, scrapeSubcapacities map[string]map[string]bool) core.CapacityPlugin {
		ftt := newNovaFlavorTranslationTable(c.SAPCCIronic.FlavorAliases)
		return &capacitySapccIronicPlugin{c, ftt, scrapeSubcapacities["compute"]["instances-baremetal"]}
	})
}

//Init implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Init(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) error {
	return nil
}

//Type implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Type() string {
	return "sapcc-ironic"
}

type ironicNode struct {
	ID                   string  `json:"uuid"`
	Name                 string  `json:"name"`
	ProvisionState       string  `json:"provision_state"`
	TargetProvisionState *string `json:"target_provision_state"`
	InstanceID           *string `json:"instance_uuid"`
	ResourceClass        *string `json:"resource_class"`
	Properties           struct {
		Cores           veryFlexibleUint64 `json:"cpus"`
		DiskGiB         veryFlexibleUint64 `json:"local_gb"`
		MemoryMiB       veryFlexibleUint64 `json:"memory_mb"`
		CPUArchitecture string             `json:"cpu_arch"`
		Capabilities    string             `json:"capabilities"` //e.g. "cpu_txt:true,cpu_aes:true"
		SerialNumber    string             `json:"serial"`
	} `json:"properties"`
}

func (n ironicNode) StableProvisionState() string {
	if n.TargetProvisionState != nil {
		return *n.TargetProvisionState
	}
	return n.ProvisionState
}

////////////////////////////////////////////////////////////////////////////////
// OpenStack is being inconsistent with itself again

//For fields that are sometimes missing, sometimes an integer, sometimes a string.
type veryFlexibleUint64 uint64

//UnmarshalJSON implements the json.Unmarshaler interface.
func (value *veryFlexibleUint64) UnmarshalJSON(buf []byte) error {
	if string(buf) == "null" {
		*value = 0
		return nil
	}

	if buf[0] == '"' {
		var str string
		err := json.Unmarshal(buf, &str)
		if err != nil {
			return err
		}
		val, err := strconv.ParseUint(str, 10, 64)
		*value = veryFlexibleUint64(val)
		return err
	}

	var val uint64
	err := json.Unmarshal(buf, &val)
	*value = veryFlexibleUint64(val)
	return err
}

//Reference:
//  Hosts are expected to be in one of the following format:
//    - "nova-compute-xxxx"
//    - "nova-compute-ironic-xxxx"
//  where "xxxx" is unique among all hosts.
var computeHostStubRx = regexp.MustCompile(`^nova-compute-(?:ironic-)?([a-zA-Z0-9]+)$`)

//Node names are expected to be in the form "nodeXXX-bmYYY" or "nodeXXX-bbYYY"
//or "nodeXXX-apYYY" or "nodeXXX-mdYYY", where the second half is the host stub
//(the match group from above).
var nodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-((?:b[bm]|ap|md|st)\d+)$`)

//As a special case, nodes in the control plane do not belong to any
//user-accessible Nova aggregates, so we cannot establish an AZ association.
//However, we don't really need the AZ association anyway: AZ capacities are
//presented to the customer as a sort of manual scheduling hint, but CP nodes
//are earmarked for internal use and thus are not relevant there.
var cpNodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-(cp\d+)$`)

//Scrape implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Scrape(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (map[string]map[string]core.CapacityData, string, error) {
	//collect info about flavors with separate instance quota
	novaClient, err := openstack.NewComputeV2(provider, eo)
	if err != nil {
		return nil, "", err
	}
	flavors, err := p.getIronicFlavorNames(novaClient)
	if err != nil {
		return nil, "", err
	}

	//we are going to report capacity for all per-flavor instance quotas
	result := make(map[string]*core.CapacityData)
	for _, flavor := range flavors {
		result[p.ftt.LimesResourceNameForFlavor(flavor)] = &core.CapacityData{
			Capacity:      0,
			CapacityPerAZ: map[string]*core.CapacityDataForAZ{},
		}
	}

	//count Ironic nodes
	ironicClient, err := openstack.NewBareMetalV1(provider, eo)
	if err != nil {
		return nil, "", err
	}
	ironicClient.Microversion = "1.22"
	allPages, err := ironicNodesListDetail(ironicClient).AllPages()
	if err != nil {
		return nil, "", err
	}
	var allNodes []ironicNode
	err = ironicExtractNodesInto(allPages, &allNodes)
	if err != nil {
		return nil, "", err
	}

	//Ironic bPods are expected to be listed as compute hosts assigned to
	//host aggregates in the format: "nova-compute-ironic-xxxx".
	azs, _, err := getAggregates(novaClient)
	if err != nil {
		return nil, "", err
	}
	azForHostStub := make(map[string]string)
	for azName, az := range azs {
		for host := range az.ContainsComputeHost {
			if host == "nova-compute-ironic" {
				azForHostStub["bpod001"] = azName //hardcoded for the few nodes using legacy naming convention
			} else {
				match := computeHostStubRx.FindStringSubmatch(host)
				if match == nil {
					logg.Error(`compute host %q does not match the "nova-compute-(ironic-)xxxx" naming convention`, host)
				} else {
					azForHostStub[match[1]] = azName
				}
			}
		}
	}

	unmatchedCounter := uint64(0)
	for _, node := range allNodes {
		//do not consider nodes that have not been made available for provisioning yet
		if !isAvailableProvisionState[node.StableProvisionState()] {
			continue
		}

		matched := false
		for _, flavor := range flavors {
			if node.Matches(flavor) {
				logg.Debug("Ironic node %q (%s) matches flavor %s", node.Name, node.ID, flavor)

				data := result[p.ftt.LimesResourceNameForFlavor(flavor)]
				data.Capacity++

				var nodeAZ string
				if match := cpNodeNameRx.FindStringSubmatch(node.Name); match != nil {
					//special case as explained above (near definition of `cpNodeNameRx`)
					nodeAZ = match[1]
				} else if match := nodeNameRx.FindStringSubmatch(node.Name); match != nil {
					nodeAZ = azForHostStub[match[1]]
					if nodeAZ == "" {
						logg.Info("Ironic node %q (%s) does not match any compute host from host aggregates", node.Name, node.ID)
						nodeAZ = "unknown"
					}
				} else {
					logg.Error(`Ironic node %q (%s) does not match the "nodeXXX-{bm,bb,ap,md}YYY" naming convention`, node.Name, node.ID)
				}

				if _, ok := data.CapacityPerAZ[nodeAZ]; !ok {
					data.CapacityPerAZ[nodeAZ] = &core.CapacityDataForAZ{}
				}
				data.CapacityPerAZ[nodeAZ].Capacity++
				if node.StableProvisionState() == "active" {
					data.CapacityPerAZ[nodeAZ].Usage++
				}

				if p.reportSubcapacities {
					sub := map[string]interface{}{
						"id":              node.ID,
						"name":            node.Name,
						"provision_state": node.ProvisionState,
					}
					if node.TargetProvisionState != nil && *node.TargetProvisionState != "" {
						sub["target_provision_state"] = *node.TargetProvisionState
					}
					if nodeAZ != "" {
						sub["availability_zone"] = nodeAZ
					}
					if node.Properties.MemoryMiB > 0 {
						sub["ram"] = limes.ValueWithUnit{Unit: limes.UnitMebibytes, Value: uint64(node.Properties.MemoryMiB)}
					}
					if node.Properties.DiskGiB > 0 {
						sub["disk"] = limes.ValueWithUnit{Unit: limes.UnitGibibytes, Value: uint64(node.Properties.DiskGiB)}
					}
					if node.Properties.Cores > 0 {
						sub["cores"] = uint64(node.Properties.Cores)
					}
					if node.Properties.SerialNumber != "" {
						sub["serial"] = node.Properties.SerialNumber
					}
					if node.InstanceID != nil && *node.InstanceID != "" {
						sub["instance_id"] = *node.InstanceID
					}
					data.Subcapacities = append(data.Subcapacities, sub)
				}

				matched = true
				break
			}
		}
		if !matched {
			logg.Error("Ironic node %q (%s) does not match any baremetal flavor", node.Name, node.ID)
			unmatchedCounter++
		}
	}

	//remove pointers from `result`
	result2 := make(map[string]core.CapacityData, len(result))
	for resourceName, data := range result {
		result2[resourceName] = *data
	}

	serializedMetrics, err := json.Marshal(capacitySapccIronicSerializedMetrics{
		UnmatchedNodeCount: unmatchedCounter,
	})
	if err != nil {
		return nil, "", err
	}

	return map[string]map[string]core.CapacityData{"compute": result2}, string(serializedMetrics), nil
}

var ironicUnmatchedNodesGauge = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "limes_unmatched_ironic_nodes",
		Help: "Number of available/active Ironic nodes without matching flavor.",
	},
	[]string{"os_cluster"},
)

//DescribeMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) DescribeMetrics(ch chan<- *prometheus.Desc) {
	ironicUnmatchedNodesGauge.Describe(ch)
}

//CollectMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) CollectMetrics(ch chan<- prometheus.Metric, clusterID, serializedMetrics string) error {
	if serializedMetrics == "" {
		return nil
	}
	var metrics capacitySapccIronicSerializedMetrics
	err := json.Unmarshal([]byte(serializedMetrics), &metrics)
	if err != nil {
		return err
	}

	descCh := make(chan *prometheus.Desc, 1)
	ironicUnmatchedNodesGauge.Describe(descCh)
	ironicUnmatchedNodesDesc := <-descCh

	ch <- prometheus.MustNewConstMetric(
		ironicUnmatchedNodesDesc,
		prometheus.GaugeValue, float64(metrics.UnmatchedNodeCount),
		clusterID,
	)
	return nil
}

func (p *capacitySapccIronicPlugin) getIronicFlavorNames(novaClient *gophercloud.ServiceClient) ([]string, error) {
	//which flavors have separate instance quota?
	flavorNames, err := p.ftt.ListFlavorsWithSeparateInstanceQuota(novaClient)
	if err != nil {
		return nil, err
	}
	isRelevantFlavorName := make(map[string]bool, len(flavorNames))
	for _, flavorName := range flavorNames {
		isRelevantFlavorName[flavorName] = true
	}

	//check flavor relevancy
	var result []string
	err = flavorsmodule.ListDetail(novaClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		flavors, err := flavorsmodule.ExtractFlavors(page)
		if err != nil {
			return false, err
		}

		for _, flavor := range flavors {
			if isRelevantFlavorName[flavor.Name] {
				result = append(result, flavor.Name)
			}
		}
		return true, nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (n ironicNode) Matches(flavorName string) bool {
	if n.ResourceClass != nil {
		return *n.ResourceClass == flavorName
	}
	return false
}
<commit_msg>[ironic] capacity plugin needs to support a new naming convention<commit_after>/*******************************************************************************
*
* Copyright 2018 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, you may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*******************************************************************************/

package plugins

import (
	"encoding/json"
	"regexp"
	"strconv"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	flavorsmodule "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/sapcc/go-api-declarations/limes"
	"github.com/sapcc/go-bits/logg"

	"github.com/sapcc/limes/pkg/core"
)

type capacitySapccIronicPlugin struct {
	cfg                 core.CapacitorConfiguration
	ftt                 novaFlavorTranslationTable
	reportSubcapacities bool
}

type capacitySapccIronicSerializedMetrics struct {
	UnmatchedNodeCount uint64 `json:"unmatched_nodes"`
}

func init() {
	core.RegisterCapacityPlugin(func(c core.CapacitorConfiguration, scrapeSubcapacities map[string]map[string]bool) core.CapacityPlugin {
		ftt := newNovaFlavorTranslationTable(c.SAPCCIronic.FlavorAliases)
		return &capacitySapccIronicPlugin{c, ftt, scrapeSubcapacities["compute"]["instances-baremetal"]}
	})
}

//Init implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Init(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) error {
	return nil
}

//Type implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Type() string {
	return "sapcc-ironic"
}

type ironicNode struct {
	ID                   string  `json:"uuid"`
	Name                 string  `json:"name"`
	ProvisionState       string  `json:"provision_state"`
	TargetProvisionState *string `json:"target_provision_state"`
	InstanceID           *string `json:"instance_uuid"`
	ResourceClass        *string `json:"resource_class"`
	Properties           struct {
		Cores           veryFlexibleUint64 `json:"cpus"`
		DiskGiB         veryFlexibleUint64 `json:"local_gb"`
		MemoryMiB       veryFlexibleUint64 `json:"memory_mb"`
		CPUArchitecture string             `json:"cpu_arch"`
		Capabilities    string             `json:"capabilities"` //e.g. "cpu_txt:true,cpu_aes:true"
		SerialNumber    string             `json:"serial"`
	} `json:"properties"`
}

func (n ironicNode) StableProvisionState() string {
	if n.TargetProvisionState != nil {
		return *n.TargetProvisionState
	}
	return n.ProvisionState
}

////////////////////////////////////////////////////////////////////////////////
// OpenStack is being inconsistent with itself again

//For fields that are sometimes missing, sometimes an integer, sometimes a string.
type veryFlexibleUint64 uint64

//UnmarshalJSON implements the json.Unmarshaler interface.
func (value *veryFlexibleUint64) UnmarshalJSON(buf []byte) error {
	if string(buf) == "null" {
		*value = 0
		return nil
	}

	if buf[0] == '"' {
		var str string
		err := json.Unmarshal(buf, &str)
		if err != nil {
			return err
		}
		val, err := strconv.ParseUint(str, 10, 64)
		*value = veryFlexibleUint64(val)
		return err
	}

	var val uint64
	err := json.Unmarshal(buf, &val)
	*value = veryFlexibleUint64(val)
	return err
}

//Reference:
//  Hosts are expected to be in one of the following format:
//    - "nova-compute-xxxx"
//    - "nova-compute-ironic-xxxx"
//  where "xxxx" is unique among all hosts.
var computeHostStubRx = regexp.MustCompile(`^nova-compute-(?:ironic-)?([a-zA-Z0-9]+)$`)

//Node names are expected to be in the form "nodeXXX-bmYYY" or "nodeXXX-bbYYY"
//or "nodeXXX-apYYY" or "nodeXXX-mdYYY", where the second half is the host stub
//(the match group from above).
var nodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-((?:b[bm]|ap|md|st|swf)\d+)$`)

//As a special case, nodes in the control plane do not belong to any
//user-accessible Nova aggregates, so we cannot establish an AZ association.
//However, we don't really need the AZ association anyway: AZ capacities are
//presented to the customer as a sort of manual scheduling hint, but CP nodes
//are earmarked for internal use and thus are not relevant there.
var cpNodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-(cp\d+)$`)

//Scrape implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Scrape(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (map[string]map[string]core.CapacityData, string, error) {
	//collect info about flavors with separate instance quota
	novaClient, err := openstack.NewComputeV2(provider, eo)
	if err != nil {
		return nil, "", err
	}
	flavors, err := p.getIronicFlavorNames(novaClient)
	if err != nil {
		return nil, "", err
	}

	//we are going to report capacity for all per-flavor instance quotas
	result := make(map[string]*core.CapacityData)
	for _, flavor := range flavors {
		result[p.ftt.LimesResourceNameForFlavor(flavor)] = &core.CapacityData{
			Capacity:      0,
			CapacityPerAZ: map[string]*core.CapacityDataForAZ{},
		}
	}

	//count Ironic nodes
	ironicClient, err := openstack.NewBareMetalV1(provider, eo)
	if err != nil {
		return nil, "", err
	}
	ironicClient.Microversion = "1.22"
	allPages, err := ironicNodesListDetail(ironicClient).AllPages()
	if err != nil {
		return nil, "", err
	}
	var allNodes []ironicNode
	err = ironicExtractNodesInto(allPages, &allNodes)
	if err != nil {
		return nil, "", err
	}

	//Ironic bPods are expected to be listed as compute hosts assigned to
	//host aggregates in the format: "nova-compute-ironic-xxxx".
	azs, _, err := getAggregates(novaClient)
	if err != nil {
		return nil, "", err
	}
	azForHostStub := make(map[string]string)
	for azName, az := range azs {
		for host := range az.ContainsComputeHost {
			if host == "nova-compute-ironic" {
				azForHostStub["bpod001"] = azName //hardcoded for the few nodes using legacy naming convention
			} else {
				match := computeHostStubRx.FindStringSubmatch(host)
				if match == nil {
					logg.Error(`compute host %q does not match the "nova-compute-(ironic-)xxxx" naming convention`, host)
				} else {
					azForHostStub[match[1]] = azName
				}
			}
		}
	}

	unmatchedCounter := uint64(0)
	for _, node := range allNodes {
		//do not consider nodes that have not been made available for provisioning yet
		if !isAvailableProvisionState[node.StableProvisionState()] {
			continue
		}

		matched := false
		for _, flavor := range flavors {
			if node.Matches(flavor) {
				logg.Debug("Ironic node %q (%s) matches flavor %s", node.Name, node.ID, flavor)

				data := result[p.ftt.LimesResourceNameForFlavor(flavor)]
				data.Capacity++

				var nodeAZ string
				if match := cpNodeNameRx.FindStringSubmatch(node.Name); match != nil {
					//special case as explained above (near definition of `cpNodeNameRx`)
					nodeAZ = match[1]
				} else if match := nodeNameRx.FindStringSubmatch(node.Name); match != nil {
					nodeAZ = azForHostStub[match[1]]
					if nodeAZ == "" {
						logg.Info("Ironic node %q (%s) does not match any compute host from host aggregates", node.Name, node.ID)
						nodeAZ = "unknown"
					}
				} else {
					logg.Error(`Ironic node %q (%s) does not match the "nodeXXX-{bm,bb,ap,md,st,swf}YYY" naming convention`, node.Name, node.ID)
				}

				if _, ok := data.CapacityPerAZ[nodeAZ]; !ok {
					data.CapacityPerAZ[nodeAZ] = &core.CapacityDataForAZ{}
				}
				data.CapacityPerAZ[nodeAZ].Capacity++
				if node.StableProvisionState() == "active" {
					data.CapacityPerAZ[nodeAZ].Usage++
				}

				if p.reportSubcapacities {
					sub := map[string]interface{}{
						"id":              node.ID,
						"name":            node.Name,
						"provision_state": node.ProvisionState,
					}
					if node.TargetProvisionState != nil && *node.TargetProvisionState != "" {
						sub["target_provision_state"] = *node.TargetProvisionState
					}
					if nodeAZ != "" {
						sub["availability_zone"] = nodeAZ
					}
					if node.Properties.MemoryMiB > 0 {
						sub["ram"] = limes.ValueWithUnit{Unit: limes.UnitMebibytes, Value: uint64(node.Properties.MemoryMiB)}
					}
					if node.Properties.DiskGiB > 0 {
						sub["disk"] = limes.ValueWithUnit{Unit: limes.UnitGibibytes, Value: uint64(node.Properties.DiskGiB)}
					}
					if node.Properties.Cores > 0 {
						sub["cores"] = uint64(node.Properties.Cores)
					}
					if node.Properties.SerialNumber != "" {
						sub["serial"] = node.Properties.SerialNumber
					}
					if node.InstanceID != nil && *node.InstanceID != "" {
						sub["instance_id"] = *node.InstanceID
					}
					data.Subcapacities = append(data.Subcapacities, sub)
				}

				matched = true
				break
			}
		}
		if !matched {
			logg.Error("Ironic node %q (%s) does not match any baremetal flavor", node.Name, node.ID)
			unmatchedCounter++
		}
	}

	//remove pointers from `result`
	result2 := make(map[string]core.CapacityData, len(result))
	for resourceName, data := range result {
		result2[resourceName] = *data
	}

	serializedMetrics, err := json.Marshal(capacitySapccIronicSerializedMetrics{
		UnmatchedNodeCount: unmatchedCounter,
	})
	if err != nil {
		return nil, "", err
	}

	return map[string]map[string]core.CapacityData{"compute": result2}, string(serializedMetrics), nil
}

var ironicUnmatchedNodesGauge = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "limes_unmatched_ironic_nodes",
		Help: "Number of available/active Ironic nodes without matching flavor.",
	},
	[]string{"os_cluster"},
)

//DescribeMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) DescribeMetrics(ch chan<- *prometheus.Desc) {
	ironicUnmatchedNodesGauge.Describe(ch)
}

//CollectMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) CollectMetrics(ch chan<- prometheus.Metric, clusterID, serializedMetrics string) error {
	if serializedMetrics == "" {
		return nil
	}
	var metrics capacitySapccIronicSerializedMetrics
	err := json.Unmarshal([]byte(serializedMetrics), &metrics)
	if err != nil {
		return err
	}

	descCh := make(chan *prometheus.Desc, 1)
	ironicUnmatchedNodesGauge.Describe(descCh)
	ironicUnmatchedNodesDesc := <-descCh

	ch <- prometheus.MustNewConstMetric(
		ironicUnmatchedNodesDesc,
		prometheus.GaugeValue, float64(metrics.UnmatchedNodeCount),
		clusterID,
	)
	return nil
}

func (p *capacitySapccIronicPlugin) getIronicFlavorNames(novaClient *gophercloud.ServiceClient) ([]string, error) {
	//which flavors have separate instance quota?
	flavorNames, err := p.ftt.ListFlavorsWithSeparateInstanceQuota(novaClient)
	if err != nil {
		return nil, err
	}
	isRelevantFlavorName := make(map[string]bool, len(flavorNames))
	for _, flavorName := range flavorNames {
		isRelevantFlavorName[flavorName] = true
	}

	//check flavor relevancy
	var result []string
	err = flavorsmodule.ListDetail(novaClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		flavors, err := flavorsmodule.ExtractFlavors(page)
		if err != nil {
			return false, err
		}

		for _, flavor := range flavors {
			if isRelevantFlavorName[flavor.Name] {
				result = append(result, flavor.Name)
			}
		}
		return true, nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (n ironicNode) Matches(flavorName string) bool {
	if n.ResourceClass != nil {
		return *n.ResourceClass == flavorName
	}
	return false
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package validation

import (
	"errors"
	"fmt"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apiserver/pkg/authentication/serviceaccount"
	"k8s.io/apiserver/pkg/authentication/user"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/kubernetes/pkg/apis/rbac"
)

type AuthorizationRuleResolver interface {
	// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding.  The passed namespace should be the namepsace
	// of the role binding, the empty string if a cluster role binding.
	GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)

	// RulesFor returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
	// PolicyRules may not be complete, but it contains all retrievable rules.  This is done because policy rules are purely additive and policy determinations
	// can be made on the basis of those rules that are found.
	RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error)
}

// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx genericapirequest.Context, ruleResolver AuthorizationRuleResolver, rules []rbac.PolicyRule) error {
	ruleResolutionErrors := []error{}

	user, ok := genericapirequest.UserFrom(ctx)
	if !ok {
		return fmt.Errorf("no user on context")
	}
	namespace, _ := genericapirequest.NamespaceFrom(ctx)

	ownerRules, err := ruleResolver.RulesFor(user, namespace)
	if err != nil {
		// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	ownerRightsCover, missingRights := Covers(ownerRules, rules)
	if !ownerRightsCover {
		user, _ := genericapirequest.UserFrom(ctx)
		return apierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
	}
	return nil
}

type DefaultRuleResolver struct {
	roleGetter               RoleGetter
	roleBindingLister        RoleBindingLister
	clusterRoleGetter        ClusterRoleGetter
	clusterRoleBindingLister ClusterRoleBindingLister
}

func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver {
	return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister}
}

type RoleGetter interface {
	GetRole(namespace, name string) (*rbac.Role, error)
}

type RoleBindingLister interface {
	ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error)
}

type ClusterRoleGetter interface {
	GetClusterRole(name string) (*rbac.ClusterRole, error)
}

type ClusterRoleBindingLister interface {
	ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error)
}

func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error) {
	policyRules := []rbac.PolicyRule{}
	errorlist := []error{}

	if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
		errorlist = append(errorlist, err)

	} else {
		for _, clusterRoleBinding := range clusterRoleBindings {
			if !appliesTo(user, clusterRoleBinding.Subjects, "") {
				continue
			}
			rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
			if err != nil {
				errorlist = append(errorlist, err)
				continue
			}
			policyRules = append(policyRules, rules...)
		}
	}

	if len(namespace) > 0 {
		if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
			errorlist = append(errorlist, err)

		} else {
			for _, roleBinding := range roleBindings {
				if !appliesTo(user, roleBinding.Subjects, namespace) {
					continue
				}
				rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
				if err != nil {
					errorlist = append(errorlist, err)
					continue
				}
				policyRules = append(policyRules, rules...)
			}
		}
	}

	return policyRules, utilerrors.NewAggregate(errorlist)
}

// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbac.RoleRef, bindingNamespace string) ([]rbac.PolicyRule, error) {
	switch kind := rbac.RoleRefGroupKind(roleRef); kind {
	case rbac.Kind("Role"):
		role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name)
		if err != nil {
			return nil, err
		}
		return role.Rules, nil

	case rbac.Kind("ClusterRole"):
		clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name)
		if err != nil {
			return nil, err
		}
		return clusterRole.Rules, nil

	default:
		return nil, fmt.Errorf("unsupported role reference kind: %q", kind)
	}
}
func appliesTo(user user.Info, bindingSubjects []rbac.Subject, namespace string) bool {
	for _, bindingSubject := range bindingSubjects {
		if appliesToUser(user, bindingSubject, namespace) {
			return true
		}
	}
	return false
}

func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool {
	switch subject.Kind {
	case rbac.UserKind:
		return user.GetName() == subject.Name

	case rbac.GroupKind:
		return has(user.GetGroups(), subject.Name)

	case rbac.ServiceAccountKind:
		// default the namespace to namespace we're working in if its available.  This allows rolebindings that reference
		// SAs in th local namespace to avoid having to qualify them.
		saNamespace := namespace
		if len(subject.Namespace) > 0 {
			saNamespace = subject.Namespace
		}
		if len(saNamespace) == 0 {
			return false
		}
		return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName()
	default:
		return false
	}
}

// NewTestRuleResolver returns a rule resolver from lists of role objects.
func NewTestRuleResolver(roles []*rbac.Role, roleBindings []*rbac.RoleBinding, clusterRoles []*rbac.ClusterRole, clusterRoleBindings []*rbac.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
	r := StaticRoles{
		roles:               roles,
		roleBindings:        roleBindings,
		clusterRoles:        clusterRoles,
		clusterRoleBindings: clusterRoleBindings,
	}
	return newMockRuleResolver(&r), &r
}

func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
	return NewDefaultRuleResolver(r, r, r, r)
}

// StaticRoles is a rule resolver that resolves from lists of role objects.
type StaticRoles struct {
	roles               []*rbac.Role
	roleBindings        []*rbac.RoleBinding
	clusterRoles        []*rbac.ClusterRole
	clusterRoleBindings []*rbac.ClusterRoleBinding
}

func (r *StaticRoles) GetRole(namespace, name string) (*rbac.Role, error) {
	if len(namespace) == 0 {
		return nil, errors.New("must provide namespace when getting role")
	}
	for _, role := range r.roles {
		if role.Namespace == namespace && role.Name == name {
			return role, nil
		}
	}
	return nil, errors.New("role not found")
}

func (r *StaticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) {
	for _, clusterRole := range r.clusterRoles {
		if clusterRole.Name == name {
			return clusterRole, nil
		}
	}
	return nil, errors.New("role not found")
}

func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
	if len(namespace) == 0 {
		return nil, errors.New("must provide namespace when listing role bindings")
	}

	roleBindingList := []*rbac.RoleBinding{}
	for _, roleBinding := range r.roleBindings {
		if roleBinding.Namespace != namespace {
			continue
		}
		// TODO(ericchiang): need to implement label selectors?
		roleBindingList = append(roleBindingList, roleBinding)
	}
	return roleBindingList, nil
}

func (r *StaticRoles) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
	return r.clusterRoleBindings, nil
}
<commit_msg>remove extra function call<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package validation

import (
	"errors"
	"fmt"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apiserver/pkg/authentication/serviceaccount"
	"k8s.io/apiserver/pkg/authentication/user"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/kubernetes/pkg/apis/rbac"
)

type AuthorizationRuleResolver interface {
	// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding.  The passed namespace should be the namepsace
	// of the role binding, the empty string if a cluster role binding.
	GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)

	// RulesFor returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
	// PolicyRules may not be complete, but it contains all retrievable rules.  This is done because policy rules are purely additive and policy determinations
	// can be made on the basis of those rules that are found.
	RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error)
}

// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx genericapirequest.Context, ruleResolver AuthorizationRuleResolver, rules []rbac.PolicyRule) error {
	ruleResolutionErrors := []error{}

	user, ok := genericapirequest.UserFrom(ctx)
	if !ok {
		return fmt.Errorf("no user on context")
	}
	namespace, _ := genericapirequest.NamespaceFrom(ctx)

	ownerRules, err := ruleResolver.RulesFor(user, namespace)
	if err != nil {
		// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	ownerRightsCover, missingRights := Covers(ownerRules, rules)
	if !ownerRightsCover {
		return apierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
	}
	return nil
}

type DefaultRuleResolver struct {
	roleGetter               RoleGetter
	roleBindingLister        RoleBindingLister
	clusterRoleGetter        ClusterRoleGetter
	clusterRoleBindingLister ClusterRoleBindingLister
}

func NewDefaultRuleResolver(roleGetter RoleGetter, roleBindingLister RoleBindingLister, clusterRoleGetter ClusterRoleGetter, clusterRoleBindingLister ClusterRoleBindingLister) *DefaultRuleResolver {
	return &DefaultRuleResolver{roleGetter, roleBindingLister, clusterRoleGetter, clusterRoleBindingLister}
}

type RoleGetter interface {
	GetRole(namespace, name string) (*rbac.Role, error)
}

type RoleBindingLister interface {
	ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error)
}

type ClusterRoleGetter interface {
	GetClusterRole(name string) (*rbac.ClusterRole, error)
}

type ClusterRoleBindingLister interface {
	ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error)
}

func (r *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]rbac.PolicyRule, error) {
	policyRules := []rbac.PolicyRule{}
	errorlist := []error{}

	if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
		errorlist = append(errorlist, err)

	} else {
		for _, clusterRoleBinding := range clusterRoleBindings {
			if !appliesTo(user, clusterRoleBinding.Subjects, "") {
				continue
			}
			rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
			if err != nil {
				errorlist = append(errorlist, err)
				continue
			}
			policyRules = append(policyRules, rules...)
		}
	}

	if len(namespace) > 0 {
		if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
			errorlist = append(errorlist, err)

		} else {
			for _, roleBinding := range roleBindings {
				if !appliesTo(user, roleBinding.Subjects, namespace) {
					continue
				}
				rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
				if err != nil {
					errorlist = append(errorlist, err)
					continue
				}
				policyRules = append(policyRules, rules...)
			}
		}
	}

	return policyRules, utilerrors.NewAggregate(errorlist)
}

// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbac.RoleRef, bindingNamespace string) ([]rbac.PolicyRule, error) {
	switch kind := rbac.RoleRefGroupKind(roleRef); kind {
	case rbac.Kind("Role"):
		role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name)
		if err != nil {
			return nil, err
		}
		return role.Rules, nil

	case rbac.Kind("ClusterRole"):
		clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name)
		if err != nil {
			return nil, err
		}
		return clusterRole.Rules, nil

	default:
		return nil, fmt.Errorf("unsupported role reference kind: %q", kind)
	}
}
func appliesTo(user user.Info, bindingSubjects []rbac.Subject, namespace string) bool {
	for _, bindingSubject := range bindingSubjects {
		if appliesToUser(user, bindingSubject, namespace) {
			return true
		}
	}
	return false
}

func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool {
	switch subject.Kind {
	case rbac.UserKind:
		return user.GetName() == subject.Name

	case rbac.GroupKind:
		return has(user.GetGroups(), subject.Name)

	case rbac.ServiceAccountKind:
		// default the namespace to namespace we're working in if its available.  This allows rolebindings that reference
		// SAs in th local namespace to avoid having to qualify them.
		saNamespace := namespace
		if len(subject.Namespace) > 0 {
			saNamespace = subject.Namespace
		}
		if len(saNamespace) == 0 {
			return false
		}
		return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName()
	default:
		return false
	}
}

// NewTestRuleResolver returns a rule resolver from lists of role objects.
func NewTestRuleResolver(roles []*rbac.Role, roleBindings []*rbac.RoleBinding, clusterRoles []*rbac.ClusterRole, clusterRoleBindings []*rbac.ClusterRoleBinding) (AuthorizationRuleResolver, *StaticRoles) {
	r := StaticRoles{
		roles:               roles,
		roleBindings:        roleBindings,
		clusterRoles:        clusterRoles,
		clusterRoleBindings: clusterRoleBindings,
	}
	return newMockRuleResolver(&r), &r
}

func newMockRuleResolver(r *StaticRoles) AuthorizationRuleResolver {
	return NewDefaultRuleResolver(r, r, r, r)
}

// StaticRoles is a rule resolver that resolves from lists of role objects.
type StaticRoles struct {
	roles               []*rbac.Role
	roleBindings        []*rbac.RoleBinding
	clusterRoles        []*rbac.ClusterRole
	clusterRoleBindings []*rbac.ClusterRoleBinding
}

func (r *StaticRoles) GetRole(namespace, name string) (*rbac.Role, error) {
	if len(namespace) == 0 {
		return nil, errors.New("must provide namespace when getting role")
	}
	for _, role := range r.roles {
		if role.Namespace == namespace && role.Name == name {
			return role, nil
		}
	}
	return nil, errors.New("role not found")
}

func (r *StaticRoles) GetClusterRole(name string) (*rbac.ClusterRole, error) {
	for _, clusterRole := range r.clusterRoles {
		if clusterRole.Name == name {
			return clusterRole, nil
		}
	}
	return nil, errors.New("role not found")
}

func (r *StaticRoles) ListRoleBindings(namespace string) ([]*rbac.RoleBinding, error) {
	if len(namespace) == 0 {
		return nil, errors.New("must provide namespace when listing role bindings")
	}

	roleBindingList := []*rbac.RoleBinding{}
	for _, roleBinding := range r.roleBindings {
		if roleBinding.Namespace != namespace {
			continue
		}
		// TODO(ericchiang): need to implement label selectors?
		roleBindingList = append(roleBindingList, roleBinding)
	}
	return roleBindingList, nil
}

func (r *StaticRoles) ListClusterRoleBindings() ([]*rbac.ClusterRoleBinding, error) {
	return r.clusterRoleBindings, nil
}
<|endoftext|>