<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package v1alpha1

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

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

// InitializerConfiguration describes the configuration of initializers.
type InitializerConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Initializers is a list of resources and their default initializers
	// Order-sensitive.
	// When merging multiple InitializerConfigurations, we sort the initializers
	// from different InitializerConfigurations by the name of the
	// InitializerConfigurations; the order of the initializers from the same
	// InitializerConfiguration is preserved.
	// +patchMergeKey=name
	// +patchStrategy=merge
	// +optional
	Initializers []Initializer `json:"initializers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=initializers"`
}

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

// InitializerConfigurationList is a list of InitializerConfiguration.
type InitializerConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// List of InitializerConfiguration.
	Items []InitializerConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

// Initializer describes the name and the failure policy of an initializer, and
// what resources it applies to.
type Initializer struct {
	// Name is the identifier of the initializer. It will be added to the
	// object that needs to be initialized.
	// Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where
	// "alwayspullimages" is the name of the webhook, and kubernetes.io is the name
	// of the organization.
	// Required
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`

	// Rules describes what resources/subresources the initializer cares about.
	// The initializer cares about an operation if it matches _any_ Rule.
	// Rule.Resources must not include subresources.
	Rules []Rule `json:"rules,omitempty" protobuf:"bytes,2,rep,name=rules"`
}

// Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended
// to make sure that all the tuple expansions are valid.
type Rule struct {
	// APIGroups is the API groups the resources belong to. '*' is all groups.
	// If '*' is present, the length of the slice must be one.
	// Required.
	APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"`

	// APIVersions is the API versions the resources belong to. '*' is all versions.
	// If '*' is present, the length of the slice must be one.
	// Required.
	APIVersions []string `json:"apiVersions,omitempty" protobuf:"bytes,2,rep,name=apiVersions"`

	// Resources is a list of resources this rule applies to.
	//
	// For example:
	// 'pods' means pods.
	// 'pods/log' means the log subresource of pods.
	// '*' means all resources, but not subresources.
	// 'pods/*' means all subresources of pods.
	// '*/scale' means all scale subresources.
	// '*/*' means all resources and their subresources.
	//
	// If wildcard is present, the validation rule will ensure resources do not
	// overlap with each other.
	//
	// Depending on the enclosing object, subresources might not be allowed.
	// Required.
	Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
}

type FailurePolicyType string

const (
	// Ignore means the initializer is removed from the initializers list of an
	// object if the initializer is timed out.
	Ignore FailurePolicyType = "Ignore"
	// For 1.7, only "Ignore" is allowed. "Fail" will be allowed when the
	// extensible admission feature is beta.
	Fail FailurePolicyType = "Fail"
)

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

// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.
type ValidatingWebhookConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// Webhooks is a list of webhooks and the affected resources and operations.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge
	Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
}

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

// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.
type ValidatingWebhookConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// List of ValidatingWebhookConfiguration.
	Items []ValidatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

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

// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
type MutatingWebhookConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// Webhooks is a list of webhooks and the affected resources and operations.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge
	Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
}

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

// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.
type MutatingWebhookConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// List of MutatingWebhookConfiguration.
	Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

// Webhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct {
	// The name of the admission webhook.
	// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
	// "imagepolicy" is the name of the webhook, and kubernetes.io is the name
	// of the organization.
	// Required.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`

	// ClientConfig defines how to communicate with the hook.
	// Required
	ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"`

	// Rules describes what operations on what resources/subresources the webhook cares about.
	// The webhook cares about an operation if it matches _any_ Rule.
	Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"`

	// FailurePolicy defines how unrecognized errors from the admission endpoint are handled -
	// allowed values are Ignore or Fail. Defaults to Ignore.
	// +optional
	FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"`

	// NamespaceSelector decides whether to run the webhook on an object based
	// on whether the namespace for that object matches the selector. If the
	// object itself is a namespace, the matching is performed on
	// object.metadata.labels. If the object is other cluster scoped resource,
	// it is not subjected to the webhook.
	//
	// For example, to run the webhook on any objects whose namespace is not
	// associated with "runlevel" of "0" or "1";  you will set the selector as
	// follows:
	// "namespaceSelector": {
	//   "matchExpressions": [
	//     {
	//       "key": "runlevel",
	//       "operator": "NotIn",
	//       "values": [
	//         "0",
	//         "1"
	//       ]
	//     }
	//   ]
	// }
	//
	// If instead you want to only run the webhook on any objects whose
	// namespace is associated with the "environment" of "prod" or "staging";
	// you will set the selector as follows:
	// "namespaceSelector": {
	//   "matchExpressions": [
	//     {
	//       "key": "environment",
	//       "operator": "In",
	//       "values": [
	//         "prod",
	//         "staging"
	//       ]
	//     }
	//   ]
	// }
	//
	// See
	// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
	// for more examples of label selectors.
	//
	// Default to the empty LabelSelector, which matches everything.
	// +optional
	NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"`
}

// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
// sure that all the tuple expansions are valid.
type RuleWithOperations struct {
	// Operations is the operations the admission hook cares about - CREATE, UPDATE, or *
	// for all operations.
	// If '*' is present, the length of the slice must be one.
	// Required.
	Operations []OperationType `json:"operations,omitempty" protobuf:"bytes,1,rep,name=operations,casttype=OperationType"`
	// Rule is embedded, it describes other criteria of the rule, like
	// APIGroups, APIVersions, Resources, etc.
	Rule `json:",inline" protobuf:"bytes,2,opt,name=rule"`
}

type OperationType string

// The constants should be kept in sync with those defined in k8s.io/kubernetes/pkg/admission/interface.go.
const (
	OperationAll OperationType = "*"
	Create       OperationType = "CREATE"
	Update       OperationType = "UPDATE"
	Delete       OperationType = "DELETE"
	Connect      OperationType = "CONNECT"
)

// WebhookClientConfig contains the information to make a TLS
// connection with the webhook
type WebhookClientConfig struct {
	// `url` gives the location of the webhook, in standard URL form
	// (`[scheme://]host:port/path`). Exactly one of `url` or `service`
	// must be specified.
	//
	// The `host` should not refer to a service running in the cluster; use
	// the `service` field instead. The host might be resolved via external
	// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
	// in-cluster DNS as that would be a layering violation). `host` may
	// also be an IP address.
	//
	// Please note that using `localhost` or `127.0.0.1` as a `host` is
	// risky unless you take great care to run this webhook on all hosts
	// which run an apiserver which might need to make calls to this
	// webhook. Such installs are likely to be non-portable, i.e., not easy
	// to turn up in a new cluster.
	//
	// If the scheme is present, it must be "https://".
	//
	// A path is optional, and if present may be any string permissible in
	// a URL. You may use the path to pass an arbitrary string to the
	// webhook, for example, a cluster identifier.
	//
	// +optional
	URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"`

	// `service` is a reference to the service for this webhook. Either
	// `service` or `url` must be specified.
	//
	// If the webhook is running within the cluster, then you should use `service`.
	//
	// If there is only one port open for the service, that port will be
	// used. If there are multiple ports open, port 443 will be used if it
	// is open, otherwise it is an error.
	//
	// +optional
	Service *ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"`

	// `caBundle` is a PEM encoded CA bundle which will be used to validate
	// the webhook's server certificate.
	// Required.
	CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"`
}

// ServiceReference holds a reference to Service.legacy.k8s.io
type ServiceReference struct {
	// `namespace` is the namespace of the service.
	// Required
	Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"`
	// `name` is the name of the service.
	// Required
	Name string `json:"name" protobuf:"bytes,2,opt,name=name"`

	// `path` is an optional URL path which will be sent in any request to
	// this service.
	// +optional
	Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"`
}
<commit_msg>fix docs and validation<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package v1alpha1

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

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

// InitializerConfiguration describes the configuration of initializers.
type InitializerConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Initializers is a list of resources and their default initializers
	// Order-sensitive.
	// When merging multiple InitializerConfigurations, we sort the initializers
	// from different InitializerConfigurations by the name of the
	// InitializerConfigurations; the order of the initializers from the same
	// InitializerConfiguration is preserved.
	// +patchMergeKey=name
	// +patchStrategy=merge
	// +optional
	Initializers []Initializer `json:"initializers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=initializers"`
}

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

// InitializerConfigurationList is a list of InitializerConfiguration.
type InitializerConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// List of InitializerConfiguration.
	Items []InitializerConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

// Initializer describes the name and the failure policy of an initializer, and
// what resources it applies to.
type Initializer struct {
	// Name is the identifier of the initializer. It will be added to the
	// object that needs to be initialized.
	// Name should be fully qualified, e.g., alwayspullimages.kubernetes.io, where
	// "alwayspullimages" is the name of the webhook, and kubernetes.io is the name
	// of the organization.
	// Required
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`

	// Rules describes what resources/subresources the initializer cares about.
	// The initializer cares about an operation if it matches _any_ Rule.
	// Rule.Resources must not include subresources.
	Rules []Rule `json:"rules,omitempty" protobuf:"bytes,2,rep,name=rules"`
}

// Rule is a tuple of APIGroups, APIVersion, and Resources.It is recommended
// to make sure that all the tuple expansions are valid.
type Rule struct {
	// APIGroups is the API groups the resources belong to. '*' is all groups.
	// If '*' is present, the length of the slice must be one.
	// Required.
	APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"`

	// APIVersions is the API versions the resources belong to. '*' is all versions.
	// If '*' is present, the length of the slice must be one.
	// Required.
	APIVersions []string `json:"apiVersions,omitempty" protobuf:"bytes,2,rep,name=apiVersions"`

	// Resources is a list of resources this rule applies to.
	//
	// For example:
	// 'pods' means pods.
	// 'pods/log' means the log subresource of pods.
	// '*' means all resources, but not subresources.
	// 'pods/*' means all subresources of pods.
	// '*/scale' means all scale subresources.
	// '*/*' means all resources and their subresources.
	//
	// If wildcard is present, the validation rule will ensure resources do not
	// overlap with each other.
	//
	// Depending on the enclosing object, subresources might not be allowed.
	// Required.
	Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
}

type FailurePolicyType string

const (
	// Ignore means the initializer is removed from the initializers list of an
	// object if the initializer is timed out.
	Ignore FailurePolicyType = "Ignore"
	// For 1.7, only "Ignore" is allowed. "Fail" will be allowed when the
	// extensible admission feature is beta.
	Fail FailurePolicyType = "Fail"
)

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

// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.
type ValidatingWebhookConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// Webhooks is a list of webhooks and the affected resources and operations.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge
	Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
}

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

// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration.
type ValidatingWebhookConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// List of ValidatingWebhookConfiguration.
	Items []ValidatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

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

// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
type MutatingWebhookConfiguration struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// Webhooks is a list of webhooks and the affected resources and operations.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge
	Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
}

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

// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.
type MutatingWebhookConfigurationList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// List of MutatingWebhookConfiguration.
	Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}

// Webhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct {
	// The name of the admission webhook.
	// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
	// "imagepolicy" is the name of the webhook, and kubernetes.io is the name
	// of the organization.
	// Required.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`

	// ClientConfig defines how to communicate with the hook.
	// Required
	ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"`

	// Rules describes what operations on what resources/subresources the webhook cares about.
	// The webhook cares about an operation if it matches _any_ Rule.
	Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"`

	// FailurePolicy defines how unrecognized errors from the admission endpoint are handled -
	// allowed values are Ignore or Fail. Defaults to Ignore.
	// +optional
	FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"`

	// NamespaceSelector decides whether to run the webhook on an object based
	// on whether the namespace for that object matches the selector. If the
	// object itself is a namespace, the matching is performed on
	// object.metadata.labels. If the object is other cluster scoped resource,
	// it is not subjected to the webhook.
	//
	// For example, to run the webhook on any objects whose namespace is not
	// associated with "runlevel" of "0" or "1";  you will set the selector as
	// follows:
	// "namespaceSelector": {
	//   "matchExpressions": [
	//     {
	//       "key": "runlevel",
	//       "operator": "NotIn",
	//       "values": [
	//         "0",
	//         "1"
	//       ]
	//     }
	//   ]
	// }
	//
	// If instead you want to only run the webhook on any objects whose
	// namespace is associated with the "environment" of "prod" or "staging";
	// you will set the selector as follows:
	// "namespaceSelector": {
	//   "matchExpressions": [
	//     {
	//       "key": "environment",
	//       "operator": "In",
	//       "values": [
	//         "prod",
	//         "staging"
	//       ]
	//     }
	//   ]
	// }
	//
	// See
	// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
	// for more examples of label selectors.
	//
	// Default to the empty LabelSelector, which matches everything.
	// +optional
	NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"`
}

// RuleWithOperations is a tuple of Operations and Resources. It is recommended to make
// sure that all the tuple expansions are valid.
type RuleWithOperations struct {
	// Operations is the operations the admission hook cares about - CREATE, UPDATE, or *
	// for all operations.
	// If '*' is present, the length of the slice must be one.
	// Required.
	Operations []OperationType `json:"operations,omitempty" protobuf:"bytes,1,rep,name=operations,casttype=OperationType"`
	// Rule is embedded, it describes other criteria of the rule, like
	// APIGroups, APIVersions, Resources, etc.
	Rule `json:",inline" protobuf:"bytes,2,opt,name=rule"`
}

type OperationType string

// The constants should be kept in sync with those defined in k8s.io/kubernetes/pkg/admission/interface.go.
const (
	OperationAll OperationType = "*"
	Create       OperationType = "CREATE"
	Update       OperationType = "UPDATE"
	Delete       OperationType = "DELETE"
	Connect      OperationType = "CONNECT"
)

// WebhookClientConfig contains the information to make a TLS
// connection with the webhook
type WebhookClientConfig struct {
	// `url` gives the location of the webhook, in standard URL form
	// (`[scheme://]host:port/path`). Exactly one of `url` or `service`
	// must be specified.
	//
	// The `host` should not refer to a service running in the cluster; use
	// the `service` field instead. The host might be resolved via external
	// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
	// in-cluster DNS as that would be a layering violation). `host` may
	// also be an IP address.
	//
	// Please note that using `localhost` or `127.0.0.1` as a `host` is
	// risky unless you take great care to run this webhook on all hosts
	// which run an apiserver which might need to make calls to this
	// webhook. Such installs are likely to be non-portable, i.e., not easy
	// to turn up in a new cluster.
	//
	// The scheme must be "https"; the URL must begin with "https://".
	//
	// A path is optional, and if present may be any string permissible in
	// a URL. You may use the path to pass an arbitrary string to the
	// webhook, for example, a cluster identifier.
	//
	// Attempting to use a user or basic auth e.g. "user:password@" is not
	// allowed. Fragments ("#...") and query parameters ("?...") are not
	// allowed, either.
	//
	// +optional
	URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"`

	// `service` is a reference to the service for this webhook. Either
	// `service` or `url` must be specified.
	//
	// If the webhook is running within the cluster, then you should use `service`.
	//
	// If there is only one port open for the service, that port will be
	// used. If there are multiple ports open, port 443 will be used if it
	// is open, otherwise it is an error.
	//
	// +optional
	Service *ServiceReference `json:"service" protobuf:"bytes,1,opt,name=service"`

	// `caBundle` is a PEM encoded CA bundle which will be used to validate
	// the webhook's server certificate.
	// Required.
	CABundle []byte `json:"caBundle" protobuf:"bytes,2,opt,name=caBundle"`
}

// ServiceReference holds a reference to Service.legacy.k8s.io
type ServiceReference struct {
	// `namespace` is the namespace of the service.
	// Required
	Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"`
	// `name` is the name of the service.
	// Required
	Name string `json:"name" protobuf:"bytes,2,opt,name=name"`

	// `path` is an optional URL path which will be sent in any request to
	// this service.
	// +optional
	Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"`
}
<|endoftext|>


<commit_before>// Copyright 2015 CloudAwan LLC
//
// 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 identity

import (
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/context"
	"github.com/cloudawan/cloudone_gui/controllers/utility/guimessagedisplay"
	"github.com/cloudawan/cloudone_utility/audit"
	"github.com/cloudawan/cloudone_utility/rbac"
	"github.com/cloudawan/cloudone_utility/restclient"
)

const (
	loginPageURL  = "/gui/login"
	logoutPageURL = "/gui/logout"
)

func FilterUser(ctx *context.Context) {
	if (ctx.Input.IsGet() || ctx.Input.IsPost()) && (ctx.Input.URL() == loginPageURL || ctx.Input.URL() == logoutPageURL) {
		// Don't redirect itself to prevent the circle
	} else {
		user, ok := ctx.Input.Session("user").(*rbac.User)

		if ok == false {
			if guiMessage := guimessagedisplay.GetGUIMessageFromContext(ctx); guiMessage != nil {
				guiMessage.AddDanger("Username or password is incorrect")
			}
			ctx.Redirect(302, loginPageURL)
		} else {
			// Authorize
			if user.HasPermission(componentName, ctx.Input.Method(), ctx.Input.URL()) == false {
				if guiMessage := guimessagedisplay.GetGUIMessageFromContext(ctx); guiMessage != nil {
					guiMessage.AddDanger("User is not authorized to this page. Please use another user with priviledge.")
				}
				ctx.Redirect(302, loginPageURL)
			}

			// Resource check is in another place since GUI doesn't place the resource name in url

			// Audit log
			go func() {
				sendAuditLog(ctx, user.Name, true)
			}()
		}
	}
}

func sendAuditLog(ctx *context.Context, userName string, saveParameter bool) (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			returnedError = err.(error)
		}
	}()

	cloudoneAnalysisProtocol := beego.AppConfig.String("cloudoneAnalysisProtocol")
	cloudoneAnalysisHost := beego.AppConfig.String("cloudoneAnalysisHost")
	cloudoneAnalysisPort := beego.AppConfig.String("cloudoneAnalysisPort")

	tokenHeaderMap, tokenHeaderMapOK := ctx.Input.Session("tokenHeaderMap").(map[string]string)
	requestURI := ctx.Input.URI()
	method := ctx.Input.Method()
	path := ctx.Input.URL()
	remoteAddress := ctx.Request.RemoteAddr
	queryParameterMap := ctx.Request.Form

	proxySlice := ctx.Input.Proxy()
	if proxySlice != nil && len(proxySlice) > 0 {
		proxySlice = append(proxySlice, remoteAddress)
		remoteAddress = fmt.Sprintf("%v", proxySlice)
	}

	if saveParameter == false {
		// Not to save parameter, such as password
		requestURI = path
		queryParameterMap = nil
	}

	// Header is not used since the header has no useful information for now
	// Body is not used since the backend component will record again.
	// Path is not used since the backend component will record again.
	auditLog := audit.CreateAuditLog(componentName, path, userName, remoteAddress, queryParameterMap, nil, method, requestURI, "", nil)

	if tokenHeaderMapOK {
		url := cloudoneAnalysisProtocol + "://" + cloudoneAnalysisHost + ":" + cloudoneAnalysisPort + "/api/v1/auditlogs"

		_, err := restclient.RequestPost(url, auditLog, tokenHeaderMap, false)
		if err != nil {
			if guiMessage := guimessagedisplay.GetGUIMessageFromContext(ctx); guiMessage != nil {
				guiMessage.AddDanger("Fail to send audit log with error " + err.Error())
			}
			return err
		}
	}

	return nil
}
<commit_msg>Not to log the audit error since it is logged<commit_after>// Copyright 2015 CloudAwan LLC
//
// 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 identity

import (
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/context"
	"github.com/cloudawan/cloudone_gui/controllers/utility/guimessagedisplay"
	"github.com/cloudawan/cloudone_utility/audit"
	"github.com/cloudawan/cloudone_utility/rbac"
	"github.com/cloudawan/cloudone_utility/restclient"
)

const (
	loginPageURL  = "/gui/login"
	logoutPageURL = "/gui/logout"
)

func FilterUser(ctx *context.Context) {
	if (ctx.Input.IsGet() || ctx.Input.IsPost()) && (ctx.Input.URL() == loginPageURL || ctx.Input.URL() == logoutPageURL) {
		// Don't redirect itself to prevent the circle
	} else {
		user, ok := ctx.Input.Session("user").(*rbac.User)

		if ok == false {
			if guiMessage := guimessagedisplay.GetGUIMessageFromContext(ctx); guiMessage != nil {
				guiMessage.AddDanger("Username or password is incorrect")
			}
			ctx.Redirect(302, loginPageURL)
		} else {
			// Authorize
			if user.HasPermission(componentName, ctx.Input.Method(), ctx.Input.URL()) == false {
				if guiMessage := guimessagedisplay.GetGUIMessageFromContext(ctx); guiMessage != nil {
					guiMessage.AddDanger("User is not authorized to this page. Please use another user with priviledge.")
				}
				ctx.Redirect(302, loginPageURL)
			}

			// Resource check is in another place since GUI doesn't place the resource name in url

			// Audit log
			go func() {
				sendAuditLog(ctx, user.Name, true)
			}()
		}
	}
}

func sendAuditLog(ctx *context.Context, userName string, saveParameter bool) (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			returnedError = err.(error)
		}
	}()

	cloudoneAnalysisProtocol := beego.AppConfig.String("cloudoneAnalysisProtocol")
	cloudoneAnalysisHost := beego.AppConfig.String("cloudoneAnalysisHost")
	cloudoneAnalysisPort := beego.AppConfig.String("cloudoneAnalysisPort")

	tokenHeaderMap, tokenHeaderMapOK := ctx.Input.Session("tokenHeaderMap").(map[string]string)
	requestURI := ctx.Input.URI()
	method := ctx.Input.Method()
	path := ctx.Input.URL()
	remoteAddress := ctx.Request.RemoteAddr
	queryParameterMap := ctx.Request.Form

	proxySlice := ctx.Input.Proxy()
	if proxySlice != nil && len(proxySlice) > 0 {
		proxySlice = append(proxySlice, remoteAddress)
		remoteAddress = fmt.Sprintf("%v", proxySlice)
	}

	if saveParameter == false {
		// Not to save parameter, such as password
		requestURI = path
		queryParameterMap = nil
	}

	// Header is not used since the header has no useful information for now
	// Body is not used since the backend component will record again.
	// Path is not used since the backend component will record again.
	auditLog := audit.CreateAuditLog(componentName, path, userName, remoteAddress, queryParameterMap, nil, method, requestURI, "", nil)

	if tokenHeaderMapOK {
		url := cloudoneAnalysisProtocol + "://" + cloudoneAnalysisHost + ":" + cloudoneAnalysisPort + "/api/v1/auditlogs"

		restclient.RequestPost(url, auditLog, tokenHeaderMap, false)
		// err is logged in analysis so don't need to here
	}

	return nil
}
<|endoftext|>


<commit_before>package core

import (
	"log"
	"os"

	"github.com/mdlayher/wavepipe/common"
	"github.com/mdlayher/wavepipe/config"
)

// App is the application's name
const App = "wavepipe"

// Version is the application's version
const Version = "git-dev-subsonic-api"

// ConfigPath is the application's configuration path
var ConfigPath string

// Manager is responsible for coordinating the application
func Manager(killChan chan struct{}, exitChan chan int) {
	log.Printf("manager: initializing %s %s...", App, Version)

	// Gather information about the operating system
	stat, err := common.OSInfo()
	if err != nil {
		log.Println("manager: could not get operating system info:", err)
	} else {
		log.Printf("manager: %s - %s_%s (%d CPU) [pid: %d]", stat.Hostname, stat.Platform, stat.Architecture, stat.NumCPU, stat.PID)
	}

	// Set configuration (if default path used, config will be created)
	config.C = new(config.JSONFileConfig)
	if err := config.C.Use(ConfigPath); err != nil {
		log.Fatalf("manager: could not use config: %s, %s", ConfigPath, err.Error())
	}

	// Load configuration from specified source
	conf, err := config.C.Load()
	if err != nil {
		log.Fatalf("manager: could not load config: %s, %s", ConfigPath, err.Error())
	}

	// Check valid media folder, unless in test mode
	folder := conf.Media()
	if os.Getenv("WAVEPIPE_TEST") != "1" {
		// Check empty folder
		if folder == "" {
			log.Fatalf("manager: no media folder set in config: %s", ConfigPath)
		} else if _, err := os.Stat(folder); err != nil {
			// Check file existence
			log.Fatalf("manager: invalid media folder set in config: %s", err.Error())
		}
	}

	// Launch database manager to handle database/ORM connections
	dbLaunchChan := make(chan struct{})
	dbKillChan := make(chan struct{})
	go dbManager(*conf, dbLaunchChan, dbKillChan)

	// Wait for database to be fully ready before other operations start
	<-dbLaunchChan

	// Launch cron manager to handle timed events
	cronKillChan := make(chan struct{})
	go cronManager(cronKillChan)

	// Launch filesystem manager to handle file scanning
	fsKillChan := make(chan struct{})
	go fsManager(folder, fsKillChan)

	// Launch HTTP API server
	apiKillChan := make(chan struct{})
	go apiRouter(apiKillChan)

	// Launch transcode manager to handle ffmpeg and file transcoding
	transcodeKillChan := make(chan struct{})
	go transcodeManager(transcodeKillChan)

	// Wait for termination signal
	for {
		select {
		// Trigger a graceful shutdown
		case <-killChan:
			log.Println("manager: triggering graceful shutdown, press Ctrl+C again to force halt")

			// Stop transcodes, wait for confirmation
			transcodeKillChan <- struct{}{}
			<-transcodeKillChan
			close(transcodeKillChan)

			// Stop API, wait for confirmation
			apiKillChan <- struct{}{}
			<-apiKillChan
			close(apiKillChan)

			// Stop filesystem, wait for confirmation
			fsKillChan <- struct{}{}
			<-fsKillChan
			close(fsKillChan)

			// Stop database, wait for confirmation
			dbKillChan <- struct{}{}
			<-dbKillChan
			close(dbKillChan)

			// Stop cron, wait for confirmation
			cronKillChan <- struct{}{}
			<-cronKillChan
			close(cronKillChan)

			// Exit gracefully
			log.Println("manager: stopped!")
			exitChan <- 0
		}
	}
}
<commit_msg>Fix core.Version branch<commit_after>package core

import (
	"log"
	"os"

	"github.com/mdlayher/wavepipe/common"
	"github.com/mdlayher/wavepipe/config"
)

// App is the application's name
const App = "wavepipe"

// Version is the application's version
const Version = "git-master"

// ConfigPath is the application's configuration path
var ConfigPath string

// Manager is responsible for coordinating the application
func Manager(killChan chan struct{}, exitChan chan int) {
	log.Printf("manager: initializing %s %s...", App, Version)

	// Gather information about the operating system
	stat, err := common.OSInfo()
	if err != nil {
		log.Println("manager: could not get operating system info:", err)
	} else {
		log.Printf("manager: %s - %s_%s (%d CPU) [pid: %d]", stat.Hostname, stat.Platform, stat.Architecture, stat.NumCPU, stat.PID)
	}

	// Set configuration (if default path used, config will be created)
	config.C = new(config.JSONFileConfig)
	if err := config.C.Use(ConfigPath); err != nil {
		log.Fatalf("manager: could not use config: %s, %s", ConfigPath, err.Error())
	}

	// Load configuration from specified source
	conf, err := config.C.Load()
	if err != nil {
		log.Fatalf("manager: could not load config: %s, %s", ConfigPath, err.Error())
	}

	// Check valid media folder, unless in test mode
	folder := conf.Media()
	if os.Getenv("WAVEPIPE_TEST") != "1" {
		// Check empty folder
		if folder == "" {
			log.Fatalf("manager: no media folder set in config: %s", ConfigPath)
		} else if _, err := os.Stat(folder); err != nil {
			// Check file existence
			log.Fatalf("manager: invalid media folder set in config: %s", err.Error())
		}
	}

	// Launch database manager to handle database/ORM connections
	dbLaunchChan := make(chan struct{})
	dbKillChan := make(chan struct{})
	go dbManager(*conf, dbLaunchChan, dbKillChan)

	// Wait for database to be fully ready before other operations start
	<-dbLaunchChan

	// Launch cron manager to handle timed events
	cronKillChan := make(chan struct{})
	go cronManager(cronKillChan)

	// Launch filesystem manager to handle file scanning
	fsKillChan := make(chan struct{})
	go fsManager(folder, fsKillChan)

	// Launch HTTP API server
	apiKillChan := make(chan struct{})
	go apiRouter(apiKillChan)

	// Launch transcode manager to handle ffmpeg and file transcoding
	transcodeKillChan := make(chan struct{})
	go transcodeManager(transcodeKillChan)

	// Wait for termination signal
	for {
		select {
		// Trigger a graceful shutdown
		case <-killChan:
			log.Println("manager: triggering graceful shutdown, press Ctrl+C again to force halt")

			// Stop transcodes, wait for confirmation
			transcodeKillChan <- struct{}{}
			<-transcodeKillChan
			close(transcodeKillChan)

			// Stop API, wait for confirmation
			apiKillChan <- struct{}{}
			<-apiKillChan
			close(apiKillChan)

			// Stop filesystem, wait for confirmation
			fsKillChan <- struct{}{}
			<-fsKillChan
			close(fsKillChan)

			// Stop database, wait for confirmation
			dbKillChan <- struct{}{}
			<-dbKillChan
			close(dbKillChan)

			// Stop cron, wait for confirmation
			cronKillChan <- struct{}{}
			<-cronKillChan
			close(cronKillChan)

			// Exit gracefully
			log.Println("manager: stopped!")
			exitChan <- 0
		}
	}
}
<|endoftext|>


<commit_before>package handshake

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"

	"github.com/lucas-clemente/quic-go/fuzzing/internal/helper"
	"github.com/lucas-clemente/quic-go/internal/handshake"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/internal/wire"
)

var cert *tls.Certificate
var certPool *x509.CertPool
var sessionTicketKey = [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}

func init() {
	priv, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		log.Fatal(err)
	}
	cert, certPool, err = helper.GenerateCertificate(priv)
	if err != nil {
		log.Fatal(err)
	}
}

type messageType uint8

// TLS handshake message types.
const (
	typeClientHello         messageType = 1
	typeServerHello         messageType = 2
	typeNewSessionTicket    messageType = 4
	typeEncryptedExtensions messageType = 8
	typeCertificate         messageType = 11
	typeCertificateRequest  messageType = 13
	typeCertificateVerify   messageType = 15
	typeFinished            messageType = 20
)

func (m messageType) String() string {
	switch m {
	case typeClientHello:
		return "ClientHello"
	case typeServerHello:
		return "ServerHello"
	case typeNewSessionTicket:
		return "NewSessionTicket"
	case typeEncryptedExtensions:
		return "EncryptedExtensions"
	case typeCertificate:
		return "Certificate"
	case typeCertificateRequest:
		return "CertificateRequest"
	case typeCertificateVerify:
		return "CertificateVerify"
	case typeFinished:
		return "Finished"
	default:
		return fmt.Sprintf("unknown message type: %d", m)
	}
}

type chunk struct {
	data     []byte
	encLevel protocol.EncryptionLevel
}

type stream struct {
	chunkChan chan<- chunk
	encLevel  protocol.EncryptionLevel
}

func (s *stream) Write(b []byte) (int, error) {
	data := append([]byte{}, b...)
	select {
	case s.chunkChan <- chunk{data: data, encLevel: s.encLevel}:
	default:
		panic("chunkChan too small")
	}
	return len(b), nil
}

func initStreams() (chan chunk, *stream /* initial */, *stream /* handshake */) {
	chunkChan := make(chan chunk, 10)
	initialStream := &stream{chunkChan: chunkChan, encLevel: protocol.EncryptionInitial}
	handshakeStream := &stream{chunkChan: chunkChan, encLevel: protocol.EncryptionHandshake}
	return chunkChan, initialStream, handshakeStream
}

type handshakeRunner interface {
	OnReceivedParams(*wire.TransportParameters)
	OnHandshakeComplete()
	OnError(error)
	DropKeys(protocol.EncryptionLevel)
}

type runner struct {
	errored        bool
	client, server *handshake.CryptoSetup
}

var _ handshakeRunner = &runner{}

func newRunner(client, server *handshake.CryptoSetup) *runner {
	return &runner{client: client, server: server}
}

func (r *runner) OnReceivedParams(*wire.TransportParameters) {}
func (r *runner) OnHandshakeComplete()                       {}
func (r *runner) OnError(err error) {
	if r.errored {
		return
	}
	r.errored = true
	(*r.client).Close()
	(*r.server).Close()
}
func (r *runner) DropKeys(protocol.EncryptionLevel) {}

const alpn = "fuzzing"

func toEncryptionLevel(n uint8) protocol.EncryptionLevel {
	switch n % 3 {
	default:
		return protocol.EncryptionInitial
	case 1:
		return protocol.EncryptionHandshake
	case 2:
		return protocol.Encryption1RTT
	}
}

func maxEncLevel(cs handshake.CryptoSetup, encLevel protocol.EncryptionLevel) protocol.EncryptionLevel {
	switch encLevel {
	case protocol.EncryptionInitial:
		return protocol.EncryptionInitial
	case protocol.EncryptionHandshake:
		// Handshake opener not available. We can't possibly read a Handshake handshake message.
		if opener, err := cs.GetHandshakeOpener(); err != nil || opener == nil {
			return protocol.EncryptionInitial
		}
		return protocol.EncryptionHandshake
	case protocol.Encryption1RTT:
		// 1-RTT opener not available. We can't possibly read a post-handshake message.
		if opener, err := cs.Get1RTTOpener(); err != nil || opener == nil {
			return maxEncLevel(cs, protocol.EncryptionHandshake)
		}
		return protocol.Encryption1RTT
	default:
		panic("unexpected encryption level")
	}
}

// PrefixLen is the number of bytes used for configuration
const PrefixLen = 4

// Fuzz fuzzes the TLS 1.3 handshake used by QUIC.
//go:generate go run ./cmd/corpus.go
func Fuzz(data []byte) int {
	if len(data) < PrefixLen {
		return -1
	}
	runConfig1 := data[0]
	messageConfig1 := data[1]
	runConfig2 := data[2]
	messageConfig2 := data[3]
	data = data[PrefixLen:]

	clientConf := &tls.Config{
		ServerName: "localhost",
		NextProtos: []string{alpn},
		RootCAs:    certPool,
	}
	useSessionTicketCache := helper.NthBit(runConfig1, 2)
	if useSessionTicketCache {
		clientConf.ClientSessionCache = tls.NewLRUClientSessionCache(5)
	}

	if val := runHandshake(runConfig1, messageConfig1, clientConf, data); val != 1 {
		return val
	}
	return runHandshake(runConfig2, messageConfig2, clientConf, data)
}

func runHandshake(runConfig uint8, messageConfig uint8, clientConf *tls.Config, data []byte) int {
	enable0RTTClient := helper.NthBit(runConfig, 0)
	enable0RTTServer := helper.NthBit(runConfig, 1)
	sendPostHandshakeMessageToClient := helper.NthBit(runConfig, 3)
	sendPostHandshakeMessageToServer := helper.NthBit(runConfig, 4)
	sendSessionTicket := helper.NthBit(runConfig, 5)

	messageToReplace := messageConfig % 32
	messageToReplaceEncLevel := toEncryptionLevel(messageConfig >> 6)

	cChunkChan, cInitialStream, cHandshakeStream := initStreams()
	var client, server handshake.CryptoSetup
	runner := newRunner(&client, &server)
	client, _ = handshake.NewCryptoSetupClient(
		cInitialStream,
		cHandshakeStream,
		protocol.ConnectionID{},
		nil,
		nil,
		&wire.TransportParameters{},
		runner,
		clientConf,
		enable0RTTClient,
		utils.NewRTTStats(),
		nil,
		utils.DefaultLogger.WithPrefix("client"),
	)

	sChunkChan, sInitialStream, sHandshakeStream := initStreams()
	server = handshake.NewCryptoSetupServer(
		sInitialStream,
		sHandshakeStream,
		protocol.ConnectionID{},
		nil,
		nil,
		&wire.TransportParameters{},
		runner,
		&tls.Config{
			Certificates:     []tls.Certificate{*cert},
			NextProtos:       []string{alpn},
			SessionTicketKey: sessionTicketKey,
		},
		enable0RTTServer,
		utils.NewRTTStats(),
		nil,
		utils.DefaultLogger.WithPrefix("server"),
	)

	if len(data) == 0 {
		return -1
	}

	serverHandshakeCompleted := make(chan struct{})
	go func() {
		defer close(serverHandshakeCompleted)
		server.RunHandshake()
	}()

	clientHandshakeCompleted := make(chan struct{})
	go func() {
		defer close(clientHandshakeCompleted)
		client.RunHandshake()
	}()

	done := make(chan struct{})
	go func() {
		<-serverHandshakeCompleted
		<-clientHandshakeCompleted
		close(done)
	}()

messageLoop:
	for {
		select {
		case c := <-cChunkChan:
			b := c.data
			encLevel := c.encLevel
			if len(b) > 0 && b[0] == messageToReplace {
				fmt.Println("replacing message to the server", messageType(b[0]).String())
				b = data
				encLevel = maxEncLevel(server, messageToReplaceEncLevel)
			}
			server.HandleMessage(b, encLevel)
		case c := <-sChunkChan:
			b := c.data
			encLevel := c.encLevel
			if len(b) > 0 && b[0] == messageToReplace {
				fmt.Println("replacing message to the client", messageType(b[0]).String())
				b = data
				encLevel = maxEncLevel(client, messageToReplaceEncLevel)
			}
			client.HandleMessage(b, encLevel)
		case <-done: // test done
			break messageLoop
		}
		if runner.errored {
			break messageLoop
		}
	}

	<-done
	_ = client.ConnectionState()
	_ = server.ConnectionState()
	if runner.errored {
		return 0
	}
	if sendSessionTicket {
		ticket, err := server.GetSessionTicket()
		if err != nil {
			panic(err)
		}
		if ticket == nil {
			panic("empty ticket")
		}
		client.HandleMessage(ticket, protocol.Encryption1RTT)
	}
	if sendPostHandshakeMessageToClient {
		client.HandleMessage(data, messageToReplaceEncLevel)
	}
	if sendPostHandshakeMessageToServer {
		server.HandleMessage(data, messageToReplaceEncLevel)
	}

	return 1
}
<commit_msg>use more tls.Config options in the handshake fuzzer<commit_after>package handshake

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	mrand "math/rand"
	"time"

	"github.com/lucas-clemente/quic-go/fuzzing/internal/helper"
	"github.com/lucas-clemente/quic-go/internal/handshake"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/internal/wire"
)

var cert, clientCert *tls.Certificate
var certPool, clientCertPool *x509.CertPool
var sessionTicketKey = [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}

func init() {
	priv, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		log.Fatal(err)
	}
	cert, certPool, err = helper.GenerateCertificate(priv)
	if err != nil {
		log.Fatal(err)
	}

	privClient, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		log.Fatal(err)
	}
	clientCert, clientCertPool, err = helper.GenerateCertificate(privClient)
	if err != nil {
		log.Fatal(err)
	}
}

type messageType uint8

// TLS handshake message types.
const (
	typeClientHello         messageType = 1
	typeServerHello         messageType = 2
	typeNewSessionTicket    messageType = 4
	typeEncryptedExtensions messageType = 8
	typeCertificate         messageType = 11
	typeCertificateRequest  messageType = 13
	typeCertificateVerify   messageType = 15
	typeFinished            messageType = 20
)

func (m messageType) String() string {
	switch m {
	case typeClientHello:
		return "ClientHello"
	case typeServerHello:
		return "ServerHello"
	case typeNewSessionTicket:
		return "NewSessionTicket"
	case typeEncryptedExtensions:
		return "EncryptedExtensions"
	case typeCertificate:
		return "Certificate"
	case typeCertificateRequest:
		return "CertificateRequest"
	case typeCertificateVerify:
		return "CertificateVerify"
	case typeFinished:
		return "Finished"
	default:
		return fmt.Sprintf("unknown message type: %d", m)
	}
}

func appendSuites(suites []uint16, rand uint8) []uint16 {
	const (
		s1 = tls.TLS_AES_128_GCM_SHA256
		s2 = tls.TLS_AES_256_GCM_SHA384
		s3 = tls.TLS_CHACHA20_POLY1305_SHA256
	)
	switch rand % 4 {
	default:
		return suites
	case 1:
		return append(suites, s1)
	case 2:
		return append(suites, s2)
	case 3:
		return append(suites, s3)
	}
}

// consumes 2 bits
func getSuites(rand uint8) []uint16 {
	suites := make([]uint16, 0, 3)
	for i := 1; i <= 3; i++ {
		suites = appendSuites(suites, rand>>i%4)
	}
	return suites
}

// consumes 3 bits
func getClientAuth(rand uint8) tls.ClientAuthType {
	switch rand {
	default:
		return tls.NoClientCert
	case 0:
		return tls.RequestClientCert
	case 1:
		return tls.RequireAnyClientCert
	case 2:
		return tls.VerifyClientCertIfGiven
	case 3:
		return tls.RequireAndVerifyClientCert
	}
}

type chunk struct {
	data     []byte
	encLevel protocol.EncryptionLevel
}

type stream struct {
	chunkChan chan<- chunk
	encLevel  protocol.EncryptionLevel
}

func (s *stream) Write(b []byte) (int, error) {
	data := append([]byte{}, b...)
	select {
	case s.chunkChan <- chunk{data: data, encLevel: s.encLevel}:
	default:
		panic("chunkChan too small")
	}
	return len(b), nil
}

func initStreams() (chan chunk, *stream /* initial */, *stream /* handshake */) {
	chunkChan := make(chan chunk, 10)
	initialStream := &stream{chunkChan: chunkChan, encLevel: protocol.EncryptionInitial}
	handshakeStream := &stream{chunkChan: chunkChan, encLevel: protocol.EncryptionHandshake}
	return chunkChan, initialStream, handshakeStream
}

type handshakeRunner interface {
	OnReceivedParams(*wire.TransportParameters)
	OnHandshakeComplete()
	OnError(error)
	DropKeys(protocol.EncryptionLevel)
}

type runner struct {
	errored        bool
	client, server *handshake.CryptoSetup
}

var _ handshakeRunner = &runner{}

func newRunner(client, server *handshake.CryptoSetup) *runner {
	return &runner{client: client, server: server}
}

func (r *runner) OnReceivedParams(*wire.TransportParameters) {}
func (r *runner) OnHandshakeComplete()                       {}
func (r *runner) OnError(err error) {
	if r.errored {
		return
	}
	r.errored = true
	(*r.client).Close()
	(*r.server).Close()
}
func (r *runner) DropKeys(protocol.EncryptionLevel) {}

const alpn = "fuzzing"
const alpnWrong = "wrong"

func toEncryptionLevel(n uint8) protocol.EncryptionLevel {
	switch n % 3 {
	default:
		return protocol.EncryptionInitial
	case 1:
		return protocol.EncryptionHandshake
	case 2:
		return protocol.Encryption1RTT
	}
}

func maxEncLevel(cs handshake.CryptoSetup, encLevel protocol.EncryptionLevel) protocol.EncryptionLevel {
	switch encLevel {
	case protocol.EncryptionInitial:
		return protocol.EncryptionInitial
	case protocol.EncryptionHandshake:
		// Handshake opener not available. We can't possibly read a Handshake handshake message.
		if opener, err := cs.GetHandshakeOpener(); err != nil || opener == nil {
			return protocol.EncryptionInitial
		}
		return protocol.EncryptionHandshake
	case protocol.Encryption1RTT:
		// 1-RTT opener not available. We can't possibly read a post-handshake message.
		if opener, err := cs.Get1RTTOpener(); err != nil || opener == nil {
			return maxEncLevel(cs, protocol.EncryptionHandshake)
		}
		return protocol.Encryption1RTT
	default:
		panic("unexpected encryption level")
	}
}

func getTransportParameters(seed uint8) *wire.TransportParameters {
	const maxVarInt = math.MaxUint64 / 4
	r := mrand.New(mrand.NewSource(int64(seed)))
	return &wire.TransportParameters{
		InitialMaxData:                 protocol.ByteCount(r.Int63n(maxVarInt)),
		InitialMaxStreamDataBidiLocal:  protocol.ByteCount(r.Int63n(maxVarInt)),
		InitialMaxStreamDataBidiRemote: protocol.ByteCount(r.Int63n(maxVarInt)),
		InitialMaxStreamDataUni:        protocol.ByteCount(r.Int63n(maxVarInt)),
	}
}

// PrefixLen is the number of bytes used for configuration
const PrefixLen = 12
const confLen = 5

// Fuzz fuzzes the TLS 1.3 handshake used by QUIC.
//go:generate go run ./cmd/corpus.go
func Fuzz(data []byte) int {
	if len(data) < PrefixLen {
		return -1
	}
	dataLen := len(data)
	var runConfig1, runConfig2 [confLen]byte
	copy(runConfig1[:], data)
	data = data[confLen:]
	messageConfig1 := data[0]
	data = data[1:]
	copy(runConfig2[:], data)
	data = data[confLen:]
	messageConfig2 := data[0]
	data = data[1:]
	if dataLen != len(data)+PrefixLen {
		panic("incorrect configuration")
	}

	clientConf := &tls.Config{
		ServerName: "localhost",
		NextProtos: []string{alpn},
		RootCAs:    certPool,
	}
	useSessionTicketCache := helper.NthBit(runConfig1[0], 2)
	if useSessionTicketCache {
		clientConf.ClientSessionCache = tls.NewLRUClientSessionCache(5)
	}

	if val := runHandshake(runConfig1, messageConfig1, clientConf, data); val != 1 {
		return val
	}
	return runHandshake(runConfig2, messageConfig2, clientConf, data)
}

func runHandshake(runConfig [confLen]byte, messageConfig uint8, clientConf *tls.Config, data []byte) int {
	serverConf := &tls.Config{
		Certificates:     []tls.Certificate{*cert},
		NextProtos:       []string{alpn},
		SessionTicketKey: sessionTicketKey,
	}

	enable0RTTClient := helper.NthBit(runConfig[0], 0)
	enable0RTTServer := helper.NthBit(runConfig[0], 1)
	sendPostHandshakeMessageToClient := helper.NthBit(runConfig[0], 3)
	sendPostHandshakeMessageToServer := helper.NthBit(runConfig[0], 4)
	sendSessionTicket := helper.NthBit(runConfig[0], 5)
	clientConf.CipherSuites = getSuites(runConfig[0] >> 6)
	serverConf.ClientAuth = getClientAuth(runConfig[1] & 0b00000111)
	serverConf.CipherSuites = getSuites(runConfig[1] >> 6)
	serverConf.SessionTicketsDisabled = helper.NthBit(runConfig[1], 3)
	clientConf.PreferServerCipherSuites = helper.NthBit(runConfig[1], 4)
	if helper.NthBit(runConfig[2], 0) {
		clientConf.RootCAs = x509.NewCertPool()
	}
	if helper.NthBit(runConfig[2], 1) {
		serverConf.ClientCAs = clientCertPool
	} else {
		serverConf.ClientCAs = x509.NewCertPool()
	}
	if helper.NthBit(runConfig[2], 2) {
		serverConf.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
			if helper.NthBit(runConfig[2], 3) {
				return nil, errors.New("getting client config failed")
			}
			if helper.NthBit(runConfig[2], 4) {
				return nil, nil
			}
			return serverConf, nil
		}
	}
	if helper.NthBit(runConfig[2], 5) {
		serverConf.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
			if helper.NthBit(runConfig[2], 6) {
				return nil, errors.New("getting certificate failed")
			}
			if helper.NthBit(runConfig[2], 7) {
				return nil, nil
			}
			return clientCert, nil // this certificate will be invalid
		}
	}
	if helper.NthBit(runConfig[3], 0) {
		serverConf.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			if helper.NthBit(runConfig[3], 1) {
				return errors.New("certificate verification failed")
			}
			return nil
		}
	}
	if helper.NthBit(runConfig[3], 2) {
		clientConf.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			if helper.NthBit(runConfig[3], 3) {
				return errors.New("certificate verification failed")
			}
			return nil
		}
	}
	if helper.NthBit(runConfig[3], 4) {
		serverConf.NextProtos = []string{alpnWrong}
	}
	if helper.NthBit(runConfig[3], 5) {
		serverConf.NextProtos = []string{alpnWrong, alpn}
	}
	if helper.NthBit(runConfig[3], 6) {
		serverConf.KeyLogWriter = ioutil.Discard
	}
	if helper.NthBit(runConfig[3], 7) {
		clientConf.KeyLogWriter = ioutil.Discard
	}
	clientTP := getTransportParameters(runConfig[4] & 0x3)
	if helper.NthBit(runConfig[4], 3) {
		clientTP.MaxAckDelay = protocol.MaxMaxAckDelay + 5
	}
	serverTP := getTransportParameters(runConfig[4] & 0b00011000)
	if helper.NthBit(runConfig[4], 3) {
		serverTP.MaxAckDelay = protocol.MaxMaxAckDelay + 5
	}

	messageToReplace := messageConfig % 32
	messageToReplaceEncLevel := toEncryptionLevel(messageConfig >> 6)

	cChunkChan, cInitialStream, cHandshakeStream := initStreams()
	var client, server handshake.CryptoSetup
	runner := newRunner(&client, &server)
	client, _ = handshake.NewCryptoSetupClient(
		cInitialStream,
		cHandshakeStream,
		protocol.ConnectionID{},
		nil,
		nil,
		clientTP,
		runner,
		clientConf,
		enable0RTTClient,
		utils.NewRTTStats(),
		nil,
		utils.DefaultLogger.WithPrefix("client"),
	)

	sChunkChan, sInitialStream, sHandshakeStream := initStreams()
	server = handshake.NewCryptoSetupServer(
		sInitialStream,
		sHandshakeStream,
		protocol.ConnectionID{},
		nil,
		nil,
		serverTP,
		runner,
		serverConf,
		enable0RTTServer,
		utils.NewRTTStats(),
		nil,
		utils.DefaultLogger.WithPrefix("server"),
	)

	if len(data) == 0 {
		return -1
	}

	serverHandshakeCompleted := make(chan struct{})
	go func() {
		defer close(serverHandshakeCompleted)
		server.RunHandshake()
	}()

	clientHandshakeCompleted := make(chan struct{})
	go func() {
		defer close(clientHandshakeCompleted)
		client.RunHandshake()
	}()

	done := make(chan struct{})
	go func() {
		<-serverHandshakeCompleted
		<-clientHandshakeCompleted
		close(done)
	}()

messageLoop:
	for {
		select {
		case c := <-cChunkChan:
			b := c.data
			encLevel := c.encLevel
			if len(b) > 0 && b[0] == messageToReplace {
				fmt.Printf("replacing %s message to the server with %s\n", messageType(b[0]), messageType(data[0]))
				b = data
				encLevel = maxEncLevel(server, messageToReplaceEncLevel)
			}
			server.HandleMessage(b, encLevel)
		case c := <-sChunkChan:
			b := c.data
			encLevel := c.encLevel
			if len(b) > 0 && b[0] == messageToReplace {
				fmt.Printf("replacing %s message to the client with %s\n", messageType(b[0]), messageType(data[0]))
				b = data
				encLevel = maxEncLevel(client, messageToReplaceEncLevel)
			}
			client.HandleMessage(b, encLevel)
		case <-done: // test done
			break messageLoop
		}
		if runner.errored {
			break messageLoop
		}
	}

	<-done
	_ = client.ConnectionState()
	_ = server.ConnectionState()
	if runner.errored {
		return 0
	}

	sealer, err := client.Get1RTTSealer()
	if err != nil {
		panic("expected to get a 1-RTT sealer")
	}
	opener, err := server.Get1RTTOpener()
	if err != nil {
		panic("expected to get a 1-RTT opener")
	}
	const msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
	encrypted := sealer.Seal(nil, []byte(msg), 1337, []byte("foobar"))
	decrypted, err := opener.Open(nil, encrypted, time.Time{}, 1337, protocol.KeyPhaseZero, []byte("foobar"))
	if err != nil {
		panic(fmt.Sprintf("Decrypting message failed: %s", err.Error()))
	}
	if string(decrypted) != msg {
		panic("wrong message")
	}

	if sendSessionTicket && !serverConf.SessionTicketsDisabled {
		ticket, err := server.GetSessionTicket()
		if err != nil {
			panic(err)
		}
		if ticket == nil {
			panic("empty ticket")
		}
		client.HandleMessage(ticket, protocol.Encryption1RTT)
	}
	if sendPostHandshakeMessageToClient {
		client.HandleMessage(data, messageToReplaceEncLevel)
	}
	if sendPostHandshakeMessageToServer {
		server.HandleMessage(data, messageToReplaceEncLevel)
	}

	return 1
}
<|endoftext|>


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

// The code in this file is adapted from src/mime/multipart/writer.go in the Go
// repository at commit c007ce824d9a4fccb148f9204e04c23ed2984b71. The LICENSE
// file there says:
//
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package httputil

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"net/textproto"
)

type ContentTypedReader struct {
	ContentType string
	Reader      io.Reader
}

// Create a reader that streams an HTTP multipart body (see RFC 2388) composed
// of the contents of each component reader in sequence, each with a
// Content-Type header as specified.
//
// Unlike multipart.Writer from the standard library, this can be used directly
// as http.Request.Body without bending over backwards to convert an io.Writer
// to an io.Reader.
func NewMultipartReader(readers []ContentTypedReader) io.Reader

// FormDataContentType returns the Content-Type for an HTTP
// multipart/form-data with this Writer's Boundary.
func (w *Writer) FormDataContentType() string {
	return "multipart/form-data; boundary=" + w.boundary
}

func randomBoundary() string {
	var buf [30]byte
	_, err := io.ReadFull(rand.Reader, buf[:])
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", buf[:])
}

// CreatePart creates a new multipart section with the provided
// header. The body of the part should be written to the returned
// Writer. After calling CreatePart, any previous part may no longer
// be written to.
func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
	if w.lastpart != nil {
		if err := w.lastpart.close(); err != nil {
			return nil, err
		}
	}
	var b bytes.Buffer
	if w.lastpart != nil {
		fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
	} else {
		fmt.Fprintf(&b, "--%s\r\n", w.boundary)
	}
	// TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort
	// and clean, like http.Header.Write(w) does.
	for k, vv := range header {
		for _, v := range vv {
			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
		}
	}
	fmt.Fprintf(&b, "\r\n")
	_, err := io.Copy(w.w, &b)
	if err != nil {
		return nil, err
	}
	p := &part{
		mw: w,
	}
	w.lastpart = p
	return p, nil
}

// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
func (w *Writer) Close() error {
	if w.lastpart != nil {
		if err := w.lastpart.close(); err != nil {
			return err
		}
		w.lastpart = nil
	}
	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
	return err
}

type part struct {
	mw     *Writer
	closed bool
	we     error // last error that occurred writing
}

func (p *part) close() error {
	p.closed = true
	return p.we
}

func (p *part) Write(d []byte) (n int, err error) {
	if p.closed {
		return 0, errors.New("multipart: can't write to finished part")
	}
	n, err = p.mw.w.Write(d)
	if err != nil {
		p.we = err
	}
	return
}
<commit_msg>Defined the MultipartReader interface.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The code in this file is adapted from src/mime/multipart/writer.go in the Go
// repository at commit c007ce824d9a4fccb148f9204e04c23ed2984b71. The LICENSE
// file there says:
//
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package httputil

import (
	"bytes"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"net/textproto"
)

type ContentTypedReader struct {
	ContentType string
	Reader      io.Reader
}

// Create a reader that streams an HTTP multipart body (see RFC 2388) composed
// of the contents of each component reader in sequence, each with a
// Content-Type header as specified.
//
// Unlike multipart.Writer from the standard library, this can be used directly
// as http.Request.Body without bending over backwards to convert an io.Writer
// to an io.Reader.
func NewMultipartReader(readers []ContentTypedReader) *MultipartReader

// MultipartReader is an io.Reader that generates HTTP multipart bodies. See
// NewMultipartReader for details.
type MultipartReader struct {
}

func (mr *MultipartReader) Read(p []byte) (n int, err error) {
	err = fmt.Errorf("TODO: MultipartReader.Read")
	return
}

// Return the value to use for the Content-Type header in an HTTP request that
// uses mr as the body.
func (mr *MultipartReader) ContentType() string {
	return fmt.Sprintf("multipart/related; boundary=%s", mr.boundary)
}

func randomBoundary() string {
	var buf [30]byte
	_, err := io.ReadFull(rand.Reader, buf[:])
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", buf[:])
}

// CreatePart creates a new multipart section with the provided
// header. The body of the part should be written to the returned
// Writer. After calling CreatePart, any previous part may no longer
// be written to.
func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
	if w.lastpart != nil {
		if err := w.lastpart.close(); err != nil {
			return nil, err
		}
	}
	var b bytes.Buffer
	if w.lastpart != nil {
		fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
	} else {
		fmt.Fprintf(&b, "--%s\r\n", w.boundary)
	}
	// TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort
	// and clean, like http.Header.Write(w) does.
	for k, vv := range header {
		for _, v := range vv {
			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
		}
	}
	fmt.Fprintf(&b, "\r\n")
	_, err := io.Copy(w.w, &b)
	if err != nil {
		return nil, err
	}
	p := &part{
		mw: w,
	}
	w.lastpart = p
	return p, nil
}

// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
func (w *Writer) Close() error {
	if w.lastpart != nil {
		if err := w.lastpart.close(); err != nil {
			return err
		}
		w.lastpart = nil
	}
	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
	return err
}

type part struct {
	mw     *Writer
	closed bool
	we     error // last error that occurred writing
}

func (p *part) close() error {
	p.closed = true
	return p.we
}

func (p *part) Write(d []byte) (n int, err error) {
	if p.closed {
		return 0, errors.New("multipart: can't write to finished part")
	}
	n, err = p.mw.w.Write(d)
	if err != nil {
		p.we = err
	}
	return
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"log"
	"os"
	"sort"
	"strings"
	"text/template"

	"golang.org/x/net/idna"

	"github.com/mvdan/xurls"
)

const path = "regex.go"

var regexTmpl = template.Must(template.New("regex").Parse(`// Generated by regexgen

package xurls

const ({{ range $key, $value := . }}
	{{$key}} = ` + "`" + `{{$value}}` + "`" + `{{end}}
)
`))

// These schemes may be followed by just ":" instead of "://", so instead of
// allowing for arbitrary schemes we're limiting the regex to just a few
// well-known ones.
var schemes = []string{
	`bitcoin`,
	`magnet`,
	`mailto`,
	`sms`,
	`tel`,
	`xmpp`,
}

func writeRegex(tlds []string) error {
	allTldsSet := make(map[string]struct{})
	for _, tldlist := range [...][]string{tlds, xurls.PseudoTLDs} {
		for _, tld := range tldlist {
			allTldsSet[tld] = struct{}{}
			asciiTld, err := idna.ToASCII(tld)
			if err != nil {
				return err
			}
			allTldsSet[asciiTld] = struct{}{}
		}
	}
	var allTlds []string
	for tld := range allTldsSet {
		allTlds = append(allTlds, tld)
	}
	sort.Strings(allTlds)
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	return regexTmpl.Execute(f, map[string]string{
		"gtld       ": `(?i)(` + strings.Join(allTlds, `|`) + `)(?-i)`,
		"otherScheme": `(?i)(` + strings.Join(schemes, `|`) + `)(?-i):`,
	})
}

func main() {
	log.Printf("Generating %s...", path)
	if err := writeRegex(xurls.TLDs); err != nil {
		log.Fatalf("Could not write %s: %s", path, err)
	}
}
<commit_msg>Detect duplicate TLDs<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"log"
	"os"
	"sort"
	"strings"
	"text/template"

	"golang.org/x/net/idna"

	"github.com/mvdan/xurls"
)

const path = "regex.go"

var regexTmpl = template.Must(template.New("regex").Parse(`// Generated by regexgen

package xurls

const ({{ range $key, $value := . }}
	{{$key}} = ` + "`" + `{{$value}}` + "`" + `{{end}}
)
`))

// These schemes may be followed by just ":" instead of "://", so instead of
// allowing for arbitrary schemes we're limiting the regex to just a few
// well-known ones.
var schemes = []string{
	`bitcoin`,
	`magnet`,
	`mailto`,
	`sms`,
	`tel`,
	`xmpp`,
}

func writeRegex(tlds []string) error {
	allTldsSet := make(map[string]struct{})
	add := func(tld string) {
		if _, e := allTldsSet[tld]; e {
			log.Fatalf("Duplicate TLD: %s", tld)
		}
		allTldsSet[tld] = struct{}{}
	}
	for _, tldlist := range [...][]string{tlds, xurls.PseudoTLDs} {
		for _, tld := range tldlist {
			add(tld)
			asciiTld, err := idna.ToASCII(tld)
			if err != nil {
				return err
			}
			if asciiTld != tld {
				add(asciiTld)
			}
		}
	}
	var allTlds []string
	for tld := range allTldsSet {
		allTlds = append(allTlds, tld)
	}
	sort.Strings(allTlds)
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	return regexTmpl.Execute(f, map[string]string{
		"gtld       ": `(?i)(` + strings.Join(allTlds, `|`) + `)(?-i)`,
		"otherScheme": `(?i)(` + strings.Join(schemes, `|`) + `)(?-i):`,
	})
}

func main() {
	log.Printf("Generating %s...", path)
	if err := writeRegex(xurls.TLDs); err != nil {
		log.Fatalf("Could not write %s: %s", path, err)
	}
}
<|endoftext|>


<commit_before>// UVa 10891 - Game of Sum

package main

import (
	"fmt"
	"os"
)

var (
	sum     []int
	dp      [][]int
	visited [][]bool
)

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func find(l, r int) int {
	if visited[l][r] {
		return dp[l][r]
	}
	visited[l][r] = true
	var b int
	for k := l + 1; k <= r; k++ {
		b = min(b, find(k, r))
	}
	for k := r - 1; k >= l; k-- {
		b = min(b, find(l, k))
	}
	dp[l][r] = sum[r] - sum[l-1] - b
	return dp[l][r]
}

func solve(n int, nums []int) int {
	sum = make([]int, n+1)
	for i, num := range nums {
		sum[i+1] = sum[i] + num
	}
	visited = make([][]bool, n+1)
	dp = make([][]int, n+1)
	for i := range visited {
		visited[i] = make([]bool, n+1)
		dp[i] = make([]int, n+1)
	}
	return 2*find(1, n) - sum[n]
}

func main() {
	in, _ := os.Open("10891.in")
	defer in.Close()
	out, _ := os.Create("10891.out")
	defer out.Close()

	var n int
	for {
		if fmt.Fscanf(in, "%d", &n); n == 0 {
			break
		}
		nums := make([]int, n)
		for i := range nums {
			fmt.Fscanf(in, "%d", &nums[i])
		}
		fmt.Fprintln(out, solve(n, nums))
	}
}
<commit_msg>code clean up<commit_after>// UVa 10891 - Game of Sum

package main

import (
	"fmt"
	"os"
)

var (
	sum     []int
	dp      [][]int
	visited [][]bool
)

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func find(l, r int) int {
	if !visited[l][r] {
		visited[l][r] = true
		var b int
		for k := l + 1; k <= r; k++ {
			b = min(b, find(k, r))
		}
		for k := r - 1; k >= l; k-- {
			b = min(b, find(l, k))
		}
		dp[l][r] = sum[r] - sum[l-1] - b
	}
	return dp[l][r]
}

func solve(n int, nums []int) int {
	sum = make([]int, n+1)
	for i, num := range nums {
		sum[i+1] = sum[i] + num
	}
	visited = make([][]bool, n+1)
	dp = make([][]int, n+1)
	for i := range visited {
		visited[i] = make([]bool, n+1)
		dp[i] = make([]int, n+1)
	}
	return 2*find(1, n) - sum[n]
}

func main() {
	in, _ := os.Open("10891.in")
	defer in.Close()
	out, _ := os.Create("10891.out")
	defer out.Close()

	var n int
	for {
		if fmt.Fscanf(in, "%d", &n); n == 0 {
			break
		}
		nums := make([]int, n)
		for i := range nums {
			fmt.Fscanf(in, "%d", &nums[i])
		}
		fmt.Fprintln(out, solve(n, nums))
	}
}
<|endoftext|>


<commit_before>package bundle

import (
	"archive/tar"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/ghodss/yaml"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/proto/hapi/chart"

	"github.com/kyma-project/kyma/components/helm-broker/internal"
)

const (
	bundleChartDirName = "chart"
	bundleMetaName     = "meta.yaml"
	bundlePlanDirName  = "plans"

	bundlePlanMetaName             = "meta.yaml"
	bundlePlaSchemaCreateJSONName  = "create-instance-schema.json"
	bundlePlanSchemaUpdateJSONName = "update-instance-schema.json"
	bundlePlanValuesFileName       = "values.yaml"
	bundlePlanBindTemplateFileName = "bind.yaml"

	maxSchemaLength = 65536 // 64 k
)

// Loader provides loading of bundles from repository and representing them as bundles and charts.
type Loader struct {
	tmpDir       string
	loadChart    func(name string) (*chart.Chart, error)
	createTmpDir func(dir, prefix string) (name string, err error)
	log          logrus.FieldLogger
}

// NewLoader returns new instance of Loader.
func NewLoader(tmpDir string, log logrus.FieldLogger) *Loader {
	return &Loader{
		tmpDir:       tmpDir,
		loadChart:    chartutil.Load,
		createTmpDir: ioutil.TempDir,
		log:          log.WithField("service", "ybundle:loader"),
	}
}

// Load takes stream with compressed tgz archive as io.Reader, tries to unpack it to tmp directory,
// and then loads it as YBundle and helm Chart
func (l *Loader) Load(in io.Reader) (*internal.Bundle, []*chart.Chart, error) {
	unpackedDir, err := l.unpackArchive(l.tmpDir, in)
	if err != nil {
		return nil, nil, err
	}
	cleanPath := filepath.Clean(unpackedDir)
	if strings.HasPrefix(cleanPath, l.tmpDir) {
		defer os.RemoveAll(unpackedDir)
	} else {
		defer l.log.Warnf("directory %s was not deleted because its name does not resolve to expected path", unpackedDir)
	}

	return l.loadDir(unpackedDir)
}

// LoadDir takes uncompressed chart in specified directory and loads it.
func (l Loader) LoadDir(path string) (*internal.Bundle, []*chart.Chart, error) {
	return l.loadDir(path)
}

func (l Loader) loadDir(path string) (*internal.Bundle, []*chart.Chart, error) {
	c, err := l.loadChartFromDir(path)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while loading chart")
	}

	form, err := l.createFormFromBundleDir(path)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while mapping buffered files to form")
	}

	if err := form.Validate(); err != nil {
		return nil, nil, errors.Wrap(err, "while validating form")
	}

	yb, err := form.ToModel(c)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while mapping form to model")
	}

	return &yb, []*chart.Chart{c}, nil
}

func (l Loader) loadChartFromDir(baseDir string) (*chart.Chart, error) {
	// In current version we have only one chart per bundle
	// in future version we will have some loop over each plan to load all charts
	chartPath, err := l.discoverPathToHelmChart(baseDir)
	if err != nil {
		return nil, errors.Wrapf(err, "while discovering the name of the Helm Chart under the %q bundle directory", bundleChartDirName)
	}

	c, err := l.loadChart(chartPath)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, errors.New("bundle does not contains \"chart\" directory")
	default:
		return nil, errors.Wrap(err, "while loading chart")
	}

	return c, nil
}

// discoverPathToHelmChart returns the full path to the Helm Chart directory from `bundleChartDirName` folder
//
// - if more that one directory is found then error is returned
// - if additional files are found under the `bundleChartDirName` directory then
//   they are ignored but logged as warning to improve transparency.
func (l Loader) discoverPathToHelmChart(baseDir string) (string, error) {
	cDir := filepath.Join(baseDir, bundleChartDirName)
	rawFiles, err := ioutil.ReadDir(cDir)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return "", errors.Errorf("bundle does not contains %q directory", bundleChartDirName)
	default:
		return "", errors.Wrapf(err, "while reading directory %s", cDir)
	}

	directories, files := splitForDirectoriesAndFiles(rawFiles)
	if len(directories) == 0 {
		return "", fmt.Errorf("%q directory SHOULD contain one Helm Chart folder but it's empty", bundleChartDirName)
	}

	if len(directories) > 1 {
		return "", fmt.Errorf("%q directory MUST contain only one Helm Chart folder but found multiple directories: [%s]", bundleChartDirName, strings.Join(directories, ", "))
	}

	if len(files) != 0 { // ignoring by design
		l.log.Warningf("Found files: [%s] in %q bundle directory. All are ignored.", strings.Join(files, ", "), bundleChartDirName)
	}

	chartFullPath := filepath.Join(cDir, directories[0])
	return chartFullPath, nil
}

func splitForDirectoriesAndFiles(rawFiles []os.FileInfo) (dirs []string, files []string) {
	for _, f := range rawFiles {
		if f.IsDir() {
			dirs = append(dirs, f.Name())
		} else {
			files = append(files, f.Name())
		}
	}

	return dirs, files
}

func (l Loader) createFormFromBundleDir(baseDir string) (*form, error) {
	f := &form{Plans: make(map[string]*formPlan)}

	bundleMetaFile, err := ioutil.ReadFile(filepath.Join(baseDir, bundleMetaName))
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, fmt.Errorf("missing metadata information about bundle, please check if bundle contains %q file", bundleMetaName)
	default:
		return nil, errors.Wrapf(err, "while reading %q file", bundleMetaName)
	}

	if err := yaml.Unmarshal(bundleMetaFile, &f.Meta); err != nil {
		return nil, errors.Wrapf(err, "while unmarshaling bundle %q file", bundleMetaName)
	}

	plansPath := filepath.Join(baseDir, bundlePlanDirName)
	files, err := ioutil.ReadDir(plansPath)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, fmt.Errorf("bundle does not contains any plans, please check if bundle contains %q directory", bundlePlanDirName)
	default:
		return nil, errors.Wrapf(err, "while reading %q file", bundleMetaName)
	}

	for _, fileInfo := range files {
		if fileInfo.IsDir() {
			planName := fileInfo.Name()
			f.Plans[planName] = &formPlan{}

			if err := l.loadPlanDefinition(filepath.Join(plansPath, planName), f.Plans[planName]); err != nil {
				return nil, err
			}
		}
	}

	return f, nil
}

func (Loader) loadPlanDefinition(path string, plan *formPlan) error {
	topdir, err := filepath.Abs(path)
	if err != nil {
		return err
	}

	unmarshalPlanErr := func(err error, filename string) error {
		return errors.Wrapf(err, "while unmarshaling plan %q file", filename)
	}

	if err := yamlUnmarshal(topdir, bundlePlanMetaName, &plan.Meta, true); err != nil {
		return unmarshalPlanErr(err, bundlePlanMetaName)
	}

	if err := yamlUnmarshal(topdir, bundlePlanValuesFileName, &plan.Values, false); err != nil {
		return unmarshalPlanErr(err, bundlePlanValuesFileName)
	}

	if plan.SchemasCreate, err = loadPlanSchema(topdir, bundlePlaSchemaCreateJSONName, false); err != nil {
		return unmarshalPlanErr(err, bundlePlaSchemaCreateJSONName)
	}

	if plan.SchemasUpdate, err = loadPlanSchema(topdir, bundlePlanSchemaUpdateJSONName, false); err != nil {
		return unmarshalPlanErr(err, bundlePlanSchemaUpdateJSONName)
	}

	if plan.BindTemplate, err = loadRaw(topdir, bundlePlanBindTemplateFileName, false); err != nil {
		return errors.Wrapf(err, "while loading plan %q file", bundlePlanBindTemplateFileName)
	}

	return nil
}

// unpackArchive unpack from a reader containing a compressed tar archive to tmpdir.
func (l Loader) unpackArchive(baseDir string, in io.Reader) (string, error) {
	dir, err := l.createTmpDir(baseDir, "unpacked-bundle")
	if err != nil {
		return "", err
	}

	unzipped, err := gzip.NewReader(in)
	if err != nil {
		return "", err
	}
	defer unzipped.Close()

	tr := tar.NewReader(unzipped)

Unpack:
	for {
		header, err := tr.Next()
		switch err {
		case nil:
		case io.EOF:
			break Unpack
		default:
			return "", err
		}

		// the target location where the dir/file should be created
		target := filepath.Join(dir, header.Name)

		// check the file type
		switch header.Typeflag {
		// its a dir and if it doesn't exist - create it
		case tar.TypeDir:
			if _, err := os.Stat(target); os.IsNotExist(err) {
				if err := os.MkdirAll(target, 0755); err != nil {
					return "", err
				}
			}
			// it's a file - create it
		case tar.TypeReg:
			if err := l.createFile(target, header.Mode, tr); err != nil {
				return "", err
			}
		}
	}

	return dir, nil
}

func (Loader) createFile(target string, mode int64, r io.Reader) error {
	f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(mode))
	if err != nil {
		return err
	}
	defer f.Close()

	// copy over contents
	if _, err := io.Copy(f, r); err != nil {
		return err
	}
	return nil
}

func yamlUnmarshal(basePath, fileName string, unmarshalTo interface{}, required bool) error {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil
	case os.IsNotExist(err) && required:
		return fmt.Errorf("%q is required but is not present", fileName)
	default:
		return err
	}

	return yaml.Unmarshal(b, unmarshalTo)
}

func loadPlanSchema(basePath, fileName string, required bool) (*internal.PlanSchema, error) {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil, nil
	case os.IsNotExist(err) && required:
		return nil, fmt.Errorf("%q is required but is not present", fileName)
	default:
		return nil, errors.Wrap(err, "while loading plan schema")
	}

	// OSB API defines: Schemas MUST NOT be larger than 64kB.
	// See: https://github.com/openservicebrokerapi/servicebroker/blob/v2.13/spec.md#schema-object
	if len(b) >= maxSchemaLength {
		return nil, fmt.Errorf("schema %s is larger than 64 kB", fileName)
	}

	var schema internal.PlanSchema
	err = json.Unmarshal(b, &schema)
	return &schema, errors.Wrap(err, "while loading plan shcema")
}

func loadRaw(basePath, fileName string, required bool) ([]byte, error) {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil, nil
	case os.IsNotExist(err) && required:
		return nil, fmt.Errorf("%q is required but is not present", fileName)
	default:
		return nil, err
	}

	return b, nil
}
<commit_msg>Remove "ybundle" reference (#1436)<commit_after>package bundle

import (
	"archive/tar"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/ghodss/yaml"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/proto/hapi/chart"

	"github.com/kyma-project/kyma/components/helm-broker/internal"
)

const (
	bundleChartDirName = "chart"
	bundleMetaName     = "meta.yaml"
	bundlePlanDirName  = "plans"

	bundlePlanMetaName             = "meta.yaml"
	bundlePlaSchemaCreateJSONName  = "create-instance-schema.json"
	bundlePlanSchemaUpdateJSONName = "update-instance-schema.json"
	bundlePlanValuesFileName       = "values.yaml"
	bundlePlanBindTemplateFileName = "bind.yaml"

	maxSchemaLength = 65536 // 64 k
)

// Loader provides loading of bundles from repository and representing them as bundles and charts.
type Loader struct {
	tmpDir       string
	loadChart    func(name string) (*chart.Chart, error)
	createTmpDir func(dir, prefix string) (name string, err error)
	log          logrus.FieldLogger
}

// NewLoader returns new instance of Loader.
func NewLoader(tmpDir string, log logrus.FieldLogger) *Loader {
	return &Loader{
		tmpDir:       tmpDir,
		loadChart:    chartutil.Load,
		createTmpDir: ioutil.TempDir,
		log:          log.WithField("service", "bundle:loader"),
	}
}

// Load takes stream with compressed tgz archive as io.Reader, tries to unpack it to tmp directory,
// and then loads it as bundle and Helm chart
func (l *Loader) Load(in io.Reader) (*internal.Bundle, []*chart.Chart, error) {
	unpackedDir, err := l.unpackArchive(l.tmpDir, in)
	if err != nil {
		return nil, nil, err
	}
	cleanPath := filepath.Clean(unpackedDir)
	if strings.HasPrefix(cleanPath, l.tmpDir) {
		defer os.RemoveAll(unpackedDir)
	} else {
		defer l.log.Warnf("directory %s was not deleted because its name does not resolve to expected path", unpackedDir)
	}

	return l.loadDir(unpackedDir)
}

// LoadDir takes uncompressed chart in specified directory and loads it.
func (l Loader) LoadDir(path string) (*internal.Bundle, []*chart.Chart, error) {
	return l.loadDir(path)
}

func (l Loader) loadDir(path string) (*internal.Bundle, []*chart.Chart, error) {
	c, err := l.loadChartFromDir(path)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while loading chart")
	}

	form, err := l.createFormFromBundleDir(path)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while mapping buffered files to form")
	}

	if err := form.Validate(); err != nil {
		return nil, nil, errors.Wrap(err, "while validating form")
	}

	yb, err := form.ToModel(c)
	if err != nil {
		return nil, nil, errors.Wrap(err, "while mapping form to model")
	}

	return &yb, []*chart.Chart{c}, nil
}

func (l Loader) loadChartFromDir(baseDir string) (*chart.Chart, error) {
	// In current version we have only one chart per bundle
	// in future version we will have some loop over each plan to load all charts
	chartPath, err := l.discoverPathToHelmChart(baseDir)
	if err != nil {
		return nil, errors.Wrapf(err, "while discovering the name of the Helm Chart under the %q bundle directory", bundleChartDirName)
	}

	c, err := l.loadChart(chartPath)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, errors.New("bundle does not contains \"chart\" directory")
	default:
		return nil, errors.Wrap(err, "while loading chart")
	}

	return c, nil
}

// discoverPathToHelmChart returns the full path to the Helm Chart directory from `bundleChartDirName` folder
//
// - if more that one directory is found then error is returned
// - if additional files are found under the `bundleChartDirName` directory then
//   they are ignored but logged as warning to improve transparency.
func (l Loader) discoverPathToHelmChart(baseDir string) (string, error) {
	cDir := filepath.Join(baseDir, bundleChartDirName)
	rawFiles, err := ioutil.ReadDir(cDir)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return "", errors.Errorf("bundle does not contains %q directory", bundleChartDirName)
	default:
		return "", errors.Wrapf(err, "while reading directory %s", cDir)
	}

	directories, files := splitForDirectoriesAndFiles(rawFiles)
	if len(directories) == 0 {
		return "", fmt.Errorf("%q directory SHOULD contain one Helm Chart folder but it's empty", bundleChartDirName)
	}

	if len(directories) > 1 {
		return "", fmt.Errorf("%q directory MUST contain only one Helm Chart folder but found multiple directories: [%s]", bundleChartDirName, strings.Join(directories, ", "))
	}

	if len(files) != 0 { // ignoring by design
		l.log.Warningf("Found files: [%s] in %q bundle directory. All are ignored.", strings.Join(files, ", "), bundleChartDirName)
	}

	chartFullPath := filepath.Join(cDir, directories[0])
	return chartFullPath, nil
}

func splitForDirectoriesAndFiles(rawFiles []os.FileInfo) (dirs []string, files []string) {
	for _, f := range rawFiles {
		if f.IsDir() {
			dirs = append(dirs, f.Name())
		} else {
			files = append(files, f.Name())
		}
	}

	return dirs, files
}

func (l Loader) createFormFromBundleDir(baseDir string) (*form, error) {
	f := &form{Plans: make(map[string]*formPlan)}

	bundleMetaFile, err := ioutil.ReadFile(filepath.Join(baseDir, bundleMetaName))
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, fmt.Errorf("missing metadata information about bundle, please check if bundle contains %q file", bundleMetaName)
	default:
		return nil, errors.Wrapf(err, "while reading %q file", bundleMetaName)
	}

	if err := yaml.Unmarshal(bundleMetaFile, &f.Meta); err != nil {
		return nil, errors.Wrapf(err, "while unmarshaling bundle %q file", bundleMetaName)
	}

	plansPath := filepath.Join(baseDir, bundlePlanDirName)
	files, err := ioutil.ReadDir(plansPath)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, fmt.Errorf("bundle does not contains any plans, please check if bundle contains %q directory", bundlePlanDirName)
	default:
		return nil, errors.Wrapf(err, "while reading %q file", bundleMetaName)
	}

	for _, fileInfo := range files {
		if fileInfo.IsDir() {
			planName := fileInfo.Name()
			f.Plans[planName] = &formPlan{}

			if err := l.loadPlanDefinition(filepath.Join(plansPath, planName), f.Plans[planName]); err != nil {
				return nil, err
			}
		}
	}

	return f, nil
}

func (Loader) loadPlanDefinition(path string, plan *formPlan) error {
	topdir, err := filepath.Abs(path)
	if err != nil {
		return err
	}

	unmarshalPlanErr := func(err error, filename string) error {
		return errors.Wrapf(err, "while unmarshaling plan %q file", filename)
	}

	if err := yamlUnmarshal(topdir, bundlePlanMetaName, &plan.Meta, true); err != nil {
		return unmarshalPlanErr(err, bundlePlanMetaName)
	}

	if err := yamlUnmarshal(topdir, bundlePlanValuesFileName, &plan.Values, false); err != nil {
		return unmarshalPlanErr(err, bundlePlanValuesFileName)
	}

	if plan.SchemasCreate, err = loadPlanSchema(topdir, bundlePlaSchemaCreateJSONName, false); err != nil {
		return unmarshalPlanErr(err, bundlePlaSchemaCreateJSONName)
	}

	if plan.SchemasUpdate, err = loadPlanSchema(topdir, bundlePlanSchemaUpdateJSONName, false); err != nil {
		return unmarshalPlanErr(err, bundlePlanSchemaUpdateJSONName)
	}

	if plan.BindTemplate, err = loadRaw(topdir, bundlePlanBindTemplateFileName, false); err != nil {
		return errors.Wrapf(err, "while loading plan %q file", bundlePlanBindTemplateFileName)
	}

	return nil
}

// unpackArchive unpack from a reader containing a compressed tar archive to tmpdir.
func (l Loader) unpackArchive(baseDir string, in io.Reader) (string, error) {
	dir, err := l.createTmpDir(baseDir, "unpacked-bundle")
	if err != nil {
		return "", err
	}

	unzipped, err := gzip.NewReader(in)
	if err != nil {
		return "", err
	}
	defer unzipped.Close()

	tr := tar.NewReader(unzipped)

Unpack:
	for {
		header, err := tr.Next()
		switch err {
		case nil:
		case io.EOF:
			break Unpack
		default:
			return "", err
		}

		// the target location where the dir/file should be created
		target := filepath.Join(dir, header.Name)

		// check the file type
		switch header.Typeflag {
		// its a dir and if it doesn't exist - create it
		case tar.TypeDir:
			if _, err := os.Stat(target); os.IsNotExist(err) {
				if err := os.MkdirAll(target, 0755); err != nil {
					return "", err
				}
			}
			// it's a file - create it
		case tar.TypeReg:
			if err := l.createFile(target, header.Mode, tr); err != nil {
				return "", err
			}
		}
	}

	return dir, nil
}

func (Loader) createFile(target string, mode int64, r io.Reader) error {
	f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(mode))
	if err != nil {
		return err
	}
	defer f.Close()

	// copy over contents
	if _, err := io.Copy(f, r); err != nil {
		return err
	}
	return nil
}

func yamlUnmarshal(basePath, fileName string, unmarshalTo interface{}, required bool) error {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil
	case os.IsNotExist(err) && required:
		return fmt.Errorf("%q is required but is not present", fileName)
	default:
		return err
	}

	return yaml.Unmarshal(b, unmarshalTo)
}

func loadPlanSchema(basePath, fileName string, required bool) (*internal.PlanSchema, error) {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil, nil
	case os.IsNotExist(err) && required:
		return nil, fmt.Errorf("%q is required but is not present", fileName)
	default:
		return nil, errors.Wrap(err, "while loading plan schema")
	}

	// OSB API defines: Schemas MUST NOT be larger than 64kB.
	// See: https://github.com/openservicebrokerapi/servicebroker/blob/v2.13/spec.md#schema-object
	if len(b) >= maxSchemaLength {
		return nil, fmt.Errorf("schema %s is larger than 64 kB", fileName)
	}

	var schema internal.PlanSchema
	err = json.Unmarshal(b, &schema)
	return &schema, errors.Wrap(err, "while loading plan shcema")
}

func loadRaw(basePath, fileName string, required bool) ([]byte, error) {
	b, err := ioutil.ReadFile(filepath.Join(basePath, fileName))
	switch {
	case err == nil:
	case os.IsNotExist(err) && !required:
		return nil, nil
	case os.IsNotExist(err) && required:
		return nil, fmt.Errorf("%q is required but is not present", fileName)
	default:
		return nil, err
	}

	return b, nil
}
<|endoftext|>