Adds support for linking using service binding without the service binding operator (#4905)

* Adds support for linking using service binding without the service binding operator

* Uses isLinkResource() to detect the service binding resources

* Updates the success message for operator based links

* Updates the flow to creating the deployment first

* Adds annotations to the Redis CR in devfile-with-link.yaml file

* Updates the flow to create only the services before the deployment.

It also separates the push related code for link and service creation.

* Moves the for loop in setLinksServiceNames() inside the if condition

* Adds and returns a error message when the pipeline throws a forbidden type of error

* Moves the updation of services and pvcs with owner references before attempting creation of links
This commit is contained in:
Mrinal Das
2021-08-02 21:12:41 +05:30
committed by GitHub
parent b2ca2a81a0
commit 2e661bf71d
193 changed files with 31956 additions and 361 deletions

3
go.mod
View File

@@ -38,7 +38,7 @@ require (
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.1.1
github.com/redhat-developer/service-binding-operator v0.7.1
github.com/redhat-developer/service-binding-operator v0.9.0
github.com/securego/gosec/v2 v2.8.0
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
@@ -63,6 +63,7 @@ require (
k8s.io/klog v1.0.0
k8s.io/klog/v2 v2.4.0
k8s.io/kubectl v0.20.1
sigs.k8s.io/controller-runtime v0.7.0
sigs.k8s.io/yaml v1.2.0
)

5
go.sum
View File

@@ -721,6 +721,7 @@ github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RR
github.com/mikefarah/yq/v2 v2.4.1/go.mod h1:i8SYf1XdgUvY2OFwSqGAtWOOgimD2McJ6iutoxRm4k0=
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -936,6 +937,10 @@ github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redhat-developer/service-binding-operator v0.7.1 h1:oFB0m3iHaFf8bRotao+shKx8BmbWPE4krGvgmJF1wjI=
github.com/redhat-developer/service-binding-operator v0.7.1/go.mod h1:Z3fFouJGqy08JVWBFgb9ZyDcddcqx+AUIuMOeMFU8pQ=
github.com/redhat-developer/service-binding-operator v0.8.0 h1:33nrUwKm+Osr8I/g9qZZ6Hf41dfePfZndS02/xyAYiI=
github.com/redhat-developer/service-binding-operator v0.8.0/go.mod h1:Z3fFouJGqy08JVWBFgb9ZyDcddcqx+AUIuMOeMFU8pQ=
github.com/redhat-developer/service-binding-operator v0.9.0 h1:CS+eEtzu/PtWuyvYQFQpZXd6ukSuFtN+U0EKKtTsvlA=
github.com/redhat-developer/service-binding-operator v0.9.0/go.mod h1:D415gZQiz5Q8zyRbmrNrlieb6Xp73oFtCb+nCuTL6GA=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=

View File

@@ -18,10 +18,10 @@ import (
"github.com/devfile/library/pkg/devfile/parser"
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
"github.com/openshift/odo/pkg/devfile/adapters/common"
"github.com/openshift/odo/pkg/envinfo"
"github.com/openshift/odo/pkg/kclient"
"github.com/openshift/odo/pkg/localConfigProvider"
"github.com/openshift/odo/pkg/envinfo"
"github.com/openshift/odo/pkg/service"
"github.com/pkg/errors"
"k8s.io/klog"
@@ -38,7 +38,7 @@ import (
"github.com/openshift/odo/pkg/sync"
urlpkg "github.com/openshift/odo/pkg/url"
"github.com/openshift/odo/pkg/util"
servicebinding "github.com/redhat-developer/service-binding-operator/api/v1alpha1"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -1490,11 +1490,11 @@ func GetComponentState(client *occlient.Client, componentName, applicationName s
// GetComponent provides component definition
func GetComponent(client *occlient.Client, componentName string, applicationName string, projectName string) (component Component, err error) {
return getRemoteComponentMetadata(client, componentName, applicationName, projectName, true, true)
return getRemoteComponentMetadata(client, componentName, applicationName, true, true)
}
// getRemoteComponentMetadata provides component metadata from the cluster
func getRemoteComponentMetadata(client *occlient.Client, componentName string, applicationName string, projectName string, getUrls, getStorage bool) (component Component, err error) {
func getRemoteComponentMetadata(client *occlient.Client, componentName string, applicationName string, getUrls, getStorage bool) (component Component, err error) {
fromCluster, err := GetPushedComponent(client, componentName, applicationName)
if err != nil || fromCluster == nil {
return Component{}, errors.Wrapf(err, "unable to get remote metadata for %s component", componentName)
@@ -1562,18 +1562,12 @@ func getRemoteComponentMetadata(client *occlient.Client, componentName string, a
}
}
ok, err := client.GetKubeClient().IsServiceBindingSupported()
linkedSecrets := fromCluster.GetLinkedSecrets()
err = setLinksServiceNames(client, linkedSecrets, componentlabels.GetSelector(componentName, applicationName))
if err != nil {
return Component{}, fmt.Errorf("unable to check if service binding is supported: %w", err)
}
if ok {
linkedSecrets := fromCluster.GetLinkedSecrets()
err = setLinksServiceNames(client, linkedSecrets)
if err != nil {
return Component{}, fmt.Errorf("unable to get name of services: %w", err)
}
component.Status.LinkedServices = linkedSecrets
return Component{}, fmt.Errorf("unable to get name of services: %w", err)
}
component.Status.LinkedServices = linkedSecrets
component.Namespace = client.Namespace
component.Spec.App = applicationName
@@ -1584,31 +1578,82 @@ func getRemoteComponentMetadata(client *occlient.Client, componentName string, a
}
// setLinksServiceNames sets the service name of the links from the info in ServiceBindingRequests present in the cluster
func setLinksServiceNames(client *occlient.Client, linkedSecrets []SecretMount) error {
serviceBindings := map[string]string{}
list, err := client.GetKubeClient().ListDynamicResource(kclient.ServiceBindingGroup, kclient.ServiceBindingVersion, kclient.ServiceBindingResource)
if err != nil || list == nil {
return err
func setLinksServiceNames(client *occlient.Client, linkedSecrets []SecretMount, selector string) error {
ok, err := client.GetKubeClient().IsServiceBindingSupported()
if err != nil {
return fmt.Errorf("unable to check if service binding is supported: %w", err)
}
for _, u := range list.Items {
var sbr servicebinding.ServiceBinding
js, err := u.MarshalJSON()
serviceBindings := map[string]string{}
if ok {
// service binding operator is installed on the cluster
list, err := client.GetKubeClient().ListDynamicResource(kclient.ServiceBindingGroup, kclient.ServiceBindingVersion, kclient.ServiceBindingResource)
if err != nil || list == nil {
return err
}
for _, u := range list.Items {
var sbr servicebinding.ServiceBinding
js, err := u.MarshalJSON()
if err != nil {
return err
}
err = json.Unmarshal(js, &sbr)
if err != nil {
return err
}
services := sbr.Spec.Services
if len(services) != 1 {
return errors.New("the ServiceBinding resource should define only one service")
}
service := services[0]
if service.Kind == "Service" {
serviceBindings[sbr.Status.Secret] = service.Name
} else {
serviceBindings[sbr.Status.Secret] = service.Kind + "/" + service.Name
}
}
} else {
// service binding operator is not installed
// get the secrets instead of the service binding objects to retrieve the link data
secrets, err := client.GetKubeClient().ListSecrets(selector)
if err != nil {
return err
}
err = json.Unmarshal(js, &sbr)
// get the services to get their names against the component names
services, err := client.GetKubeClient().ListServices("")
if err != nil {
return err
}
services := sbr.Spec.Services
if len(services) != 1 {
return errors.New("the ServiceBinding resource should define only one service")
serviceCompMap := make(map[string]string)
for _, gotService := range services {
serviceCompMap[gotService.Labels[componentlabels.ComponentLabel]] = gotService.Name
}
service := services[0]
if service.Kind == "Service" {
serviceBindings[sbr.Status.Secret] = service.Name
} else {
serviceBindings[sbr.Status.Secret] = service.Kind + "/" + service.Name
for _, secret := range secrets {
serviceName, serviceOK := secret.Labels[service.ServiceLabel]
_, linkOK := secret.Labels[service.LinkLabel]
serviceKind, serviceKindOK := secret.Labels[service.ServiceKind]
if serviceKindOK && serviceOK && linkOK {
if serviceKind == "Service" {
if _, ok := serviceBindings[secret.Name]; !ok {
serviceBindings[secret.Name] = serviceCompMap[serviceName]
}
} else {
// service name is stored as kind-name in the labels
parts := strings.SplitN(serviceName, "-", 2)
if len(parts) < 2 {
continue
}
serviceName = fmt.Sprintf("%v/%v", parts[0], parts[1])
if _, ok := serviceBindings[secret.Name]; !ok {
serviceBindings[secret.Name] = serviceName
}
}
}
}
}

View File

@@ -139,7 +139,7 @@ func NewComponentFullDescriptionFromClientAndLocalConfig(client *occlient.Client
}
cfd.Status.State = state
if state == StateTypePushed {
componentDescFromCluster, err := getRemoteComponentMetadata(client, componentName, applicationName, projectName, false, false)
componentDescFromCluster, err := getRemoteComponentMetadata(client, componentName, applicationName, false, false)
if err != nil {
return cfd, err
}

View File

@@ -159,30 +159,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
}
s.End(true)
log.Info("\nUpdating services")
// fetch the "kubernetes inlined components" to create them on cluster
// from odo standpoint, these components contain yaml manifest of an odo service or an odo link
k8sComponents, err := a.Devfile.Data.GetComponents(parsercommon.DevfileOptions{
ComponentOptions: parsercommon.ComponentOptions{ComponentType: devfilev1.KubernetesComponentType},
})
if err != nil {
return errors.Wrap(err, "error while trying to fetch service(s) from devfile")
}
labels := componentlabels.GetLabels(a.ComponentName, a.AppName, true)
// create the Kubernetes objects from the manifest and delete the ones not in the devfile
needRestart, err := service.PushServiceFromKubernetesInlineComponents(a.Client.GetKubeClient(), k8sComponents, labels)
if err != nil {
return errors.Wrap(err, "failed to create service(s) associated with the component")
}
if componentExists && needRestart {
err = a.Client.GetKubeClient().WaitForPodNotReady(podName)
if err != nil {
return err
}
}
log.Infof("\nCreating Kubernetes resources for component %s", a.ComponentName)
previousMode := parameters.EnvSpecificInfo.GetRunMode()
currentMode := envinfo.Run
@@ -200,6 +177,25 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
parameters.RunModeChanged = true
}
// fetch the "kubernetes inlined components" to create them on cluster
// from odo standpoint, these components contain yaml manifest of an odo service or an odo link
k8sComponents, err := a.Devfile.Data.GetComponents(parsercommon.DevfileOptions{
ComponentOptions: parsercommon.ComponentOptions{ComponentType: devfilev1.KubernetesComponentType},
})
if err != nil {
return errors.Wrap(err, "error while trying to fetch service(s) from devfile")
}
log.Infof("\nCreating Services for component %s", a.ComponentName)
// create the Kubernetes objects from the manifest and delete the ones not in the devfile
err = service.PushServices(a.Client.GetKubeClient(), k8sComponents, labels)
if err != nil {
return errors.Wrap(err, "failed to create service(s) associated with the component")
}
log.Infof("\nCreating Kubernetes resources for component %s", a.ComponentName)
err = a.createOrUpdateComponent(componentExists, parameters.EnvSpecificInfo)
if err != nil {
return errors.Wrap(err, "unable to create or update component")
@@ -234,11 +230,38 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
}
}
err = service.UpdateKubernetesInlineComponentsOwnerReferences(a.Client.GetKubeClient(), k8sComponents, ownerReference)
err = service.UpdateServicesWithOwnerReferences(a.Client.GetKubeClient(), k8sComponents, ownerReference)
if err != nil {
return err
}
// create the Kubernetes objects from the manifest and delete the ones not in the devfile
needRestart, err := service.PushLinks(a.Client.GetKubeClient(), k8sComponents, labels, a.deployment)
if err != nil {
return errors.Wrap(err, "failed to create service(s) associated with the component")
}
if needRestart {
s := log.Spinner("Restarting the component")
defer s.End(false)
err = a.Client.GetKubeClient().WaitForPodDeletion(pod.Name)
if err != nil {
return err
}
s.End(true)
}
a.deployment, err = a.Client.GetKubeClient().WaitForDeploymentRollout(a.deployment.Name)
if err != nil {
return errors.Wrap(err, "error while waiting for deployment rollout")
}
// Wait for Pod to be in running state otherwise we can't sync data or exec commands to it.
pod, err = a.getPod(true)
if err != nil {
return errors.Wrapf(err, "unable to get pod for component %s", a.ComponentName)
}
parameters.EnvSpecificInfo.SetDevfileObj(a.Devfile)
err = component.ApplyConfig(&a.Client, config.LocalConfigInfo{}, parameters.EnvSpecificInfo, color.Output, componentExists, false)
if err != nil {

View File

@@ -21,6 +21,7 @@ import (
"k8s.io/klog"
componentlabels "github.com/openshift/odo/pkg/component/labels"
apiMachineryWatch "k8s.io/apimachinery/pkg/watch"
)
func boolPtr(b bool) *bool {
@@ -116,8 +117,8 @@ func (c *Client) ListDeployments(selector string) (*appsv1.DeploymentList, error
})
}
// WaitForPodNotReady waits for the status of the given pod to be not ready, or the pod to be deleted
func (c *Client) WaitForPodNotReady(name string) error {
// WaitForPodDeletion waits for the given pod to be deleted
func (c *Client) WaitForPodDeletion(name string) error {
watch, err := c.KubeClient.CoreV1().Pods(c.Namespace).Watch(context.TODO(), metav1.ListOptions{FieldSelector: "metadata.name=" + name})
if err != nil {
return err
@@ -131,20 +132,14 @@ func (c *Client) WaitForPodNotReady(name string) error {
for {
select {
case <-time.After(time.Minute):
return fmt.Errorf("timeout while waiting for %q pod to stop", name)
return fmt.Errorf("timeout while waiting for %q pod to be deleted", name)
case val, ok := <-watch.ResultChan():
if !ok {
return errors.New("error getting value from resultchan")
}
if pod, ok := val.Object.(*corev1.Pod); ok {
for _, cond := range pod.Status.Conditions {
if cond.Type == "Ready" {
if cond.Status == corev1.ConditionFalse {
return nil
}
}
}
if val.Type == apiMachineryWatch.Deleted {
return nil
}
}
}
@@ -329,13 +324,15 @@ func (c *Client) DeleteDeployment(labels map[string]string) error {
}
// CreateDynamicResource creates a dynamic custom resource
func (c *Client) CreateDynamicResource(exampleCustomResource map[string]interface{}, group, version, resource string) error {
func (c *Client) CreateDynamicResource(exampleCustomResource map[string]interface{}, ownerReferences []metav1.OwnerReference, group, version, resource string) error {
deploymentRes := schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
deployment := &unstructured.Unstructured{
Object: exampleCustomResource,
}
deployment.SetOwnerReferences(ownerReferences)
debugOut, _ := json.MarshalIndent(deployment, " ", " ")
klog.V(5).Infoln("Creating resource:")
klog.V(5).Infoln(string(debugOut))

View File

@@ -107,6 +107,24 @@ func (c *Client) GetSecret(name, namespace string) (*corev1.Secret, error) {
return secret, nil
}
// UpdateSecret updates the given Secret object in the given namespace
func (c *Client) UpdateSecret(secret *corev1.Secret, namespace string) (*corev1.Secret, error) {
secret, err := c.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return nil, errors.Wrapf(err, "unable to update the secret %s", secret)
}
return secret, nil
}
// DeleteSecret updates the given Secret object in the given namespace
func (c *Client) DeleteSecret(secretName, namespace string) error {
err := c.KubeClient.CoreV1().Secrets(namespace).Delete(context.TODO(), secretName, metav1.DeleteOptions{})
if err != nil {
return errors.Wrapf(err, "unable to delete the secret %s", secretName)
}
return nil
}
// CreateSecret generates and creates the secret
// commonObjectMeta is the ObjectMeta for the service
func (c *Client) CreateSecret(objectMeta metav1.ObjectMeta, data map[string]string, ownerReference metav1.OwnerReference) error {
@@ -124,7 +142,7 @@ func (c *Client) CreateSecret(objectMeta metav1.ObjectMeta, data map[string]stri
return nil
}
// Create a secret for each port, containing the host and port of the component
// CreateSecrets creates a secret for each port, containing the host and port of the component
// This is done so other components can later inject the secret into the environment
// and have the "coordinates" to communicate with this component
func (c *Client) CreateSecrets(componentName string, commonObjectMeta metav1.ObjectMeta, svc *corev1.Service, ownerReference metav1.OwnerReference) error {

View File

@@ -14,7 +14,7 @@ import (
"github.com/openshift/odo/pkg/secret"
svc "github.com/openshift/odo/pkg/service"
"github.com/openshift/odo/pkg/util"
servicebinding "github.com/redhat-developer/service-binding-operator/api/v1alpha1"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
@@ -94,7 +94,7 @@ func (o *commonLinkOptions) complete(name string, cmd *cobra.Command, args []str
return err
}
if o.csvSupport && o.Context.EnvSpecificInfo != nil {
if o.Context.EnvSpecificInfo != nil {
return o.completeForOperator()
}
@@ -134,7 +134,7 @@ func (o *commonLinkOptions) complete(name string, cmd *cobra.Command, args []str
}
func (o *commonLinkOptions) validate(wait bool) (err error) {
if o.csvSupport && o.Context.EnvSpecificInfo != nil {
if o.Context.EnvSpecificInfo != nil {
return o.validateForOperator()
}
@@ -167,7 +167,7 @@ func (o *commonLinkOptions) validate(wait bool) (err error) {
}
func (o *commonLinkOptions) run() (err error) {
if o.csvSupport && o.Context.EnvSpecificInfo != nil {
if o.Context.EnvSpecificInfo != nil {
if o.operationName == unlink {
return o.unlinkOperator()
}
@@ -281,15 +281,6 @@ func (o *commonLinkOptions) getServiceBindingName(componentName string) string {
// completeForOperator completes the options when svc is supported
func (o *commonLinkOptions) completeForOperator() (err error) {
serviceBindingSupport, err := o.Client.GetKubeClient().IsServiceBindingSupported()
if err != nil {
return err
}
if !serviceBindingSupport {
return fmt.Errorf("please install Service Binding Operator to be able to create/delete a link\nrefer https://odo.dev/docs/install-service-binding-operator")
}
o.serviceType, o.serviceName, err = svc.IsOperatorServiceNameValid(o.suppliedName)
if err != nil {
o.serviceName = o.suppliedName
@@ -326,7 +317,7 @@ func (o *commonLinkOptions) completeForOperator() (err error) {
Spec: servicebinding.ServiceBindingSpec{
DetectBindingResources: true,
BindAsFiles: o.bindAsFiles,
Application: &servicebinding.Application{
Application: servicebinding.Application{
Ref: servicebinding.Ref{
Name: deployment.Name,
Group: deploymentGVR.Group,
@@ -344,6 +335,9 @@ func (o *commonLinkOptions) validateForOperator() (err error) {
var svcFullName string
if o.isTargetAService {
if !o.csvSupport {
return fmt.Errorf("operator hub is required for linking to services")
}
// let's validate if the service exists
svcFullName = strings.Join([]string{o.serviceType, o.serviceName}, "/")
svcExists, err := svc.OperatorSvcExists(o.KClient, svcFullName)

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"path/filepath"
servicebinding "github.com/redhat-developer/service-binding-operator/api/v1alpha1"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"github.com/spf13/cobra"
"github.com/openshift/odo/pkg/component"
@@ -124,7 +124,7 @@ func (o *LinkOptions) Validate() (err error) {
return err
}
if o.csvSupport && o.Context.EnvSpecificInfo != nil {
if o.Context.EnvSpecificInfo != nil {
return
}

335
pkg/service/link.go Normal file
View File

@@ -0,0 +1,335 @@
package service
import (
"encoding/json"
"fmt"
"strings"
devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/generator"
"github.com/ghodss/yaml"
applabels "github.com/openshift/odo/pkg/application/labels"
componentlabels "github.com/openshift/odo/pkg/component/labels"
"github.com/openshift/odo/pkg/kclient"
"github.com/openshift/odo/pkg/log"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
v1 "k8s.io/api/apps/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/builder"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
)
// PushLinks updates Link(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones
// returns true if the component needs to be restarted (when a link has been created or deleted)
// if service binding operator is not present, it will call pushLinksWithoutOperator to create the links without it.
func PushLinks(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) {
serviceBindingSupport, err := client.IsServiceBindingSupported()
if err != nil {
return false, err
}
if !serviceBindingSupport {
return pushLinksWithoutOperator(client, k8sComponents, labels, deployment)
}
return pushLinksWithOperator(client, k8sComponents, labels, deployment)
}
// pushLinksWithOperator creates links or deletes links (if service binding operator is installed) between components and services
// returns true if the component needs to be restarted (a secret was generated and added to the deployment)
func pushLinksWithOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) {
ownerReference := generator.GetOwnerReference(deployment)
deployed, err := ListDeployedServices(client, labels)
if err != nil {
return false, err
}
for key, deployedResource := range deployed {
if !deployedResource.isLinkResource {
delete(deployed, key)
}
}
restartNeeded := false
// create an object on the kubernetes cluster for all the Kubernetes Inlined components
for _, c := range k8sComponents {
// get the string representation of the YAML definition of a CRD
strCRD := c.Kubernetes.Inlined
// convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource
d := NewDynamicCRD()
err := yaml.Unmarshal([]byte(strCRD), &d.OriginalCRD)
if err != nil {
return false, err
}
if !isLinkResource(d.OriginalCRD["kind"].(string)) {
// operator hub is not installed on the cluster
// or it's a service binding related resource
continue
}
crdName, ok := getCRDName(d.OriginalCRD)
if !ok {
continue
}
cr, _, err := createOperatorService(client, d, labels, []metav1.OwnerReference{ownerReference})
delete(deployed, cr+"/"+crdName)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
// this could be the case when "odo push" was executed after making change to code but there was no change to the service itself
// TODO: better way to handle this might be introduced by https://github.com/openshift/odo/issues/4553
continue // this ensures that services slice is not updated
} else {
return false, err
}
}
name, _ := d.GetServiceNameFromCRD() // ignoring error because invalid yaml won't be inserted into devfile through odo
log.Successf("Created link %q using Service Binding Operator on the cluster; component will be restarted", name)
restartNeeded = true
}
for key, val := range deployed {
if !isLinkResource(val.Kind) {
continue
}
err = DeleteOperatorService(client, key)
if err != nil {
return false, err
}
log.Successf("Deleted link %q using Service Binding Operator on the cluster; component will be restarted", key)
restartNeeded = true
}
if !restartNeeded {
log.Success("Links are in sync with the cluster, no changes are required")
}
return restartNeeded, nil
}
// pushLinksWithoutOperator creates links or deletes links (if service binding operator is not installed) between components and services
// returns true if the component needs to be restarted (a secret was generated and added to the deployment)
func pushLinksWithoutOperator(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string, deployment *v1.Deployment) (bool, error) {
// check csv support before proceeding
csvSupport, err := IsCSVSupported()
if err != nil {
return false, err
}
secrets, err := client.ListSecrets(componentlabels.GetSelector(labels[componentlabels.ComponentLabel], labels[applabels.ApplicationLabel]))
if err != nil {
return false, err
}
ownerReferences := generator.GetOwnerReference(deployment)
clusterLinksMap := make(map[string]string)
for _, secret := range secrets {
if value, ok := secret.GetLabels()[LinkLabel]; ok {
clusterLinksMap[value] = secret.Name
}
}
localLinksMap := make(map[string]string)
// create an object on the kubernetes cluster for all the Kubernetes Inlined components
for _, c := range k8sComponents {
// get the string representation of the YAML definition of a CRD
strCRD := c.Kubernetes.Inlined
// convert the YAML definition into map[string]interface{} since it's needed to create dynamic resource
d := NewDynamicCRD()
err := yaml.Unmarshal([]byte(strCRD), &d.OriginalCRD)
if err != nil {
return false, err
}
if !isLinkResource(d.OriginalCRD["kind"].(string)) {
// not a service binding object, thus continue
continue
}
localLinksMap[c.Name] = strCRD
}
var processingPipeline pipeline.Pipeline
deploymentGVR, err := client.GetDeploymentAPIVersion()
if err != nil {
return false, err
}
var restartRequired bool
// delete the links not present on the devfile
for linkName, secretName := range clusterLinksMap {
if _, ok := localLinksMap[linkName]; !ok {
// recreate parts of the service binding request for deletion
var newServiceBinding servicebinding.ServiceBinding
newServiceBinding.Name = linkName
newServiceBinding.Namespace = client.Namespace
newServiceBinding.Spec.Application = servicebinding.Application{
Ref: servicebinding.Ref{
Name: deployment.Name,
Group: deploymentGVR.Group,
Version: deploymentGVR.Version,
Resource: deploymentGVR.Resource,
},
}
newServiceBinding.Status.Secret = secretName
// set the deletion time stamp to trigger deletion
timeNow := metav1.Now()
newServiceBinding.DeletionTimestamp = &timeNow
// if the pipeline was created before
// skip deletion
if processingPipeline == nil {
processingPipeline, err = getPipeline(client)
if err != nil {
return false, err
}
}
_, err = processingPipeline.Process(&newServiceBinding)
if err != nil {
return false, err
}
// since the library currently doesn't delete the secret after unbinding
// delete the secret manually
err = client.DeleteSecret(secretName, client.Namespace)
if err != nil {
return false, err
}
restartRequired = true
log.Successf("Deleted link %q on the cluster; component will be restarted", linkName)
}
}
var serviceCompMap map[string]string
// create the links
for linkName, strCRD := range localLinksMap {
if _, ok := clusterLinksMap[linkName]; !ok {
if serviceCompMap == nil {
// prevent listing of services unless required
services, err := client.ListServices("")
if err != nil {
return false, err
}
// get the services and get match them against the component
serviceCompMap = make(map[string]string)
for _, service := range services {
serviceCompMap[service.Name] = service.Labels[componentlabels.ComponentLabel]
}
}
// get the string representation of the YAML definition of a CRD
var serviceBinding servicebinding.ServiceBinding
err = yaml.Unmarshal([]byte(strCRD), &serviceBinding)
if err != nil {
return false, err
}
if len(serviceBinding.Spec.Services) != 1 {
continue
}
if !csvSupport && !isLinkResource(serviceBinding.Spec.Services[0].Kind) {
// ignore service binding objects linked to services if csv support is not present on the cluster
continue
}
// set the labels and namespace
serviceBinding.SetLabels(labels)
serviceBinding.Namespace = client.Namespace
serviceBinding.Spec.Services[0].Namespace = &client.Namespace
_, err = json.MarshalIndent(serviceBinding, " ", " ")
if err != nil {
return false, err
}
if processingPipeline == nil {
processingPipeline, err = getPipeline(client)
if err != nil {
return false, err
}
}
_, err = processingPipeline.Process(&serviceBinding)
if err != nil {
if kerrors.IsForbidden(err) {
// due to https://github.com/redhat-developer/service-binding-operator/issues/1003
return false, fmt.Errorf("please install the service binding operator")
}
return false, err
}
if len(serviceBinding.Status.Secret) == 0 {
return false, fmt.Errorf("no secret was provided by service binding's pipleine")
}
// get the generated secret and update it with the labels and owner reference
secret, err := client.GetSecret(serviceBinding.Status.Secret, client.Namespace)
if err != nil {
return false, err
}
secret.Labels = labels
secret.Labels[LinkLabel] = linkName
if _, ok := serviceCompMap[serviceBinding.Spec.Services[0].Name]; ok {
secret.Labels[ServiceLabel] = serviceCompMap[serviceBinding.Spec.Services[0].Name]
} else {
secret.Labels[ServiceLabel] = serviceBinding.Spec.Services[0].Name
}
secret.Labels[ServiceKind] = serviceBinding.Spec.Services[0].Kind
if serviceBinding.Spec.Services[0].Kind != "Service" {
// the service name is stored as kind-name as `/` is not a valid char for labels of kubernetes secrets
secret.Labels[ServiceLabel] = fmt.Sprintf("%v-%v", serviceBinding.Spec.Services[0].Kind, serviceBinding.Spec.Services[0].Name)
}
secret.SetOwnerReferences([]metav1.OwnerReference{ownerReferences})
_, err = client.UpdateSecret(secret, client.Namespace)
if err != nil {
return false, err
}
restartRequired = true
log.Successf("Created link %q on the cluster; component will be restarted", linkName)
}
}
if restartRequired {
return true, nil
} else {
log.Success("Links are in sync with the cluster, no changes are required")
}
return false, nil
}
// getPipeline gets the pipeline to process service binding requests
func getPipeline(client *kclient.Client) (pipeline.Pipeline, error) {
mgr, err := ctrl.NewManager(client.KubeClientConfig, ctrl.Options{
Scheme: runtime.NewScheme(),
// disable the health probes to prevent binding to them
HealthProbeBindAddress: "0",
// disable the prometheus metrics
MetricsBindAddress: "0",
})
if err != nil {
return nil, err
}
return builder.DefaultBuilder.WithContextProvider(context.Provider(client.DynamicClient, context.ResourceLookup(mgr.GetRESTMapper()))).Build(), nil
}

View File

@@ -5,8 +5,6 @@ import (
"fmt"
"strings"
"github.com/ghodss/yaml"
devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser/data/v2/common"
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
@@ -28,13 +26,24 @@ import (
"github.com/pkg/errors"
"github.com/devfile/library/pkg/devfile/parser"
servicebinding "github.com/redhat-developer/service-binding-operator/api/v1alpha1"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"github.com/ghodss/yaml"
)
const provisionedAndBoundStatus = "ProvisionedAndBound"
const provisionedAndLinkedStatus = "ProvisionedAndLinked"
const apiVersion = "odo.dev/v1alpha1"
// LinkLabel is the name of the name of the link in the devfile
const LinkLabel = "app.kubernetes.io/link-name"
// ServiceLabel is the name of the service in the service binding object
const ServiceLabel = "app.kubernetes.io/service-name"
// ServiceKind is the kind of the service in the service binding object
const ServiceKind = "app.kubernetes.io/service-kind"
// NewServicePlanParameter creates a new ServicePlanParameter instance with the specified state
func NewServicePlanParameter(name, typeName, defaultValue string, required bool) ServicePlanParameter {
return ServicePlanParameter{
@@ -106,7 +115,7 @@ func doesCRExist(kind string, csvs *olm.ClusterServiceVersionList) (olm.ClusterS
// CreateOperatorService creates new service (actually a Deployment) from OperatorHub
func CreateOperatorService(client *kclient.Client, group, version, resource string, CustomResourceDefinition map[string]interface{}) error {
err := client.CreateDynamicResource(CustomResourceDefinition, group, version, resource)
err := client.CreateDynamicResource(CustomResourceDefinition, nil, group, version, resource)
if err != nil {
return errors.Wrap(err, "unable to create operator backed service")
}
@@ -933,42 +942,26 @@ func (d *DynamicCRD) AddComponentLabelsToCRD(labels map[string]string) {
metaMap["labels"] = labels
}
// PushServiceFromKubernetesInlineComponents updates service(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones
// returns true if the component needs to be restarted (when a service binding has been created or deleted)
func PushServiceFromKubernetesInlineComponents(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string) (bool, error) {
// PushServices updates service(s) from Kubernetes Inlined component in a devfile by creating new ones or removing old ones
func PushServices(client *kclient.Client, k8sComponents []devfile.Component, labels map[string]string) error {
// check csv support before proceeding
csvSupported, err := IsCSVSupported()
if err != nil || !csvSupported {
return false, err
if err != nil {
return err
}
type DeployedInfo struct {
DoesDeleteRestartsComponent bool
Kind string
Name string
deployed, err := ListDeployedServices(client, labels)
if err != nil {
return err
}
deployed := map[string]DeployedInfo{}
deployedServices, _, err := ListOperatorServices(client)
if err != nil && err != kclient.ErrNoSuchOperator {
// We ignore ErrNoSuchOperator error as we can deduce Operator Services are not installed
return false, err
}
for _, svc := range deployedServices {
name := svc.GetName()
kind := svc.GetKind()
deployedLabels := svc.GetLabels()
if deployedLabels[applabels.ManagedBy] == "odo" && deployedLabels[componentlabels.ComponentLabel] == labels[componentlabels.ComponentLabel] {
deployed[kind+"/"+name] = DeployedInfo{
DoesDeleteRestartsComponent: isLinkResource(kind),
Kind: kind,
Name: name,
}
for key, deployedResource := range deployed {
if deployedResource.isLinkResource {
delete(deployed, key)
}
}
needRestart := false
madeChange := false
// create an object on the kubernetes cluster for all the Kubernetes Inlined components
@@ -980,90 +973,101 @@ func PushServiceFromKubernetesInlineComponents(client *kclient.Client, k8sCompon
d := NewDynamicCRD()
err := yaml.Unmarshal([]byte(strCRD), &d.OriginalCRD)
if err != nil {
return false, err
return err
}
cr, csv, err := GetCSV(client, d.OriginalCRD)
if err != nil {
return false, err
if !csvSupported || (isLinkResource(d.OriginalCRD["kind"].(string))) {
// operator hub is not installed on the cluster
// or it's a service binding related resource
continue
}
var group, version, kind, resource string
for _, crd := range csv.Spec.CustomResourceDefinitions.Owned {
if crd.Kind == cr {
group, version, kind, resource, err = getGVKRFromCR(crd)
if err != nil {
return false, err
}
break
}
}
// add labels to the CRD before creation
d.AddComponentLabelsToCRD(labels)
crdName, ok := getCRDName(d.OriginalCRD)
if !ok {
continue
}
if _, found := deployed[cr+"/"+crdName]; !found && isLinkResource(cr) {
// If creating the ServiceBinding, the component will restart
needRestart = true
}
cr, kind, err := createOperatorService(client, d, labels, []metav1.OwnerReference{})
delete(deployed, cr+"/"+crdName)
// create the service on cluster
err = client.CreateDynamicResource(d.OriginalCRD, group, version, resource)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
// this could be the case when "odo push" was executed after making change to code but there was no change to the service itself
// TODO: better way to handle this might be introduced by https://github.com/openshift/odo/issues/4553
continue // this ensures that services slice is not updated
} else {
return false, err
return err
}
}
name, _ := d.GetServiceNameFromCRD() // ignoring error because invalid yaml won't be inserted into devfile through odo
if isLinkResource(cr) {
log.Successf("Created link %q on the cluster; component will be restarted", name)
} else {
log.Successf("Created service %q on the cluster; refer %q to know how to link it to the component", strings.Join([]string{kind, name}, "/"), "odo link -h")
}
log.Successf("Created service %q on the cluster; refer %q to know how to link it to the component", strings.Join([]string{kind, name}, "/"), "odo link -h")
madeChange = true
}
for key, val := range deployed {
if !csvSupported || (isLinkResource(val.Kind)) {
continue
}
err = DeleteOperatorService(client, key)
if err != nil {
return false, err
return err
}
if isLinkResource(val.Kind) {
log.Successf("Deleted link %q on the cluster; component will be restarted", val.Name)
} else {
log.Successf("Deleted service %q from the cluster", key)
}
log.Successf("Deleted service %q from the cluster", key)
madeChange = true
if val.DoesDeleteRestartsComponent {
needRestart = true
}
}
if !madeChange {
log.Success("Services and Links are in sync with the cluster, no changes are required")
log.Success("Services are in sync with the cluster, no changes are required")
}
return needRestart, nil
return nil
}
// UpdateKubernetesInlineComponentsOwnerReferences adds an owner reference to an inlined Kubernetes resource
// DeployedInfo holds information about the services present on the cluster
type DeployedInfo struct {
Kind string
Name string
isLinkResource bool
}
func ListDeployedServices(client *kclient.Client, labels map[string]string) (map[string]DeployedInfo, error) {
deployed := map[string]DeployedInfo{}
deployedServices, _, err := ListOperatorServices(client)
if err != nil && err != kclient.ErrNoSuchOperator {
// We ignore ErrNoSuchOperator error as we can deduce Operator Services are not installed
return nil, err
}
for _, svc := range deployedServices {
name := svc.GetName()
kind := svc.GetKind()
deployedLabels := svc.GetLabels()
if deployedLabels[applabels.ManagedBy] == "odo" && deployedLabels[componentlabels.ComponentLabel] == labels[componentlabels.ComponentLabel] {
deployed[kind+"/"+name] = DeployedInfo{
Kind: kind,
Name: name,
isLinkResource: isLinkResource(kind),
}
}
}
return deployed, nil
}
// UpdateServicesWithOwnerReferences adds an owner reference to an inlined Kubernetes resource (except service binding objects)
// if not already present in the list of owner references
func UpdateKubernetesInlineComponentsOwnerReferences(client *kclient.Client, k8sComponents []devfile.Component, ownerReference metav1.OwnerReference) error {
func UpdateServicesWithOwnerReferences(client *kclient.Client, k8sComponents []devfile.Component, ownerReference metav1.OwnerReference) error {
csvSupport, err := client.IsCSVSupported()
if err != nil {
return err
}
if !csvSupport {
return nil
}
for _, c := range k8sComponents {
// get the string representation of the YAML definition of a CRD
strCRD := c.Kubernetes.Inlined
@@ -1075,6 +1079,11 @@ func UpdateKubernetesInlineComponentsOwnerReferences(client *kclient.Client, k8s
return err
}
if isLinkResource(d.OriginalCRD["kind"].(string)) {
// ignore service binding resources
continue
}
cr, csv, err := GetCSV(client, d.OriginalCRD)
if err != nil {
return err
@@ -1136,3 +1145,34 @@ func getCRDName(crd map[string]interface{}) (string, bool) {
func isLinkResource(kind string) bool {
return kind == "ServiceBinding"
}
// createOperatorService creates the given operator on the cluster
// it returns the CR,Kind and errors
func createOperatorService(client *kclient.Client, d *DynamicCRD, labels map[string]string, ownerReferences []metav1.OwnerReference) (string, string, error) {
cr, csv, err := GetCSV(client, d.OriginalCRD)
if err != nil {
return "", "", err
}
var group, version, kind, resource string
for _, crd := range csv.Spec.CustomResourceDefinitions.Owned {
if crd.Kind == cr {
group, version, kind, resource, err = getGVKRFromCR(crd)
if err != nil {
return cr, "", err
}
break
}
}
// add labels to the CRD before creation
d.AddComponentLabelsToCRD(labels)
// create the service on cluster
err = client.CreateDynamicResource(d.OriginalCRD, ownerReferences, group, version, resource)
if err != nil {
// return the cr name for deletion from the push map in the push code
return cr, "", err
}
return cr, kind, err
}

View File

@@ -32,15 +32,12 @@ EOF
}
if [ $KUBERNETES == "true" ]; then
# install "redis-oprator" using "kubectl" in "operators" namespace; use "operatorhubio-catalog" catalog soure from "olm" namespace
# install "redis-oprator" using "kubectl" in "operators" namespace; use "operatorhubio-catalog" catalog source from "olm" namespace
install_redis_operator kubectl operators operatorhubio-catalog olm
# install "service-binding-operator" using "kubectl" in "operators" namespace; use "operatorhubio-catalog" catalog soure from "olm" namespace
install_service_binding_operator kubectl operators service-binding-operator operatorhubio-catalog olm
else
# install "redis-oprator" using "oc" in "openshift-operators" namespace; use "community-operators" catalog soure from "openshift-marketplace" namespace
# install "redis-oprator" using "oc" in "openshift-operators" namespace; use "community-operators" catalog source from "openshift-marketplace" namespace
install_redis_operator oc openshift-operators community-operators openshift-marketplace
# install "service-binding-operator" using "oc" in "openshift-operators" namespace; use "redhat-operators" catalog soure from "openshift-marketplace" namespace
# install "service-binding-operator" using "oc" in "openshift-operators" namespace; use "redhat-operators" catalog source from "openshift-marketplace" namespace
install_service_binding_operator oc openshift-operators rh-service-binding-operator redhat-operators openshift-marketplace
fi

View File

@@ -47,6 +47,8 @@ components:
kind: Redis
metadata:
name: myredis
annotations:
service.binding/name: path={.metadata.name}
spec:
redisExporter:
enabled: true

View File

@@ -19,6 +19,7 @@ type CliRunner interface {
DeleteNamespaceProject(projectName string)
DeletePod(podName string, projectName string)
GetEnvsDevFileDeployment(componentName, appName, projectName string) map[string]string
GetEnvRefNames(componentName, appName, projectName string) []string
GetPVCSize(compName, storageName, namespace string) string
GetAllPVCNames(namespace string) []string
GetPodInitContainers(compName, namespace string) []string
@@ -31,4 +32,5 @@ type CliRunner interface {
WaitForRunnerCmdOut(args []string, timeout int, errOnFail bool, check func(output string) bool, includeStdErr ...bool) bool
PodsShouldBeRunning(project string, regex string)
CreateSecret(secretName, secretPass, project string)
GetSecrets(project string) string
}

View File

@@ -319,3 +319,13 @@ func (kubectl KubectlRunner) WaitForRunnerCmdOut(args []string, timeout int, err
func (kubectl KubectlRunner) CreateSecret(secretName, secretPass, project string) {
Cmd(kubectl.path, "create", "secret", "generic", secretName, "--from-literal=password="+secretPass, "-n", project).ShouldPass()
}
// GetSecrets gets all the secrets belonging to the project
func (kubectl KubectlRunner) GetSecrets(project string) string {
return GetSecrets(kubectl.path, project)
}
// GetEnvRefNames gets the ref values from the envFroms of the deployment belonging to the given data
func (kubectl KubectlRunner) GetEnvRefNames(componentName, appName, projectName string) []string {
return GetEnvRefNames(kubectl.path, componentName, appName, projectName)
}

View File

@@ -542,6 +542,11 @@ func (oc OcRunner) GetEnvsDevFileDeployment(componentName, appName, projectName
return mapOutput
}
// GetEnvRefNames gets the ref values from the envFroms of the deployment belonging to the given data
func (oc OcRunner) GetEnvRefNames(componentName, appName, projectName string) []string {
return GetEnvRefNames(oc.path, componentName, appName, projectName)
}
// WaitForDCRollout wait for DeploymentConfig to finish active rollout
// timeout is a maximum wait time in seconds
func (oc OcRunner) WaitForDCRollout(dcName string, project string, timeout time.Duration) {
@@ -758,3 +763,8 @@ func (oc OcRunner) WaitForRunnerCmdOut(args []string, timeout int, errOnFail boo
func (oc OcRunner) CreateSecret(secretName, secretPass, project string) {
Cmd(oc.path, "create", "secret", "generic", secretName, "--from-literal=password="+secretPass, "-n", project).ShouldPass()
}
// GetSecrets gets all the secrets belonging to the project
func (oc OcRunner) GetSecrets(project string) string {
return GetSecrets(oc.path, project)
}

View File

@@ -90,3 +90,25 @@ func GetAnnotationsDeployment(path, componentName, appName, projectName string)
}
return mapOutput
}
// GetSecrets gets all the secrets belonging to the project
func GetSecrets(path, project string) string {
session := CmdRunner(path, "get", "secrets", "--namespace", project)
Eventually(session).Should(gexec.Exit(0))
output := string(session.Wait().Out.Contents())
return output
}
// GetEnvRefNames gets the ref values from the envFroms of the deployment belonging to the given data
func GetEnvRefNames(path, componentName, appName, projectName string) []string {
selector := fmt.Sprintf("--selector=%s=%s,%s=%s", labels.ComponentLabel, componentName, applabels.ApplicationLabel, appName)
output := Cmd(path, "get", "deployment", selector, "--namespace", projectName,
"-o", "jsonpath='{range .items[0].spec.template.spec.containers[0].envFrom[*]}{.secretRef.name}{\"\\n\"}{end}'").ShouldPass().Out()
var result []string
for _, line := range strings.Split(output, "\n") {
line = strings.TrimPrefix(line, "'")
result = append(result, strings.TrimSpace(line))
}
return result
}

View File

@@ -2,6 +2,7 @@ package integration
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
@@ -17,7 +18,6 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
BeforeEach(func() {
commonVar = helper.CommonBeforeEach()
helper.Chdir(commonVar.Context)
})
AfterEach(func() {
@@ -32,7 +32,7 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
BeforeEach(func() {
// wait till odo can see that all operators installed by setup script in the namespace
odoArgs := []string{"catalog", "list", "services"}
operators := []string{"redis-operator", "service-binding-operator"}
operators := []string{"redis-operator"}
for _, operator := range operators {
helper.WaitForCmdOut("odo", odoArgs, 5, true, func(output string) bool {
return strings.Contains(output, operator)
@@ -53,51 +53,51 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
componentName = "cmp-" + helper.RandString(6)
helper.Cmd("odo", "create", "nodejs", componentName).ShouldPass()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f").ShouldPass()
helper.Cmd("odo", "create", "nodejs", componentName, "--context", commonVar.Context, "--project", commonVar.Project).ShouldPass()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f", "--context", commonVar.Context).ShouldPass()
serviceName := "service-" + helper.RandString(6)
svcFullName = strings.Join([]string{"Redis", serviceName}, "/")
helper.Cmd("odo", "service", "create", redisCluster, serviceName, "--project", commonVar.Project).ShouldPass()
helper.Cmd("odo", "service", "create", redisCluster, serviceName, "--context", commonVar.Context).ShouldPass()
helper.Cmd("odo", "push").ShouldPass()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass()
name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project)
Expect(name).To(Not(BeEmpty()))
})
It("should find files in component container", func() {
helper.Cmd("odo", "exec", "--", "ls", "/project/server.js").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/project/server.js").ShouldPass()
})
When("a link between the component and the service is created", func() {
BeforeEach(func() {
helper.Cmd("odo", "link", svcFullName).ShouldPass()
helper.Cmd("odo", "link", svcFullName, "--context", commonVar.Context).ShouldPass()
})
It("should find the link in odo describe", func() {
stdOut := helper.Cmd("odo", "describe").ShouldPass().Out()
stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out()
Expect(stdOut).To(ContainSubstring(svcFullName))
})
When("odo push is executed", func() {
BeforeEach(func() {
helper.Cmd("odo", "push").ShouldPass()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass()
name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project)
Expect(name).To(Not(BeEmpty()))
})
It("should find files in component container", func() {
helper.Cmd("odo", "exec", "--", "ls", "/project/server.js").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/project/server.js").ShouldPass()
})
It("should find the link environment variable", func() {
stdOut := helper.Cmd("odo", "exec", "--", "sh", "-c", "echo $REDIS_CLUSTERIP").ShouldPass().Out()
stdOut := helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "sh", "-c", "echo $REDIS_CLUSTERIP").ShouldPass().Out()
Expect(stdOut).To(Not(BeEmpty()))
})
It("should find the link in odo describe", func() {
stdOut := helper.Cmd("odo", "describe").ShouldPass().Out()
stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out()
Expect(stdOut).To(ContainSubstring(svcFullName))
Expect(stdOut).To(ContainSubstring("Environment Variables"))
Expect(stdOut).To(ContainSubstring("REDIS_CLUSTERIP"))
@@ -110,31 +110,31 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
var bindingName string
BeforeEach(func() {
bindingName = "sbr-" + helper.RandString(6)
helper.Cmd("odo", "link", svcFullName, "--bind-as-files", "--name", bindingName).ShouldPass()
helper.Cmd("odo", "link", svcFullName, "--bind-as-files", "--name", bindingName, "--context", commonVar.Context).ShouldPass()
})
It("should dislay the link in odo describe", func() {
stdOut := helper.Cmd("odo", "describe").ShouldPass().Out()
stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out()
Expect(stdOut).To(ContainSubstring(svcFullName))
})
When("odo push is executed", func() {
BeforeEach(func() {
helper.Cmd("odo", "push").ShouldPass()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass()
name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project)
Expect(name).To(Not(BeEmpty()))
})
It("should find files in component container", func() {
helper.Cmd("odo", "exec", "--", "ls", "/project/server.js").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/project/server.js").ShouldPass()
})
It("should find bindings for service", func() {
helper.Cmd("odo", "exec", "--", "ls", "/bindings/"+bindingName+"/clusterIP").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/bindings/"+bindingName+"/clusterIP").ShouldPass()
})
It("should display the link in odo describe", func() {
stdOut := helper.Cmd("odo", "describe").ShouldPass().Out()
stdOut := helper.Cmd("odo", "describe", "--context", commonVar.Context).ShouldPass().Out()
Expect(stdOut).To(ContainSubstring(svcFullName))
Expect(stdOut).To(ContainSubstring("Files"))
Expect(stdOut).To(ContainSubstring("/bindings/" + bindingName + "/clusterIP"))
@@ -149,22 +149,25 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
componentName := "api" // this is the name of the component in the devfile
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-link.yaml"), filepath.Join(commonVar.Context, "devfile.yaml"))
helper.Cmd("odo", "create", componentName).ShouldPass()
helper.Cmd("odo", "create", componentName, "--project", commonVar.Project, "--context", commonVar.Context).ShouldPass()
helper.Cmd("odo", "push").ShouldPass()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass()
name := commonVar.CliRunner.GetRunningPodNameByComponent(componentName, commonVar.Project)
Expect(name).To(Not(BeEmpty()))
})
It("should find files in component container", func() {
helper.Cmd("odo", "exec", "--", "ls", "/project/server.js").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/project/server.js").ShouldPass()
})
It("should find bindings for service", func() {
helper.Cmd("odo", "exec", "--", "ls", "/bindings/redis-link/clusterIP").ShouldPass()
helper.Cmd("odo", "exec", "--context", commonVar.Context, "--", "ls", "/bindings/redis-link/clusterIP").ShouldPass()
})
It("should find owner references on link and service", func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
args := []string{"get", "servicebinding", "redis-link", "-o", "jsonpath='{.metadata.ownerReferences.*.name}'", "-n", commonVar.Project}
commonVar.CliRunner.WaitForRunnerCmdOut(args, 1, true, func(output string) bool {
return strings.Contains(output, "api-app")
@@ -177,4 +180,121 @@ var _ = Describe("odo link command tests for OperatorHub", func() {
})
})
})
When("one component is deployed", func() {
var context0 string
var cmp0 string
BeforeEach(func() {
context0 = helper.CreateNewContext()
cmp0 = helper.RandString(5)
helper.Cmd("odo", "create", "nodejs", cmp0, "--context", context0).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context0)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context0, "devfile.yaml"))
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "delete", "-f", "--context", context0).ShouldPass()
helper.DeleteDir(context0)
})
It("should fail when linking to itself", func() {
stdOut := helper.Cmd("odo", "link", cmp0, "--context", context0).ShouldFail().Err()
helper.MatchAllInOutput(stdOut, []string{cmp0, "cannot be linked with itself"})
})
It("should fail if the component doesn't exist and the service name doesn't adhere to the <service-type>/<service-name> format", func() {
helper.Cmd("odo", "link", "Redis").ShouldFail()
helper.Cmd("odo", "link", "Redis/").ShouldFail()
helper.Cmd("odo", "link", "/redis-standalone").ShouldFail()
})
When("another component is deployed", func() {
var context1 string
var cmp1 string
BeforeEach(func() {
context1 = helper.CreateNewContext()
cmp1 = helper.RandString(5)
helper.Cmd("odo", "create", "nodejs", cmp1, "--context", context1).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context1)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileNestedCompCommands.yaml"), filepath.Join(context1, "devfile.yaml"))
helper.Cmd("odo", "push", "--context", context1).ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "delete", "-f", "--context", context1).ShouldPass()
helper.DeleteDir(context1)
})
It("should link the two components successfully with service binding operator", func() {
if os.Getenv("KUBERNETES") == "true" {
// service binding operator is not installed on kubernetes
Skip("This is a OpenShift specific scenario, skipping")
}
helper.Cmd("odo", "link", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
// check the link exists with the specific name
ocArgs := []string{"get", "servicebinding", strings.Join([]string{cmp0, cmp1}, "-"), "-o", "jsonpath='{.status.secret}'", "-n", commonVar.Project}
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
return strings.Contains(output, strings.Join([]string{cmp0, cmp1}, "-"))
})
// delete the link and undeploy it
helper.Cmd("odo", "unlink", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
commonVar.CliRunner.WaitAndCheckForTerminatingState("servicebinding", commonVar.Project, 1)
})
It("should link the two components successfully without service binding operator", func() {
if os.Getenv("KUBERNETES") != "true" {
// service binding operator is not installed on kubernetes
Skip("This is a Kubernetes specific scenario, skipping")
}
helper.Cmd("odo", "link", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
// check the secrets exists with the specific name
secrets := commonVar.CliRunner.GetSecrets(commonVar.Project)
Expect(secrets).To(ContainSubstring(fmt.Sprintf("%v-%v", cmp0, cmp1)))
envFromValues := commonVar.CliRunner.GetEnvRefNames(cmp0, "app", commonVar.Project)
envFound := false
for i := range envFromValues {
if strings.Contains(envFromValues[i], fmt.Sprintf("%v-%v", cmp0, cmp1)) {
envFound = true
}
}
Expect(envFound).To(BeTrue())
// delete the link and undeploy it
helper.Cmd("odo", "unlink", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
// check the secrets exists with the specific name
secrets = commonVar.CliRunner.GetSecrets(commonVar.Project)
Expect(secrets).NotTo(ContainSubstring(fmt.Sprintf("%v-%v", cmp0, cmp1)))
envFromValues = commonVar.CliRunner.GetEnvRefNames(cmp0, "app", commonVar.Project)
envFound = false
for i := range envFromValues {
if strings.Contains(envFromValues[i], fmt.Sprintf("%v-%v", cmp0, cmp1)) {
envFound = true
}
}
Expect(envFound).To(BeFalse())
})
})
})
})

View File

@@ -33,7 +33,10 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
BeforeEach(func() {
// wait till odo can see that all operators installed by setup script in the namespace
odoArgs := []string{"catalog", "list", "services"}
operators := []string{"redis-operator", "service-binding-operator"}
operators := []string{"redis-operator"}
if os.Getenv("KUBERNETES") != "true" {
operators = append(operators, "service-binding-operator")
}
for _, operator := range operators {
helper.WaitForCmdOut("odo", odoArgs, 5, true, func(output string) bool {
return strings.Contains(output, operator)
@@ -69,13 +72,13 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
// change the app name to avoid conflicts
appName := helper.RandString(5)
helper.Cmd("odo", "create", "nodejs", "--app", appName).ShouldPass().Out()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f").ShouldPass()
helper.Cmd("odo", "create", "nodejs", "--app", appName, "--context", commonVar.Context).ShouldPass().Out()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f", "--context", commonVar.Context).ShouldPass()
})
AfterEach(func() {
// we do this because for these specific tests we dont delete the project
helper.Cmd("odo", "delete", "--all", "-f").ShouldPass().Out()
helper.Cmd("odo", "delete", "--all", "-f", "--context", commonVar.Context).ShouldPass().Out()
})
When("creating a postgres operand with params", func() {
@@ -85,18 +88,18 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
operandName = helper.RandString(10)
helper.Cmd("odo", "service", "create", postgresDatabase, operandName, "-p",
"databaseName=odo", "-p", "size=1", "-p", "databaseUser=odo", "-p",
"databaseStorageRequest=1Gi", "-p", "databasePassword=odopasswd").ShouldPass().Out()
"databaseStorageRequest=1Gi", "-p", "databasePassword=odopasswd", "--context", commonVar.Context).ShouldPass().Out()
})
AfterEach(func() {
helper.Cmd("odo", "service", "delete", fmt.Sprintf("Database/%s", operandName), "-f").ShouldPass().Out()
helper.Cmd("odo", "push").ShouldPass().Out()
helper.Cmd("odo", "service", "delete", fmt.Sprintf("Database/%s", operandName), "-f", "--context", commonVar.Context).ShouldPass().Out()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass().Out()
})
When("odo push is executed", func() {
BeforeEach(func() {
helper.Cmd("odo", "push").ShouldPass().Out()
helper.Cmd("odo", "push", "--context", commonVar.Context).ShouldPass().Out()
})
It("should create pods in running state", func() {
@@ -105,7 +108,7 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
It("should list the service", func() {
// now test listing of the service using odo
stdOut := helper.Cmd("odo", "service", "list").ShouldPass().Out()
stdOut := helper.Cmd("odo", "service", "list", "--context", commonVar.Context).ShouldPass().Out()
Expect(stdOut).To(ContainSubstring(fmt.Sprintf("Database/%s", operandName)))
})
})
@@ -168,9 +171,11 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
When("a nodejs component is created", func() {
var cmpName string
BeforeEach(func() {
cmpName = helper.RandString(4)
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.Cmd("odo", "create", "nodejs").ShouldPass()
helper.Cmd("odo", "create", "nodejs", cmpName).ShouldPass()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f").ShouldPass()
})
@@ -190,9 +195,6 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
})
It("should fail if the provided service doesn't exist in the namespace", func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
stdOut := helper.Cmd("odo", "link", "Redis/redis-standalone").ShouldFail().Err()
Expect(stdOut).To(ContainSubstring("couldn't find service named %q", "Redis/redis-standalone"))
})
@@ -294,9 +296,6 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
When("a link is created with the service", func() {
var stdOut string
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
stdOut = helper.Cmd("odo", "link", "Redis/redis").ShouldPass().Out()
})
@@ -311,9 +310,6 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
When("the link is deleted", func() {
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
stdOut = helper.Cmd("odo", "unlink", "Redis/redis").ShouldPass().Out()
})
@@ -464,10 +460,14 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
})
It("should create the link with the specified name", func() {
args := []string{"get", "servicebinding", linkName, "-n", commonVar.Project}
commonVar.CliRunner.WaitForRunnerCmdOut(args, 1, true, func(output string) bool {
return strings.Contains(output, linkName)
})
envFromValues := commonVar.CliRunner.GetEnvRefNames(cmpName, "app", commonVar.Project)
envFound := false
for i := range envFromValues {
if strings.Contains(envFromValues[i], linkName) {
envFound = true
}
}
Expect(envFound).To(BeTrue())
})
})
@@ -487,11 +487,22 @@ var _ = Describe("odo service command tests for OperatorHub", func() {
helper.Cmd("odo", "push").ShouldPass()
})
It("should create a servicebinding resource with bindAsFiles set to true", func() {
args := []string{"get", "servicebinding", linkName, "-o", "jsonpath='{.spec.bindAsFiles}'", "-n", commonVar.Project}
commonVar.CliRunner.WaitForRunnerCmdOut(args, 1, true, func(output string) bool {
return strings.Contains(output, "true")
})
It("should create a link with bindAsFiles set to true", func() {
// check the volume name and mount paths for the container
deploymentName, err := util.NamespaceKubernetesObject(cmpName, "app")
if err != nil {
Expect(err).To(BeNil())
}
volNamesAndPaths := commonVar.CliRunner.GetVolumeMountNamesandPathsFromContainer(deploymentName, "runtime", commonVar.Project)
mountFound := false
volNamesAndPathsArr := strings.Fields(volNamesAndPaths)
for _, volNamesAndPath := range volNamesAndPathsArr {
volNamesAndPathArr := strings.Split(volNamesAndPath, ":")
if strings.Contains(volNamesAndPathArr[0], linkName) {
mountFound = true
}
}
Expect(mountFound).To(BeTrue())
})
})
})
@@ -547,83 +558,5 @@ spec:`
})
})
})
When("one component is deployed", func() {
var context0 string
var cmp0 string
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
context0 = helper.CreateNewContext()
cmp0 = helper.RandString(5)
helper.Cmd("odo", "create", "nodejs", cmp0, "--context", context0).ShouldPass()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f", "--context", context0).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context0)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(context0, "devfile.yaml"))
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "delete", "-f", "--context", context0).ShouldPass()
helper.DeleteDir(context0)
})
It("should fail when linking to itself", func() {
stdOut := helper.Cmd("odo", "link", cmp0, "--context", context0).ShouldFail().Err()
helper.MatchAllInOutput(stdOut, []string{cmp0, "cannot be linked with itself"})
})
It("should fail if the component doesn't exist and the service name doesn't adhere to the <service-type>/<service-name> format", func() {
helper.Cmd("odo", "link", "Redis").ShouldFail()
helper.Cmd("odo", "link", "Redis/").ShouldFail()
helper.Cmd("odo", "link", "/redis-standalone").ShouldFail()
})
When("another component is deployed", func() {
var context1 string
var cmp1 string
BeforeEach(func() {
context1 = helper.CreateNewContext()
cmp1 = helper.RandString(5)
helper.Cmd("odo", "create", "nodejs", cmp1, "--context", context1).ShouldPass()
helper.Cmd("odo", "config", "set", "Memory", "300M", "-f", "--context", context1).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), context1)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfileNestedCompCommands.yaml"), filepath.Join(context1, "devfile.yaml"))
helper.Cmd("odo", "push", "--context", context1).ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "delete", "-f", "--context", context1).ShouldPass()
helper.DeleteDir(context1)
})
It("should link the two components successfully", func() {
helper.Cmd("odo", "link", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
// check the link exists with the specific name
ocArgs := []string{"get", "servicebinding", strings.Join([]string{cmp0, cmp1}, "-"), "-o", "jsonpath='{.status.secret}'", "-n", commonVar.Project}
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
return strings.Contains(output, strings.Join([]string{cmp0, cmp1}, "-"))
})
// delete the link and undeploy it
helper.Cmd("odo", "unlink", cmp1, "--context", context0).ShouldPass()
helper.Cmd("odo", "push", "--context", context0).ShouldPass()
commonVar.CliRunner.WaitAndCheckForTerminatingState("servicebinding", commonVar.Project, 1)
})
})
})
})
})

View File

@@ -18,40 +18,10 @@ package v1alpha1
import (
"errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
// BindingReady indicates that the overall sbr succeeded
BindingReady string = "Ready"
// CollectionReady indicates readiness for collection and persistance of intermediate manifests
CollectionReady string = "CollectionReady"
// InjectionReady indicates readiness to change application manifests to use those intermediate manifests
// If status is true, it indicates that the binding succeeded
InjectionReady string = "InjectionReady"
// EmptyServiceSelectorsReason is used when the ServiceBinding has empty
// services.
EmptyServiceSelectorsReason = "EmptyServiceSelectors"
// EmptyApplicationReason is used when the ServiceBinding has empty
// application.
EmptyApplicationReason = "EmptyApplication"
// ApplicationNotFoundReason is used when the application is not found.
ApplicationNotFoundReason = "ApplicationNotFound"
// ServiceNotFoundReason is used when the service is not found.
ServiceNotFoundReason = "ServiceNotFound"
BindingInjectedReason = "BindingInjected"
DataCollectedReason = "DataCollected"
// NamingStrategyError is used when naming strategy/template used is incorrect
NamingStrategyError = "NamingStrategyError"
finalizerName = "finalizer.servicebinding.openshift.io"
)
var templates = map[string]string{
"none": "{{ .name }}",
"uppercase": "{{ .service.kind | upper }}_{{ .name | upper }}",
@@ -85,8 +55,7 @@ type ServiceBindingSpec struct {
// Application is used to identify the application connecting to the
// backing service operator.
// +optional
Application *Application `json:"application,omitempty"`
Application Application `json:"application"`
// DetectBindingResources is flag used to bind all non-bindable variables from
// different subresources owned by backing operator CR.
@@ -250,6 +219,10 @@ func (spec *ServiceBindingSpec) NamingTemplate() string {
}
}
func (sb *ServiceBinding) HasDeletionTimestamp() bool {
return !sb.DeletionTimestamp.IsZero()
}
// Returns GVR of reference if available, otherwise error
func (ref *Ref) GroupVersionResource() (*schema.GroupVersionResource, error) {
if ref.Resource == "" {
@@ -274,24 +247,6 @@ func (ref *Ref) GroupVersionKind() (*schema.GroupVersionKind, error) {
}, nil
}
func (sb *ServiceBinding) MaybeAddFinalizer() bool {
finalizers := sb.GetFinalizers()
for _, f := range finalizers {
if f == finalizerName {
return false
}
}
sb.SetFinalizers(append(finalizers, finalizerName))
return true
}
func (sb *ServiceBinding) MaybeRemoveFinalizer() bool {
finalizers := sb.GetFinalizers()
for i, f := range finalizers {
if f == finalizerName {
sb.SetFinalizers(append(finalizers[:i], finalizers[i+1:]...))
return true
}
}
return false
func (r *ServiceBinding) StatusConditions() []metav1.Condition {
return r.Status.Conditions
}

View File

@@ -0,0 +1,57 @@
/*
Copyright 2021.
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 (
"github.com/redhat-developer/service-binding-operator/apis"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
// log is for logging in this package.
var log = logf.Log.WithName("WebHook ServiceBinding")
func (r *ServiceBinding) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:path=/validate-binding-operators-coreos-com-v1alpha1-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=binding.operators.coreos.com,resources=servicebindings,verbs=update,versions=v1alpha1,name=vservicebinding.kb.io,admissionReviewVersions={v1beta1}
var _ webhook.Validator = &ServiceBinding{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateCreate() error {
return nil
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateUpdate(old runtime.Object) error {
err := apis.CanUpdateBinding(r)
if err != nil {
log.Error(err, "Update failed")
}
return err
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateDelete() error {
return nil
}

View File

@@ -22,7 +22,7 @@ package v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -212,11 +212,7 @@ func (in *ServiceBindingSpec) DeepCopyInto(out *ServiceBindingSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Application != nil {
in, out := &in.Application, &out.Application
*out = new(Application)
(*in).DeepCopyInto(*out)
}
in.Application.DeepCopyInto(&out.Application)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingSpec.

View File

@@ -0,0 +1,51 @@
package apis
import (
"errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
const finalizerName = "finalizer.servicebinding.openshift.io"
func MaybeAddFinalizer(obj Object) bool {
finalizers := obj.GetFinalizers()
for _, f := range finalizers {
if f == finalizerName {
return false
}
}
obj.SetFinalizers(append(finalizers, finalizerName))
return true
}
func MaybeRemoveFinalizer(obj Object) bool {
finalizers := obj.GetFinalizers()
for i, f := range finalizers {
if f == finalizerName {
obj.SetFinalizers(append(finalizers[:i], finalizers[i+1:]...))
return true
}
}
return false
}
type Object interface {
runtime.Object
GetFinalizers() []string
SetFinalizers([]string)
HasDeletionTimestamp() bool
StatusConditions() []metav1.Condition
}
func CanUpdateBinding(obj Object) error {
if obj.HasDeletionTimestamp() {
return nil
}
if meta.IsStatusConditionTrue(obj.StatusConditions(), BindingReady) {
return errors.New("cannot update Service Binding if 'Ready' condition is True. If you want to rebind to another service/application, remove this binding and create a new one.")
}
return nil
}

View File

@@ -1,6 +1,40 @@
package v1alpha1
package apis
import v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// BindingReady indicates that the overall sbr succeeded
BindingReady string = "Ready"
// CollectionReady indicates readiness for collection and persistance of intermediate manifests
CollectionReady string = "CollectionReady"
// InjectionReady indicates readiness to change application manifests to use those intermediate manifests
// If status is true, it indicates that the binding succeeded
InjectionReady string = "InjectionReady"
// EmptyServiceSelectorsReason is used when the ServiceBinding has empty
// services.
EmptyApplicationReason = "EmptyApplication"
// EmptyApplicationReason is used when the ServiceBinding has empty
// application.
ApplicationNotFoundReason = "ApplicationNotFound"
// ApplicationNotFoundReason is used when the application is not found.
ServiceNotFoundReason = "ServiceNotFound"
// ServiceNotFoundReason is used when the service is not found.
BindingInjectedReason = "BindingInjected"
// DataCollectedReason indicates that bindings are collected successfully
DataCollectedReason = "DataCollected"
// RequiredBindingNotFound when some mandatory bindings are missing
RequiredBindingNotFound = "RequiredBindingNotFound"
)
type conditionsBuilder struct {
cndType string

View File

@@ -0,0 +1,40 @@
/*
Copyright 2021.
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 v1alpha2 contains API Schema definitions for the spec v1alpha2 API group
// +kubebuilder:object:generate=true
// +groupName=service.binding
package v1alpha2
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "service.binding", Version: "v1alpha2"}
GroupVersionResource = GroupVersion.WithResource("servicebindings")
GroupVersionKind = GroupVersion.WithKind("ServiceBinding")
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@@ -0,0 +1,166 @@
/*
Copyright 2021.
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 v1alpha2
import (
"errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ServiceBindingApplicationReference defines a subset of corev1.ObjectReference with extensions
type ServiceBindingApplicationReference struct {
// API version of the referent.
APIVersion string `json:"apiVersion"`
// Kind of the referent.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
Kind string `json:"kind"`
// Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name,omitempty"`
// Selector is a query that selects the application or applications to bind the service to
Selector metav1.LabelSelector `json:"selector,omitempty"`
// Containers describes which containers in a Pod should be bound to
Containers []string `json:"containers,omitempty"`
}
// ServiceBindingServiceReference defines a subset of corev1.ObjectReference
type ServiceBindingServiceReference struct {
// API version of the referent.
APIVersion string `json:"apiVersion"`
// Kind of the referent.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
Kind string `json:"kind"`
// Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
}
// ServiceBindingSecretReference defines a mirror of corev1.LocalObjectReference
type ServiceBindingSecretReference struct {
// Name of the referent secret.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
}
// EnvMapping defines a mapping from the value of a Secret entry to an environment variable
type EnvMapping struct {
// Name is the name of the environment variable
Name string `json:"name"`
// Key is the key in the Secret that will be exposed
Key string `json:"key"`
}
// ServiceBindingSpec defines the desired state of ServiceBinding
type ServiceBindingSpec struct {
// Name is the name of the service as projected into the application container. Defaults to .metadata.name.
// +kubebuilder:validation:Pattern=`^[a-z0-9\-\.]*$`
// +kubebuilder:validation:MaxLength=253
Name string `json:"name,omitempty"`
// Type is the type of the service as projected into the application container
Type string `json:"type,omitempty"`
// Provider is the provider of the service as projected into the application container
Provider string `json:"provider,omitempty"`
// Application is a reference to an object
Application ServiceBindingApplicationReference `json:"application"`
// Service is a reference to an object that fulfills the ProvisionedService duck type
Service ServiceBindingServiceReference `json:"service"`
// Env is the collection of mappings from Secret entries to environment variables
Env []EnvMapping `json:"env,omitempty"`
}
// ServiceBindingStatus defines the observed state of ServiceBinding
type ServiceBindingStatus struct {
// ObservedGeneration is the 'Generation' of the ServiceBinding that
// was last processed by the controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions are the conditions of this ServiceBinding
Conditions []metav1.Condition `json:"conditions,omitempty"`
// Binding exposes the projected secret for this ServiceBinding
Binding *ServiceBindingSecretReference `json:"binding,omitempty"`
}
// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Service Binding (spec API)"
// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// ServiceBinding is the Schema for the servicebindings API
type ServiceBinding struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ServiceBindingSpec `json:"spec,omitempty"`
Status ServiceBindingStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ServiceBindingList contains a list of ServiceBinding
type ServiceBindingList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ServiceBinding `json:"items"`
}
func init() {
SchemeBuilder.Register(&ServiceBinding{}, &ServiceBindingList{})
}
func (ref *ServiceBindingServiceReference) GroupVersionResource() (*schema.GroupVersionResource, error) {
return nil, errors.New("Resource undefined")
}
func (ref *ServiceBindingServiceReference) GroupVersionKind() (*schema.GroupVersionKind, error) {
typeMeta := &metav1.TypeMeta{Kind: ref.Kind, APIVersion: ref.APIVersion}
gvk := typeMeta.GroupVersionKind()
return &gvk, nil
}
func (ref *ServiceBindingApplicationReference) GroupVersionResource() (*schema.GroupVersionResource, error) {
return nil, errors.New("Resource undefined")
}
func (ref *ServiceBindingApplicationReference) GroupVersionKind() (*schema.GroupVersionKind, error) {
typeMeta := &metav1.TypeMeta{Kind: ref.Kind, APIVersion: ref.APIVersion}
gvk := typeMeta.GroupVersionKind()
return &gvk, nil
}
func (sb *ServiceBinding) AsOwnerReference() metav1.OwnerReference {
var ownerRefController bool = true
return metav1.OwnerReference{
Name: sb.Name,
UID: sb.UID,
Kind: sb.Kind,
APIVersion: sb.APIVersion,
Controller: &ownerRefController,
}
}
func (sb *ServiceBinding) HasDeletionTimestamp() bool {
return !sb.DeletionTimestamp.IsZero()
}
func (r *ServiceBinding) StatusConditions() []metav1.Condition {
return r.Status.Conditions
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2021.
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 expr@wip
ess or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha2
import (
"github.com/redhat-developer/service-binding-operator/apis"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
// log is for logging in this package.
var log = logf.Log.WithName("WebHook Spec ServiceBinding")
func (r *ServiceBinding) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:path=/validate-service-binding-v1alpha2-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=service.binding,resources=servicebindings,verbs=update,versions=v1alpha2,name=vspecservicebinding.kb.io,admissionReviewVersions={v1beta1}
var _ webhook.Validator = &ServiceBinding{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateCreate() error {
return nil
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateUpdate(old runtime.Object) error {
err := apis.CanUpdateBinding(r)
if err != nil {
log.Error(err, "Update failed")
}
return err
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceBinding) ValidateDelete() error {
return nil
}

View File

@@ -0,0 +1,200 @@
// +build !ignore_autogenerated
/*
Copyright 2021.
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha2
import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EnvMapping) DeepCopyInto(out *EnvMapping) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvMapping.
func (in *EnvMapping) DeepCopy() *EnvMapping {
if in == nil {
return nil
}
out := new(EnvMapping)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBinding) DeepCopyInto(out *ServiceBinding) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBinding.
func (in *ServiceBinding) DeepCopy() *ServiceBinding {
if in == nil {
return nil
}
out := new(ServiceBinding)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceBinding) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingApplicationReference) DeepCopyInto(out *ServiceBindingApplicationReference) {
*out = *in
in.Selector.DeepCopyInto(&out.Selector)
if in.Containers != nil {
in, out := &in.Containers, &out.Containers
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingApplicationReference.
func (in *ServiceBindingApplicationReference) DeepCopy() *ServiceBindingApplicationReference {
if in == nil {
return nil
}
out := new(ServiceBindingApplicationReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingList) DeepCopyInto(out *ServiceBindingList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ServiceBinding, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingList.
func (in *ServiceBindingList) DeepCopy() *ServiceBindingList {
if in == nil {
return nil
}
out := new(ServiceBindingList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceBindingList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingSecretReference) DeepCopyInto(out *ServiceBindingSecretReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingSecretReference.
func (in *ServiceBindingSecretReference) DeepCopy() *ServiceBindingSecretReference {
if in == nil {
return nil
}
out := new(ServiceBindingSecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingServiceReference) DeepCopyInto(out *ServiceBindingServiceReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingServiceReference.
func (in *ServiceBindingServiceReference) DeepCopy() *ServiceBindingServiceReference {
if in == nil {
return nil
}
out := new(ServiceBindingServiceReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingSpec) DeepCopyInto(out *ServiceBindingSpec) {
*out = *in
in.Application.DeepCopyInto(&out.Application)
out.Service = in.Service
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]EnvMapping, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingSpec.
func (in *ServiceBindingSpec) DeepCopy() *ServiceBindingSpec {
if in == nil {
return nil
}
out := new(ServiceBindingSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceBindingStatus) DeepCopyInto(out *ServiceBindingStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Binding != nil {
in, out := &in.Binding, &out.Binding
*out = new(ServiceBindingSecretReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceBindingStatus.
func (in *ServiceBindingStatus) DeepCopy() *ServiceBindingStatus {
if in == nil {
return nil
}
out := new(ServiceBindingStatus)
in.DeepCopyInto(out)
return out
}

View File

@@ -0,0 +1,133 @@
package binding
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"strings"
"github.com/pkg/errors"
)
type UnstructuredResourceReader func(namespace string, name string) (*unstructured.Unstructured, error)
type secretConfigMapReader struct {
configMapReader UnstructuredResourceReader
secretReader UnstructuredResourceReader
}
type annotationBackedDefinitionBuilder struct {
*secretConfigMapReader
name string
value string
}
var _ DefinitionBuilder = (*annotationBackedDefinitionBuilder)(nil)
type modelKey string
const (
pathModelKey modelKey = "path"
objectTypeModelKey modelKey = "objectType"
sourceKeyModelKey modelKey = "sourceKey"
sourceValueModelKey modelKey = "sourceValue"
elementTypeModelKey modelKey = "elementType"
AnnotationPrefix = "service.binding"
)
func NewDefinitionBuilder(annotationName string, annotationValue string, configMapReader UnstructuredResourceReader, secretReader UnstructuredResourceReader) *annotationBackedDefinitionBuilder {
return &annotationBackedDefinitionBuilder{
name: annotationName,
value: annotationValue,
secretConfigMapReader: &secretConfigMapReader{
configMapReader: configMapReader,
secretReader: secretReader,
},
}
}
func (m *annotationBackedDefinitionBuilder) outputName() (string, error) {
// bail out in the case the annotation name doesn't start with "service.binding"
if m.name != AnnotationPrefix && !strings.HasPrefix(m.name, AnnotationPrefix+"/") {
return "", fmt.Errorf("can't process annotation with name %q", m.name)
}
if p := strings.SplitN(m.name, "/", 2); len(p) > 1 && len(p[1]) > 0 {
return p[1], nil
}
return "", nil
}
func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
outputName, err := m.outputName()
if err != nil {
return nil, err
}
mod, err := newModel(m.value)
if err != nil {
return nil, errors.Wrapf(err, "could not create binding model for annotation key %s and value %s", m.name, m.value)
}
switch {
case mod.isStringElementType() && mod.isStringObjectType():
return &stringDefinition{
outputName: outputName,
definition: definition{
path: mod.path,
},
}, nil
case mod.isStringElementType() && mod.hasDataField():
return &stringFromDataFieldDefinition{
secretConfigMapReader: m.secretConfigMapReader,
objectType: mod.objectType,
outputName: outputName,
definition: definition{
path: mod.path,
},
sourceKey: mod.sourceKey,
}, nil
case mod.isMapElementType() && mod.hasDataField():
return &mapFromDataFieldDefinition{
secretConfigMapReader: m.secretConfigMapReader,
objectType: mod.objectType,
outputName: outputName,
definition: definition{
path: mod.path,
},
sourceValue: mod.sourceValue,
}, nil
case mod.isMapElementType() && mod.isStringObjectType():
return &stringOfMapDefinition{
outputName: outputName,
definition: definition{
path: mod.path,
},
}, nil
case mod.isSliceOfMapsElementType():
return &sliceOfMapsFromPathDefinition{
outputName: outputName,
definition: definition{
path: mod.path,
},
sourceKey: mod.sourceKey,
sourceValue: mod.sourceValue,
}, nil
case mod.isSliceOfStringsElementType():
return &sliceOfStringsFromPathDefinition{
outputName: outputName,
definition: definition{
path: mod.path,
},
sourceValue: mod.sourceValue,
}, nil
}
panic(fmt.Sprintf("Annotation %s=%s not implemented!", m.name, m.value))
}

View File

@@ -0,0 +1,30 @@
package binding
import (
"errors"
"fmt"
)
type ErrInvalidAnnotationPrefix string
func (e ErrInvalidAnnotationPrefix) Error() string {
return fmt.Sprintf("invalid annotation prefix: %s", string(e))
}
func IsErrInvalidAnnotationPrefix(err error) bool {
_, ok := err.(ErrInvalidAnnotationPrefix)
return ok
}
var ErrInvalidAnnotationName = errors.New("invalid annotation name")
type ErrEmptyAnnotationName string
func (e ErrEmptyAnnotationName) Error() string {
return fmt.Sprintf("empty annotation name: %s", string(e))
}
func IsErrEmptyAnnotationName(err error) bool {
_, ok := err.(ErrEmptyAnnotationName)
return ok
}

View File

@@ -0,0 +1,322 @@
package binding
import (
"encoding/base64"
"errors"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type objectType string
type elementType string
const (
// configMapObjectType indicates the path contains a name for a ConfigMap containing the binding
// data.
configMapObjectType objectType = "ConfigMap"
// secretObjectType indicates the path contains a name for a Secret containing the binding data.
secretObjectType objectType = "Secret"
// stringObjectType indicates the path contains a value string.
stringObjectType objectType = "string"
// emptyObjectType is used as default value when the objectType key is present in the string
// provided by the user but no value has been provided; can be used by the user to force the
// system to use the default objectType.
emptyObjectType objectType = ""
// mapElementType indicates the value found at path is a map[string]interface{}.
mapElementType elementType = "map"
// sliceOfMapsElementType indicates the value found at path is a slice of maps.
sliceOfMapsElementType elementType = "sliceOfMaps"
// sliceOfStringsElementType indicates the value found at path is a slice of strings.
sliceOfStringsElementType elementType = "sliceOfStrings"
// stringElementType indicates the value found at path is a string.
stringElementType elementType = "string"
)
//go:generate mockgen -destination=mocks/mocks.go -package=mocks . Definition,Value
type Definition interface {
Apply(u *unstructured.Unstructured) (Value, error)
GetPath() string
}
type DefinitionBuilder interface {
Build() (Definition, error)
}
type definition struct {
path string
}
func (d *definition) GetPath() string {
return d.path
}
type stringDefinition struct {
outputName string
definition
}
var _ Definition = (*stringDefinition)(nil)
func (d *stringDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
if d.outputName == "" {
return nil, fmt.Errorf("cannot use generic service.binding annotation for string elements, need to specify binding key like service.binding/foo")
}
val, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
if len(val) != 1 {
return nil, fmt.Errorf("only one value should be returned for %v but we got %v", d.path, val)
}
m := map[string]interface{}{
d.outputName: val[0].Interface(),
}
return &value{v: m}, nil
}
type stringFromDataFieldDefinition struct {
secretConfigMapReader *secretConfigMapReader
objectType objectType
outputName string
definition
sourceKey string
}
var _ Definition = (*stringFromDataFieldDefinition)(nil)
func (d *stringFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
if d.secretConfigMapReader == nil {
return nil, errors.New("kubeClient required for this functionality")
}
res, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
if len(res) != 1 {
return nil, fmt.Errorf("only one value should be returned for %v but we got %v", d.path, res)
}
resourceName := res[0].String()
var otherObj *unstructured.Unstructured
if d.objectType == secretObjectType {
otherObj, err = d.secretConfigMapReader.secretReader(u.GetNamespace(), resourceName)
} else if d.objectType == configMapObjectType {
otherObj, err = d.secretConfigMapReader.configMapReader(u.GetNamespace(), resourceName)
}
if err != nil {
return nil, err
}
val, ok, err := unstructured.NestedString(otherObj.Object, "data", d.sourceKey)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("not found")
}
if d.objectType == secretObjectType {
n, err := base64.StdEncoding.DecodeString(val)
if err != nil {
return nil, err
}
val = string(n)
}
v := map[string]interface{}{
"": val,
}
return &value{v: v}, nil
}
type mapFromDataFieldDefinition struct {
secretConfigMapReader *secretConfigMapReader
objectType objectType
outputName string
sourceValue string
definition
}
var _ Definition = (*mapFromDataFieldDefinition)(nil)
func (d *mapFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
if d.secretConfigMapReader == nil {
return nil, errors.New("kubeClient required for this functionality")
}
res, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
if len(res) != 1 {
return nil, fmt.Errorf("only one value should be returned for %v but we got %v", d.path, res)
}
resourceName := res[0].Elem().String()
var otherObj *unstructured.Unstructured
if d.objectType == secretObjectType {
otherObj, err = d.secretConfigMapReader.secretReader(u.GetNamespace(), resourceName)
} else if d.objectType == configMapObjectType {
otherObj, err = d.secretConfigMapReader.configMapReader(u.GetNamespace(), resourceName)
}
if err != nil {
return nil, err
}
val, ok, err := unstructured.NestedStringMap(otherObj.Object, "data")
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("not found")
}
outputVal := make(map[string]string)
for k, v := range val {
if len(d.sourceValue) > 0 && k != d.sourceValue {
continue
}
var n string
if d.objectType == secretObjectType {
b, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
n = string(b)
} else {
n = v
}
if len(d.sourceValue) > 0 && len(d.outputName) > 0 {
outputVal[d.outputName] = string(n)
} else {
outputVal[k] = string(n)
}
}
return &value{v: outputVal}, nil
}
type stringOfMapDefinition struct {
outputName string
definition
}
var _ Definition = (*stringOfMapDefinition)(nil)
func (d *stringOfMapDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
val, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
if len(val) != 1 {
return nil, fmt.Errorf("only one value should be returned for %v but we got %v", d.path, val)
}
valMap, ok := val[0].Interface().(map[string]interface{})
if !ok {
return nil, fmt.Errorf("returned value for %v should be map, but we got %v", d.path, val[0].Interface())
}
outputName := d.outputName
if outputName != "" {
return &value{v: map[string]interface{}{
outputName: valMap,
}}, nil
}
return &value{v: valMap}, nil
}
type sliceOfMapsFromPathDefinition struct {
outputName string
definition
sourceKey string
sourceValue string
}
var _ Definition = (*sliceOfMapsFromPathDefinition)(nil)
func (d *sliceOfMapsFromPathDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
val, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
res := make(map[string]interface{})
for _, vv := range val {
for k, v := range collectSourceValuesWithKey(vv.Interface(), d.sourceValue, d.sourceKey) {
res[k] = v
}
}
if d.outputName == "" {
return &value{v: res}, nil
}
return &value{v: map[string]interface{}{d.outputName: res}}, nil
}
func collectSourceValuesWithKey(i interface{}, sourceValue string, sourceKey string) map[string]interface{} {
res := make(map[string]interface{})
switch v := i.(type) {
case map[string]interface{}:
key := v[sourceKey]
res[fmt.Sprintf("%v", key)] = v[sourceValue]
case []interface{}:
for _, item := range v {
for k, v := range collectSourceValuesWithKey(item, sourceValue, sourceKey) {
res[k] = v
}
}
}
return res
}
type sliceOfStringsFromPathDefinition struct {
outputName string
definition
sourceValue string
}
var _ Definition = (*sliceOfStringsFromPathDefinition)(nil)
func (d *sliceOfStringsFromPathDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
val, err := getValuesByJSONPath(u.Object, d.path)
if err != nil {
return nil, err
}
var res []interface{}
for _, e := range val {
res = append(res, collectSourceValues(e.Interface(), d.sourceValue)...)
}
return &value{v: map[string]interface{}{d.outputName: res}}, nil
}
func collectSourceValues(i interface{}, sourceValue string) []interface{} {
var res []interface{}
switch v := i.(type) {
case map[string]interface{}:
if sourceValue != "" {
res = append(res, v[sourceValue])
}
case []interface{}:
for _, item := range v {
res = append(res, collectSourceValues(item, sourceValue)...)
}
case string:
if sourceValue == "" {
res = append(res, v)
}
}
return res
}

View File

@@ -0,0 +1,29 @@
package binding
import (
"fmt"
"reflect"
"k8s.io/client-go/util/jsonpath"
)
// getValuesByJSONPath returns values from the given map matching the provided JSONPath
// 'path' argument takes JSONPath expressions enclosed by curly braces {}
// see https://kubernetes.io/docs/reference/kubectl/jsonpath/ for more details
// It returns zero or more filtered values back,
// or error if the jsonpath is invalid or it cannot be applied on the given map
func getValuesByJSONPath(obj map[string]interface{}, path string) ([]reflect.Value, error) {
j := jsonpath.New("")
err := j.Parse(path)
if err != nil {
return nil, err
}
result, err := j.FindResults(obj)
if err != nil {
return nil, err
}
if len(result) > 1 {
return nil, fmt.Errorf("more than one item found in the result: %v", result)
}
return result[0], nil
}

View File

@@ -0,0 +1,125 @@
package binding
import (
"errors"
"fmt"
"strings"
)
type model struct {
path string
elementType elementType
objectType objectType
sourceKey string
sourceValue string
bindAs BindingType
}
func (m *model) isStringElementType() bool {
return m.elementType == stringElementType
}
func (m *model) isStringObjectType() bool {
return m.objectType == stringObjectType
}
func (m *model) isMapElementType() bool {
return m.elementType == mapElementType
}
func (m *model) isSliceOfMapsElementType() bool {
return m.elementType == sliceOfMapsElementType
}
func (m *model) isSliceOfStringsElementType() bool {
return m.elementType == sliceOfStringsElementType
}
func (m *model) hasDataField() bool {
return m.objectType == secretObjectType || m.objectType == configMapObjectType
}
var keys = []modelKey{pathModelKey, objectTypeModelKey, elementTypeModelKey, sourceKeyModelKey, sourceValueModelKey}
func newModel(annotationValue string) (*model, error) {
raw := make(map[modelKey]string)
for _, kv := range strings.Split(annotationValue, ",") {
for i := range keys {
k := keys[i]
prefix := fmt.Sprintf("%v=", k)
if strings.HasPrefix(kv, prefix) {
raw[k] = kv[len(prefix):]
}
}
}
// assert PathModelKey is present
path, found := raw[pathModelKey]
if !found {
return nil, fmt.Errorf("path not found: %q", annotationValue)
}
if !strings.HasPrefix(path, "{") || !strings.HasSuffix(path, "}") {
return nil, fmt.Errorf("path has invalid syntax: %q", path)
}
// ensure ObjectTypeModelKey has a default value
var objType objectType
if rawObjectType, found := raw[objectTypeModelKey]; !found {
objType = stringObjectType
} else {
// in the case the key is present but the value isn't (for example, "objectType=,") the
// default string object type should be set
if objType = objectType(rawObjectType); objType == emptyObjectType {
objType = stringObjectType
}
}
// ensure sourceKey has a default value
sourceKey, found := raw[sourceKeyModelKey]
if !found {
sourceKey = ""
}
sourceValue, found := raw[sourceValueModelKey]
if !found {
sourceValue, found = raw[sourceKeyModelKey]
if !found {
sourceValue = ""
}
}
// hasData indicates the configured or inferred objectType is either a Secret or ConfigMap
hasData := objType == secretObjectType || objType == configMapObjectType
// hasSourceKey indicates a value for sourceKey has been informed
var eltType elementType
if rawEltType, found := raw[elementTypeModelKey]; found {
// the input string contains an elementType configuration, use it
eltType = elementType(rawEltType)
} else if hasData {
// the input doesn't contain an elementType configuration, does contain a sourceKey
// configuration, and is either a Secret or ConfigMap
eltType = mapElementType
} else {
// elementType configuration hasn't been informed and there's no extra hints, assume it is a
// string element
eltType = stringElementType
}
// ensure an error is returned if not all required information is available for sliceOfMaps
// element type
if eltType == sliceOfMapsElementType && (len(sourceValue) == 0 || len(sourceKey) == 0) {
return nil, errors.New("sliceOfMaps elementType requires sourceKey and sourceValue to be present")
}
return &model{
path: path,
elementType: eltType,
objectType: objType,
sourceValue: sourceValue,
sourceKey: sourceKey,
bindAs: TypeEnvVar,
}, nil
}

View File

@@ -0,0 +1,118 @@
package binding
import (
"context"
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)
// bindingType encodes the medium the binding should deliver the configuration value.
type BindingType string
const (
// TypeVolumeMount indicates the binding should happen through a volume mount.
TypeVolumeMount BindingType = "volumemount"
// TypeEnvVar indicates the binding should happen through environment variables.
TypeEnvVar BindingType = "env"
)
// result contains data that has been collected by an annotation handler.
type result struct {
// Data contains the annotation data collected by an annotation handler inside a deep structure
// with its root being the value specified in the Path field.
Data map[string]interface{}
// Type indicates where the Object field should be injected in the application; can be either
// "env" or "volumemount".
Type BindingType
// Path is the nested location the collected data can be found in the Data field.
Path string
// RawData contains the annotation data collected by an annotation handler
// inside a deep structure with its root being composed by the path where
// the external resource name was extracted and the path within the external
// resource.
RawData map[string]interface{}
}
type errHandlerNotFound string
func (e errHandlerNotFound) Error() string {
return fmt.Sprintf("could not find handler for annotation value %q", string(e))
}
func IsErrHandlerNotFound(err error) bool {
_, ok := err.(errHandlerNotFound)
return ok
}
type SpecHandler struct {
kubeClient dynamic.Interface
obj unstructured.Unstructured
annotationKey string
annotationValue string
}
func configMapsReader(client dynamic.Interface) UnstructuredResourceReader {
return func(namespace string, name string) (*unstructured.Unstructured, error) {
return client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}).Namespace(namespace).Get(context.TODO(), name, v1.GetOptions{})
}
}
func secretsReader(client dynamic.Interface) UnstructuredResourceReader {
return func(namespace string, name string) (*unstructured.Unstructured, error) {
return client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}).Namespace(namespace).Get(context.TODO(), name, v1.GetOptions{})
}
}
func (s *SpecHandler) Handle() (result, error) {
builder := NewDefinitionBuilder(s.annotationKey, s.annotationValue, configMapsReader(s.kubeClient), secretsReader(s.kubeClient))
d, err := builder.Build()
if err != nil {
return result{}, err
}
val, err := d.Apply(&s.obj)
if err != nil {
return result{}, err
}
v := val.Get()
out := make(map[string]interface{})
switch t := v.(type) {
case map[string]string:
for k, v := range t {
out[k] = v
}
case map[string]interface{}:
for k, v := range t {
out[k] = v
}
case map[interface{}]interface{}:
for k, v := range t {
out[fmt.Sprintf("%v", k)] = v
}
}
return result{
Data: out,
}, nil
}
func NewSpecHandler(
kubeClient dynamic.Interface,
annotationKey string,
annotationValue string,
obj unstructured.Unstructured,
) (*SpecHandler, error) {
return &SpecHandler{
kubeClient: kubeClient,
obj: obj,
annotationKey: annotationKey,
annotationValue: annotationValue,
}, nil
}

View File

@@ -0,0 +1,15 @@
package binding
type Value interface {
Get() interface{}
}
type value struct {
v interface{}
}
var _ Value = (*value)(nil)
func (v *value) Get() interface{} {
return v.v
}

View File

@@ -0,0 +1,19 @@
package kubernetes
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type ConfigMapReader interface {
ReadConfigMap(namespace string, name string) (*unstructured.Unstructured, error)
}
type SecretReader interface {
ReadSecret(namespace string, name string) (*unstructured.Unstructured, error)
}
type Referable interface {
GroupVersionResource() (*schema.GroupVersionResource, error)
GroupVersionKind() (*schema.GroupVersionKind, error)
}

View File

@@ -0,0 +1,60 @@
package converter
import (
"errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// ToUnstructured converts a runtime object into Unstructured, and can return errors related to it.
func ToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: data}, nil
}
// ToUnstructuredAsGVK converts a runtime object into Unstructured, and set as given GVK. It can
// return errors related to conversion.
func ToUnstructuredAsGVK(
obj interface{},
gvk schema.GroupVersionKind,
) (*unstructured.Unstructured, error) {
u, err := ToUnstructured(obj)
if err != nil {
return nil, err
}
u.SetGroupVersionKind(gvk)
return u, nil
}
// NestedResources returns slice of resources of the type specified by obj arg on the given path inside the given resource represented by a map
// Additionally the function gives an indication if specified resource is found or error if the found slice does not contain resources of the given type
func NestedResources(obj interface{}, resource map[string]interface{}, path ...string) ([]map[string]interface{}, bool, error) {
val, found, err := unstructured.NestedFieldNoCopy(resource, path...)
if err != nil {
return nil, false, err
}
if !found {
return nil, found, nil
}
valSlice, ok := val.([]interface{})
if !ok {
return nil, true, errors.New("not a slice")
}
var containers []map[string]interface{}
for _, item := range valSlice {
u, ok := item.(map[string]interface{})
if !ok {
return nil, true, errors.New("not a map")
}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u, obj)
if err != nil {
return nil, true, err
}
containers = append(containers, u)
}
return containers, true, nil
}

View File

@@ -0,0 +1,52 @@
package naming
import (
"bytes"
"errors"
"html/template"
"strings"
)
var TemplateError = errors.New("please check the namingStrategy template provided")
var templateFunctions = map[string]interface{}{
"upper": strings.ToUpper,
"title": strings.Title,
"lower": strings.ToLower,
}
type namingTemplate struct {
template *template.Template
data map[string]interface{}
namingTemplate string
}
// NewTemplate creates template instance which handles how binding names should be prepared
// templateStr is being used to format binding name.
func NewTemplate(templateStr string, data map[string]interface{}) (*namingTemplate, error) {
t, err := template.New("template").Funcs(templateFunctions).Parse(templateStr)
if err != nil {
return nil, err
}
return &namingTemplate{
template: t,
namingTemplate: templateStr,
data: data,
}, nil
}
// GetBindingName prepares binding name which accepts binding name from the OLM descriptor/annotation
// namingTemplate uses string template provided to build final binding name.
func (n *namingTemplate) GetBindingName(bindingName string) (string, error) {
d := map[string]interface{}{
"service": n.data,
"name": bindingName,
}
var tpl bytes.Buffer
err := n.template.Execute(&tpl, d)
if err != nil {
return "", TemplateError
}
return tpl.String(), nil
}

View File

@@ -0,0 +1,201 @@
package pipeline
import (
"fmt"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/service-binding-operator/pkg/binding"
"github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
//go:generate mockgen -destination=mocks/mocks_pipeline.go -package=mocks . Context,Service,CRD,Application,ContextProvider,Handler
// Reconciliation pipeline
type Pipeline interface {
// Reconcile given service binding
// Returns true if processing should be repeated
// and optional error if occurred
// important: even if error occurred it might not be needed to retry processing
Process(binding interface{}) (bool, error)
}
// A pipeline stage
type Handler interface {
Handle(ctx Context)
}
// Pipeline flow control
type FlowStatus struct {
Retry bool
Stop bool
Err error
}
type HasResource interface {
Resource() *unstructured.Unstructured
}
// Service to be bound
type Service interface {
// Service resource
HasResource
// Return CRD for this service, otherwise nil if not backed by CRD
// Error might be returned if occurred during the operation
CustomResourceDefinition() (CRD, error)
// Resources owned by the service, if any
// Error might be returned if occurred during the operation
OwnedResources() ([]*unstructured.Unstructured, error)
// Attach binding definition to service
AddBindingDef(def binding.Definition)
// All binding definitions attached to the service
BindingDefs() []binding.Definition
// Optional service id
Id() *string
}
// Application to be bound to service(s)
type Application interface {
// Application resource
HasResource
// dot-separated path inside the application resource locating container resources
// the returned value follows foo.bar.bla convention
// it cannot be empty
ContainersPath() string
// optional dot-separated path inside the application resource locating field where intermediate binding secret ref should be injected
// the returns value follows foo.bar.bla convention, but it can be empty
SecretPath() string
BindableContainers() ([]map[string]interface{}, error)
}
// Custom Resource Definition
type CRD interface {
// CRD resource
HasResource
// optional Descriptor attached to ClusterServiceVersion resource
Descriptor() (*olmv1alpha1.CRDDescription, error)
}
// Pipeline context passed to each handler
type Context interface {
BindingName() string
// Services referred by binding
// if reading fails, return error
Services() ([]Service, error)
// Applications referred by binding
// if no application found, return an error
Applications() ([]Application, error)
// Returns true if binding is about to be removed
UnbindRequested() bool
BindingSecretName() string
// Return true if bindings should be projected as files inside application containers
BindAsFiles() bool
// Path where bindings should be mounted inside application containers
MountPath() string
// Template that should be applied on collected binding names, prior projection
NamingTemplate() string
// Additional bindings that will be projected into application containers
// entry key is the future binding name
// entry value contains template that generates binding value
Mappings() map[string]string
// Add binding item to the context
AddBindingItem(item *BindingItem)
// Add bindings to the context
AddBindings(bindings Bindings)
// List binding items that should be projected into application containers
BindingItems() BindingItems
// EnvBindings returns list of (env variable name, binding name) pairs
// describing what binding should be injected as env var as well
EnvBindings() []*EnvBinding
// Indicates that the binding should be retried at some later time
// The current processing stops and context gets closed
RetryProcessing(reason error)
// Indicates that en error has occurred while processing the binding
Error(err error)
// Stops processing
StopProcessing()
// Closes the context, persisting changed resources
// Returns error if occurrs
Close() error
// Sets context condition
SetCondition(condition *metav1.Condition)
kubernetes.ConfigMapReader
kubernetes.SecretReader
FlowStatus() FlowStatus
}
// Provides context for a given service binding
type ContextProvider interface {
Get(binding interface{}) (Context, error)
}
type HandlerFunc func(ctx Context)
func (f HandlerFunc) Handle(ctx Context) {
f(ctx)
}
type BindingItems []*BindingItem
type BindingItem struct {
Name string
Value interface{}
Source Service
}
type EnvBinding struct {
Var string
Name string
}
// a collection of bindings
type Bindings interface {
// available bindgins
Items() (BindingItems, error)
// reference to resource holding the bindings, nil if not persisted in a resource
Source() *v1.ObjectReference
}
// Returns map representation of given list of binding items
func (items *BindingItems) AsMap() map[string]string {
result := make(map[string]string)
for _, i := range *items {
result[i.Name] = fmt.Sprintf("%v", i.Value)
}
return result
}

View File

@@ -0,0 +1,98 @@
package builder
import (
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/collect"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/mapping"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/naming"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/project"
)
var _ pipeline.Pipeline = &impl{}
type impl struct {
ctxProvider pipeline.ContextProvider
handlers []pipeline.Handler
}
func (i *impl) Process(binding interface{}) (bool, error) {
ctx, err := i.ctxProvider.Get(binding)
if err != nil {
return false, err
}
var status pipeline.FlowStatus
for _, h := range i.handlers {
h.Handle(ctx)
status = ctx.FlowStatus()
if status.Stop {
break
}
}
err = ctx.Close()
if err != nil {
return true, err
}
return status.Retry, status.Err
}
type builder struct {
ctxProvider pipeline.ContextProvider
handlers []pipeline.Handler
}
func (b *builder) WithContextProvider(ctxProvider pipeline.ContextProvider) *builder {
b.ctxProvider = ctxProvider
return b
}
func (b *builder) WithHandlers(h ...pipeline.Handler) *builder {
b.handlers = append(b.handlers, h...)
return b
}
func (b *builder) Build() pipeline.Pipeline {
return &impl{
handlers: b.handlers,
ctxProvider: b.ctxProvider,
}
}
func Builder() *builder {
return &builder{}
}
var defaultFlow = []pipeline.Handler{
pipeline.HandlerFunc(project.Unbind),
pipeline.HandlerFunc(collect.PreFlight),
pipeline.HandlerFunc(collect.ProvisionedService),
pipeline.HandlerFunc(collect.DirectSecretReference),
pipeline.HandlerFunc(collect.BindingDefinitions),
pipeline.HandlerFunc(collect.BindingItems),
pipeline.HandlerFunc(collect.OwnedResources),
pipeline.HandlerFunc(mapping.Handle),
pipeline.HandlerFunc(naming.Handle),
pipeline.HandlerFunc(project.PreFlightCheck()),
pipeline.HandlerFunc(project.InjectSecretRef),
pipeline.HandlerFunc(project.BindingsAsEnv),
pipeline.HandlerFunc(project.BindingsAsFiles),
pipeline.HandlerFunc(project.PostFlightCheck),
}
var specFlow = []pipeline.Handler{
pipeline.HandlerFunc(project.Unbind),
pipeline.HandlerFunc(collect.PreFlight),
pipeline.HandlerFunc(collect.ProvisionedService),
pipeline.HandlerFunc(collect.DirectSecretReference),
pipeline.HandlerFunc(collect.BindingDefinitions),
pipeline.HandlerFunc(collect.BindingItems),
pipeline.HandlerFunc(project.PreFlightCheck("type")),
pipeline.HandlerFunc(project.BindingsAsEnv),
pipeline.HandlerFunc(project.BindingsAsFiles),
pipeline.HandlerFunc(project.PostFlightCheck),
}
var (
DefaultBuilder = Builder().WithHandlers(defaultFlow...)
SpecBuilder = Builder().WithHandlers(specFlow...)
)

View File

@@ -0,0 +1,80 @@
package context
import (
"errors"
"fmt"
"github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"github.com/redhat-developer/service-binding-operator/pkg/converter"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"reflect"
"strings"
)
const defaultContainerPath = "spec.template.spec.containers"
var _ pipeline.Application = &application{}
type application struct {
gvr *schema.GroupVersionResource
persistedResource *unstructured.Unstructured
resource *unstructured.Unstructured
bindingPath *v1alpha1.BindingPath
bindableContainerNames sets.String
}
func (a *application) SecretPath() string {
if a.bindingPath != nil {
return a.bindingPath.SecretPath
}
return ""
}
func (a *application) Resource() *unstructured.Unstructured {
if a.resource == nil {
a.resource = a.persistedResource.DeepCopy()
}
return a.resource
}
func (a *application) ContainersPath() string {
if a.bindingPath == nil || a.bindingPath.ContainersPath == "" {
return defaultContainerPath
}
return a.bindingPath.ContainersPath
}
func (a *application) IsUpdated() bool {
return !reflect.DeepEqual(a.persistedResource, a.resource)
}
func (a *application) BindableContainers() ([]map[string]interface{}, error) {
path := strings.Split(a.ContainersPath(), ".")
containers, found, err := converter.NestedResources(&corev1.Container{}, a.Resource().Object, path...)
if !found {
err = errors.New("no containers found in application resource")
}
if err != nil {
return nil, err
}
initPath := append(path[:len(path)-1], "initContainers")
initContainers, found, err := converter.NestedResources(&corev1.Container{}, a.Resource().Object, initPath...)
if found && err == nil {
containers = append(containers, initContainers...)
}
if len(a.bindableContainerNames) == 0 {
return containers, err
}
filteredContainers := make([]map[string]interface{}, 0, len(containers))
for _, c := range containers {
cname, ok := c["name"]
if ok && a.bindableContainerNames.Has(fmt.Sprintf("%v", cname)) {
filteredContainers = append(filteredContainers, c)
}
}
return filteredContainers, nil
}

View File

@@ -0,0 +1,31 @@
package context
import "github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
type flowCtrl struct {
retry bool
stop bool
err error
}
func (i *flowCtrl) FlowStatus() pipeline.FlowStatus {
return pipeline.FlowStatus{
Retry: i.retry,
Stop: i.stop,
Err: i.err,
}
}
func (i *flowCtrl) RetryProcessing(reason error) {
i.retry = true
i.stop = true
i.err = reason
}
func (i *flowCtrl) Error(err error) {
i.err = err
}
func (i *flowCtrl) StopProcessing() {
i.stop = true
}

View File

@@ -0,0 +1,387 @@
package context
import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"github.com/redhat-developer/service-binding-operator/apis"
"github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"sort"
"github.com/redhat-developer/service-binding-operator/pkg/converter"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"github.com/redhat-developer/service-binding-operator/pkg/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)
var _ pipeline.Context = &bindingImpl{}
type impl struct {
client dynamic.Interface
typeLookup K8STypeLookup
//nolint
services []*service
applications []*application
bindingItems pipeline.BindingItems
bindings []pipeline.Bindings
conditions map[string]*metav1.Condition
flowCtrl
bindingMeta *metav1.ObjectMeta
statusSecretName func() string
setStatusSecretName func(name string)
unstructuredBinding func() (*unstructured.Unstructured, error)
statusConditions func() *[]metav1.Condition
ownerReference func() metav1.OwnerReference
groupVersionResource func() schema.GroupVersionResource
}
type bindingImpl struct {
impl
serviceBinding *v1alpha1.ServiceBinding
}
func (i *impl) UnbindRequested() bool {
return !i.bindingMeta.DeletionTimestamp.IsZero()
}
type provider struct {
client dynamic.Interface
typeLookup K8STypeLookup
get func(binding interface{}) (pipeline.Context, error)
}
func (p *provider) Get(binding interface{}) (pipeline.Context, error) {
return p.get(binding)
}
var Provider = func(client dynamic.Interface, typeLookup K8STypeLookup) pipeline.ContextProvider {
return &provider{
client: client,
typeLookup: typeLookup,
get: func(binding interface{}) (pipeline.Context, error) {
switch sb := binding.(type) {
case *v1alpha1.ServiceBinding:
return &bindingImpl{
impl: impl{
conditions: make(map[string]*metav1.Condition),
client: client,
typeLookup: typeLookup,
bindingMeta: &sb.ObjectMeta,
statusSecretName: func() string {
return sb.Status.Secret
},
setStatusSecretName: func(name string) {
sb.Status.Secret = name
},
unstructuredBinding: func() (*unstructured.Unstructured, error) {
return converter.ToUnstructured(sb)
},
statusConditions: func() *[]metav1.Condition {
return &sb.Status.Conditions
},
ownerReference: func() metav1.OwnerReference {
return sb.AsOwnerReference()
},
groupVersionResource: func() schema.GroupVersionResource {
return v1alpha1.GroupVersionResource
},
},
serviceBinding: sb,
}, nil
}
return nil, fmt.Errorf("cannot create context for passed instance %v", binding)
},
}
}
func (i *bindingImpl) BindingName() string {
return i.bindingMeta.GetName()
}
func (i *bindingImpl) EnvBindings() []*pipeline.EnvBinding {
return make([]*pipeline.EnvBinding, 0)
}
func (i *bindingImpl) MountPath() string {
return i.serviceBinding.Spec.MountPath
}
func (i *bindingImpl) Mappings() map[string]string {
result := make(map[string]string)
for _, m := range i.serviceBinding.Spec.Mappings {
result[m.Name] = m.Value
}
return result
}
func (i *bindingImpl) Services() ([]pipeline.Service, error) {
if i.services == nil {
serviceRefs := i.serviceBinding.Spec.Services
for idx := 0; idx < len(serviceRefs); idx++ {
serviceRef := serviceRefs[idx]
gvr, err := i.typeLookup.ResourceForReferable(&serviceRef)
if err != nil {
return nil, err
}
if serviceRef.Namespace == nil {
serviceRef.Namespace = &i.serviceBinding.Namespace
}
u, err := i.client.Resource(*gvr).Namespace(*serviceRef.Namespace).Get(context.Background(), serviceRef.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
i.services = append(i.services, &service{client: i.client, resource: u, groupVersionResource: gvr, namespace: *serviceRef.Namespace, id: serviceRef.Id, lookForOwnedResources: i.serviceBinding.Spec.DetectBindingResources})
}
}
services := make([]pipeline.Service, len(i.services))
for idx := 0; idx < len(i.services); idx++ {
services[idx] = i.services[idx]
}
return services, nil
}
// Application return a list of applications.
// And if no application found, return an error
func (i *bindingImpl) Applications() ([]pipeline.Application, error) {
if i.applications == nil {
ref := i.serviceBinding.Spec.Application
gvr, err := i.typeLookup.ResourceForReferable(&ref)
if err != nil {
return nil, err
}
if i.serviceBinding.Spec.Application.Name != "" {
u, err := i.client.Resource(*gvr).Namespace(i.serviceBinding.Namespace).Get(context.Background(), ref.Name, metav1.GetOptions{})
if err != nil {
return nil, emptyApplicationsErr{err}
}
i.applications = append(i.applications, &application{gvr: gvr, persistedResource: u, bindingPath: i.serviceBinding.Spec.Application.BindingPath})
}
if i.serviceBinding.Spec.Application.LabelSelector != nil && i.serviceBinding.Spec.Application.LabelSelector.MatchLabels != nil {
matchLabels := i.serviceBinding.Spec.Application.LabelSelector.MatchLabels
opts := metav1.ListOptions{
LabelSelector: labels.Set(matchLabels).String(),
}
objList, err := i.client.Resource(*gvr).Namespace(i.serviceBinding.Namespace).List(context.Background(), opts)
if err != nil {
return nil, err
}
if len(objList.Items) == 0 {
return nil, emptyApplicationsErr{}
}
for index := range objList.Items {
i.applications = append(i.applications, &application{gvr: gvr, persistedResource: &(objList.Items[index]), bindingPath: i.serviceBinding.Spec.Application.BindingPath})
}
}
}
result := make([]pipeline.Application, len(i.applications))
for l, a := range i.applications {
result[l] = a
}
return result, nil
}
type emptyApplicationsErr struct {
originalErr error
}
func (e emptyApplicationsErr) Error() string {
if e.originalErr != nil {
return "cannot find application resources for the given reference: " + e.originalErr.Error()
}
return "cannot find application resources for the given reference"
}
func (i *impl) AddBindingItem(item *pipeline.BindingItem) {
i.bindingItems = append(i.bindingItems, item)
}
func (i *impl) BindingItems() pipeline.BindingItems {
var allItems pipeline.BindingItems
for _, b := range i.bindings {
items, err := b.Items()
if err != nil {
continue
}
allItems = append(allItems, items...)
}
if len(i.bindingItems) > 0 {
allItems = append(allItems, i.bindingItems...)
}
return allItems
}
func (i *impl) BindingSecretName() string {
name, _ := i.bindingSecretName()
return name
}
func (i *impl) bindingSecretName() (string, bool) {
if i.UnbindRequested() {
return i.statusSecretName(), true
}
if i.bindingItems == nil && len(i.bindings) == 1 {
ref := i.bindings[0].Source()
if ref != nil && ref.Namespace == i.bindingMeta.GetNamespace() {
return ref.Name, true
}
}
data := i.bindingItemMap()
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
hash := sha1.New()
for _, k := range keys {
_, _ = hash.Write([]byte(k))
_, _ = hash.Write([]byte(data[k]))
}
return i.bindingMeta.Name + "-" + string(hex.EncodeToString(hash.Sum(nil))[:8]), false
}
func (i *impl) bindingItemMap() map[string]string {
data := make(map[string]string)
for _, b := range i.bindings {
items, err := b.Items()
if err != nil {
continue
}
util.MergeMaps(data, items.AsMap())
}
if len(i.bindingItems) > 0 {
util.MergeMaps(data, i.bindingItems.AsMap())
}
return data
}
func (i *bindingImpl) NamingTemplate() string {
return i.serviceBinding.Spec.NamingTemplate()
}
func (i *bindingImpl) BindAsFiles() bool {
return i.serviceBinding.Spec.BindAsFiles
}
func (i *impl) persistBinding() error {
if i.bindingMeta.UID == "" {
return nil
}
for _, c := range i.conditions {
meta.SetStatusCondition(i.statusConditions(), *c)
}
u, err := i.unstructuredBinding()
if err != nil {
return err
}
client := i.client.Resource(i.groupVersionResource()).Namespace(i.bindingMeta.Namespace)
_, err = client.UpdateStatus(context.Background(), u, metav1.UpdateOptions{})
return err
}
func (i *impl) persistSecret() (string, error) {
name, secretExist := i.bindingSecretName()
if secretExist {
return name, nil
}
data := i.bindingItemMap()
if len(data) == 0 {
return "", nil
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: i.bindingMeta.Namespace,
Name: name,
},
StringData: data,
}
if i.bindingMeta.UID != "" {
secret.OwnerReferences = []metav1.OwnerReference{i.ownerReference()}
}
u, err := converter.ToUnstructuredAsGVK(secret, corev1.SchemeGroupVersion.WithKind("Secret"))
if err != nil {
return name, err
}
secretClient := i.client.Resource(corev1.SchemeGroupVersion.WithResource("secrets")).Namespace(i.bindingMeta.Namespace)
_, err = secretClient.Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
_, err = secretClient.Create(context.Background(), u, metav1.CreateOptions{})
return name, err
}
return name, err
}
_, err = secretClient.Update(context.Background(), u, metav1.UpdateOptions{})
return name, err
}
func (i *impl) Close() error {
if i.err != nil {
i.SetCondition(apis.Conditions().NotBindingReady().Reason("ProcessingError").Msg(i.err.Error()).Build())
return i.persistBinding()
}
secretName, err := i.persistSecret()
if err != nil {
i.SetCondition(apis.Conditions().NotBindingReady().Reason("ErrorPersistingSecret").Msg(err.Error()).Build())
_ = i.persistBinding()
return err
}
if secretName != "" {
i.setStatusSecretName(secretName)
}
for _, app := range i.applications {
if app.IsUpdated() {
_, err = i.client.Resource(*app.gvr).Namespace(i.bindingMeta.Namespace).Update(context.Background(), app.Resource(), metav1.UpdateOptions{})
if err != nil {
i.SetCondition(apis.Conditions().NotBindingReady().Reason("ApplicationUpdateError").Msg(err.Error()).Build())
_ = i.persistBinding()
return err
}
}
}
i.SetCondition(apis.Conditions().BindingReady().Reason("ApplicationsBound").Build())
return i.persistBinding()
}
func (i *impl) SetCondition(condition *metav1.Condition) {
i.conditions[condition.Type] = condition
}
func (i *impl) ReadConfigMap(namespace string, name string) (*unstructured.Unstructured, error) {
return i.client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
}
func (i *impl) ReadSecret(namespace string, name string) (*unstructured.Unstructured, error) {
return i.client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
}
func (i *impl) AddBindings(bindings pipeline.Bindings) {
i.bindings = append(i.bindings, bindings)
}

View File

@@ -0,0 +1,50 @@
package context
import (
"github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
)
//go:generate mockgen -destination=mocks/mocks.go -package=mocks . K8STypeLookup
type K8STypeLookup interface {
ResourceForReferable(obj kubernetes.Referable) (*schema.GroupVersionResource, error)
ResourceForKind(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, error)
KindForResource(gvr schema.GroupVersionResource) (*schema.GroupVersionKind, error)
}
type resourceLookup struct {
restMapper meta.RESTMapper
}
func ResourceLookup(restMapper meta.RESTMapper) K8STypeLookup {
return &resourceLookup{
restMapper: restMapper,
}
}
func (i *resourceLookup) ResourceForReferable(obj kubernetes.Referable) (*schema.GroupVersionResource, error) {
gvr, err := obj.GroupVersionResource()
if err == nil {
return gvr, nil
}
gvk, err := obj.GroupVersionKind()
if err != nil {
return nil, err
}
return i.ResourceForKind(*gvk)
}
func (i *resourceLookup) ResourceForKind(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, error) {
mapping, err := i.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, err
}
return &mapping.Resource, nil
}
func (i *resourceLookup) KindForResource(gvr schema.GroupVersionResource) (*schema.GroupVersionKind, error) {
gvk, err := i.restMapper.KindFor(gvr)
return &gvk, err
}

View File

@@ -0,0 +1,174 @@
package context
import (
"context"
e "errors"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/service-binding-operator/pkg/binding"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"reflect"
)
var _ pipeline.Service = &service{}
var crdGVRs = []schema.GroupVersionResource{
{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
},
{
Group: "apiextensions.k8s.io",
Version: "v1beta1",
Resource: "customresourcedefinitions",
},
}
var bindableResourceGVRs = []schema.GroupVersionResource{
{Group: "", Version: "v1", Resource: "configmaps"},
{Group: "", Version: "v1", Resource: "secrets"},
{Group: "", Version: "v1", Resource: "services"},
{Group: "route.openshift.io", Version: "v1", Resource: "routes"},
}
type service struct {
client dynamic.Interface
namespace string
resource *unstructured.Unstructured
groupVersionResource *schema.GroupVersionResource
crd *customResourceDefinition
crdLookup bool
lookForOwnedResources bool
bindingDefinitions []binding.Definition
id *string
}
func (s *service) OwnedResources() ([]*unstructured.Unstructured, error) {
uid := s.Resource().GetUID()
var result []*unstructured.Unstructured
if !s.lookForOwnedResources {
return result, nil
}
for _, gvr := range bindableResourceGVRs {
list, err := s.client.Resource(gvr).Namespace(s.namespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
if errors.IsNotFound(err) {
continue
}
return nil, err
}
for i := range list.Items {
item := list.Items[i]
for _, ownerRef := range item.GetOwnerReferences() {
if reflect.DeepEqual(ownerRef.UID, uid) {
result = append(result, &item)
}
}
}
}
return result, nil
}
func (s *service) Id() *string {
return s.id
}
func (s *service) Resource() *unstructured.Unstructured {
return s.resource
}
func (s *service) CustomResourceDefinition() (pipeline.CRD, error) {
if s.crd == nil {
if s.crdLookup {
return nil, nil
}
var err error
var u *unstructured.Unstructured
for _, crd := range crdGVRs {
u, err = s.client.Resource(crd).Get(context.Background(), s.groupVersionResource.GroupResource().String(), metav1.GetOptions{})
if err == nil {
s.crd = &customResourceDefinition{resource: u, client: s.client, ns: s.namespace, serviceGVR: s.groupVersionResource}
return s.crd, nil
}
}
if errors.IsNotFound(err) {
s.crdLookup = true
return nil, nil
}
return nil, err
}
return s.crd, nil
}
func (s *service) AddBindingDef(def binding.Definition) {
s.bindingDefinitions = append(s.bindingDefinitions, def)
}
func (s *service) BindingDefs() []binding.Definition {
return s.bindingDefinitions
}
type customResourceDefinition struct {
resource *unstructured.Unstructured
serviceGVR *schema.GroupVersionResource
client dynamic.Interface
ns string
}
func (c *customResourceDefinition) Resource() *unstructured.Unstructured {
return c.resource
}
func (c *customResourceDefinition) kind() string {
val, found, _ := unstructured.NestedString(c.resource.Object, "spec", "names", "kind")
if found {
return val
}
return ""
}
func (c *customResourceDefinition) Descriptor() (*olmv1alpha1.CRDDescription, error) {
csvs, err := c.client.Resource(olmv1alpha1.SchemeGroupVersion.WithResource("clusterserviceversions")).Namespace(c.ns).List(context.Background(), metav1.ListOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
if len(csvs.Items) == 0 {
return nil, nil
}
for _, csv := range csvs.Items {
ownedPath := []string{"spec", "customresourcedefinitions", "owned"}
ownedCRDs, exists, err := unstructured.NestedSlice(csv.Object, ownedPath...)
if err != nil {
return nil, err
}
if !exists {
continue
}
for _, crd := range ownedCRDs {
crdDesciption := &olmv1alpha1.CRDDescription{}
data, ok := crd.(map[string]interface{})
if !ok {
return nil, e.New("cannot cast to map")
}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(data, crdDesciption)
if err != nil {
return nil, err
}
if crdDesciption.Name == c.Resource().GetName() && crdDesciption.Kind == c.kind() && crdDesciption.Version == c.serviceGVR.Version {
return crdDesciption, nil
}
}
}
return nil, nil
}

View File

@@ -0,0 +1,174 @@
package context
import (
"context"
"fmt"
"github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha2"
"github.com/redhat-developer/service-binding-operator/pkg/converter"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/dynamic"
)
var _ pipeline.Context = &specImpl{}
var SpecProvider = func(client dynamic.Interface, typeLookup K8STypeLookup) pipeline.ContextProvider {
return &provider{
client: client,
typeLookup: typeLookup,
get: func(binding interface{}) (pipeline.Context, error) {
switch sb := binding.(type) {
case *v1alpha2.ServiceBinding:
if sb.Generation != 0 {
sb.Status.ObservedGeneration = sb.Generation
}
ctx := &specImpl{
impl: impl{
conditions: make(map[string]*metav1.Condition),
client: client,
typeLookup: typeLookup,
bindingMeta: &sb.ObjectMeta,
statusSecretName: func() string {
if sb.Status.Binding == nil {
return ""
}
return sb.Status.Binding.Name
},
setStatusSecretName: func(name string) {
sb.Status.Binding = &v1alpha2.ServiceBindingSecretReference{Name: name}
},
unstructuredBinding: func() (*unstructured.Unstructured, error) {
return converter.ToUnstructured(sb)
},
statusConditions: func() *[]metav1.Condition {
return &sb.Status.Conditions
},
ownerReference: func() metav1.OwnerReference {
return sb.AsOwnerReference()
},
groupVersionResource: func() schema.GroupVersionResource {
return v1alpha2.GroupVersionResource
},
},
serviceBinding: sb,
}
if sb.Spec.Type != "" {
ctx.AddBindingItem(&pipeline.BindingItem{Name: "type", Value: sb.Spec.Type})
}
if sb.Spec.Provider != "" {
ctx.AddBindingItem(&pipeline.BindingItem{Name: "provider", Value: sb.Spec.Provider})
}
return ctx, nil
}
return nil, fmt.Errorf("cannot create context for passed instance %v", binding)
},
}
}
type specImpl struct {
impl
serviceBinding *v1alpha2.ServiceBinding
}
func (i *specImpl) BindingName() string {
if i.serviceBinding.Spec.Name != "" {
return i.serviceBinding.Spec.Name
}
return i.bindingMeta.Name
}
func (i *specImpl) EnvBindings() []*pipeline.EnvBinding {
if len(i.serviceBinding.Spec.Env) == 0 {
return make([]*pipeline.EnvBinding, 0)
}
result := make([]*pipeline.EnvBinding, 0, len(i.serviceBinding.Spec.Env))
for _, e := range i.serviceBinding.Spec.Env {
result = append(result, &pipeline.EnvBinding{Var: e.Name, Name: e.Key})
}
return result
}
func (i *specImpl) Services() ([]pipeline.Service, error) {
if i.services == nil {
serviceRef := i.serviceBinding.Spec.Service
gvr, err := i.typeLookup.ResourceForReferable(&serviceRef)
if err != nil {
return nil, err
}
u, err := i.client.Resource(*gvr).Namespace(i.serviceBinding.Namespace).Get(context.Background(), serviceRef.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
i.services = append(i.services, &service{client: i.client, resource: u, groupVersionResource: gvr, namespace: i.serviceBinding.Namespace})
}
services := make([]pipeline.Service, len(i.services))
for idx := 0; idx < len(i.services); idx++ {
services[idx] = i.services[idx]
}
return services, nil
}
func (i *specImpl) Applications() ([]pipeline.Application, error) {
if i.applications == nil {
ref := i.serviceBinding.Spec.Application
gvr, err := i.typeLookup.ResourceForReferable(&ref)
if err != nil {
return nil, err
}
if i.serviceBinding.Spec.Application.Name != "" {
u, err := i.client.Resource(*gvr).Namespace(i.serviceBinding.Namespace).Get(context.Background(), ref.Name, metav1.GetOptions{})
if err != nil {
return nil, emptyApplicationsErr{err}
}
i.applications = append(i.applications, &application{gvr: gvr, persistedResource: u, bindableContainerNames: sets.NewString(i.serviceBinding.Spec.Application.Containers...)})
}
if i.serviceBinding.Spec.Application.Selector.MatchLabels != nil {
matchLabels := i.serviceBinding.Spec.Application.Selector.MatchLabels
opts := metav1.ListOptions{
LabelSelector: labels.Set(matchLabels).String(),
}
objList, err := i.client.Resource(*gvr).Namespace(i.serviceBinding.Namespace).List(context.Background(), opts)
if err != nil {
return nil, err
}
if len(objList.Items) == 0 {
return nil, emptyApplicationsErr{}
}
for index := range objList.Items {
i.applications = append(i.applications, &application{gvr: gvr, persistedResource: &(objList.Items[index]), bindableContainerNames: sets.NewString(i.serviceBinding.Spec.Application.Containers...)})
}
}
}
result := make([]pipeline.Application, len(i.applications))
for l, a := range i.applications {
result[l] = a
}
return result, nil
}
func (s *specImpl) BindAsFiles() bool {
return true
}
func (s *specImpl) MountPath() string {
return ""
}
func (s *specImpl) NamingTemplate() string {
return ""
}
func (s *specImpl) Mappings() map[string]string {
return make(map[string]string)
}

View File

@@ -0,0 +1,336 @@
package collect
import (
"encoding/base64"
"errors"
"fmt"
"reflect"
"strings"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/service-binding-operator/apis"
"github.com/redhat-developer/service-binding-operator/pkg/binding"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"github.com/redhat-developer/service-binding-operator/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var DataNotMap = errors.New("Returned data are not a map, skip collecting")
const (
ErrorReadingServicesReason = "ErrorReadingServices"
ErrorReadingCRD = "ErrorReadingCRD"
ErrorReadingDescriptorReason = "ErrorReadingDescriptor"
ErrorReadingBindingReason = "ErrorReadingBinding"
ErrorReadingSecret = "ErrorReadingSecret"
)
func PreFlight(ctx pipeline.Context) {
_, err := ctx.Services()
if err != nil {
requestRetry(ctx, ErrorReadingServicesReason, err)
return
}
}
func BindingDefinitions(ctx pipeline.Context) {
services, _ := ctx.Services()
for _, service := range services {
anns := make(map[string]string)
crd, err := service.CustomResourceDefinition()
if err != nil {
requestRetry(ctx, ErrorReadingCRD, err)
return
}
if crd != nil {
descr, err := crd.Descriptor()
if err != nil {
requestRetry(ctx, ErrorReadingDescriptorReason, err)
return
}
if descr != nil {
util.MergeMaps(anns, bindingAnnotations(descr))
}
util.MergeMaps(anns, crd.Resource().GetAnnotations())
}
util.MergeMaps(anns, service.Resource().GetAnnotations())
for k, v := range anns {
definition, err := makeBindingDefinition(k, v, ctx)
if err != nil {
continue
}
service.AddBindingDef(definition)
}
}
}
func BindingItems(ctx pipeline.Context) {
services, _ := ctx.Services()
for _, service := range services {
serviceResource := service.Resource()
for _, bd := range service.BindingDefs() {
bindingValue, err := bd.Apply(serviceResource)
if err != nil {
requestRetry(ctx, ErrorReadingBindingReason, err)
return
}
val := bindingValue.Get()
v := reflect.ValueOf(val)
if v.Kind() != reflect.Map {
requestRetry(ctx, "DataNotMap", DataNotMap)
return
}
for _, n := range v.MapKeys() {
collectItems("", ctx, service, n, v.MapIndex(n).Interface())
}
}
}
}
const ProvisionedServiceAnnotationKey = "service.binding/provisioned-service"
func ProvisionedService(ctx pipeline.Context) {
services, _ := ctx.Services()
for _, service := range services {
res := service.Resource()
secretName, found, err := unstructured.NestedString(res.Object, "status", "binding", "name")
if err != nil {
requestRetry(ctx, ErrorReadingBindingReason, err)
return
}
if found {
if secretName != "" {
secret, err := ctx.ReadSecret(res.GetNamespace(), secretName)
if err != nil {
requestRetry(ctx, ErrorReadingSecret, err)
return
}
ctx.AddBindings(&pipeline.SecretBackedBindings{Service: service, Secret: secret})
}
} else {
crd, err := service.CustomResourceDefinition()
if err != nil {
requestRetry(ctx, ErrorReadingCRD, err)
return
}
if crd == nil {
continue
}
v, ok := crd.Resource().GetAnnotations()[ProvisionedServiceAnnotationKey]
if ok && v == "true" {
requestRetry(ctx, ErrorReadingBindingReason, fmt.Errorf("CRD of service %v/%v indicates provisioned service, but no secret name provided under .status.binding.name", res.GetNamespace(), res.GetName()))
return
}
}
}
}
func DirectSecretReference(ctx pipeline.Context) {
// Error is ignored as this check is there in the PreFlight stage.
// That stage was created to perform common checks for all followup stages.
services, _ := ctx.Services()
for _, service := range services {
res := service.Resource()
if res.GetKind() == "Secret" && res.GetAPIVersion() == "v1" && res.GroupVersionKind().Group == "" {
annotations := res.GetAnnotations()
for k := range annotations {
if strings.HasPrefix(k, binding.AnnotationPrefix) {
return
}
}
name := res.GetName()
secret, err := ctx.ReadSecret(res.GetNamespace(), name)
if err != nil {
requestRetry(ctx, ErrorReadingSecret, err)
return
}
ctx.AddBindings(&pipeline.SecretBackedBindings{Service: service, Secret: secret})
}
}
}
type pathMapping struct {
input string
transform func(interface{}) (interface{}, error)
output string
}
var bindableResources = map[schema.GroupVersionKind]pathMapping{
schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}: {
input: "data",
output: "",
},
schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}: {
input: "data",
transform: func(i interface{}) (interface{}, error) {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Map {
return nil, errors.New("data is not map")
}
result := map[string]string{}
for _, n := range v.MapKeys() {
b, err := base64.StdEncoding.DecodeString(fmt.Sprintf("%v", v.MapIndex(n).Interface()))
if err != nil {
return nil, err
}
key := fmt.Sprintf("%v", n.Interface())
result[key] = string(b)
}
return result, nil
},
output: "",
},
schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}: {
input: "spec.clusterIP",
output: "clusterIP",
},
schema.GroupVersionKind{
Group: "route.openshift.io",
Version: "v1",
Kind: "Route",
}: {
input: "spec.host",
output: "host",
},
}
func OwnedResources(ctx pipeline.Context) {
services, err := ctx.Services()
if err != nil {
requestRetry(ctx, ErrorReadingServicesReason, err)
return
}
for _, service := range services {
ownedResources, err := service.OwnedResources()
if err != nil {
requestRetry(ctx, ErrorReadingServicesReason, err)
return
}
for _, res := range ownedResources {
pathMapping, ok := bindableResources[res.GroupVersionKind()]
if !ok {
continue
}
val, found, err := unstructured.NestedFieldNoCopy(res.Object, strings.Split(pathMapping.input, ".")...)
if !found {
err = errors.New("Not found")
}
if err != nil {
requestRetry(ctx, ErrorReadingServicesReason, err)
return
}
if pathMapping.transform != nil {
val, err = pathMapping.transform(val)
if err != nil {
requestRetry(ctx, ErrorReadingServicesReason, err)
return
}
}
collectItems("", ctx, service, reflect.ValueOf(pathMapping.output), val)
}
}
}
func collectItems(prefix string, ctx pipeline.Context, service pipeline.Service, k reflect.Value, val interface{}) {
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Map:
p := prefix + k.String() + "_"
if p == "_" {
p = ""
}
for _, n := range v.MapKeys() {
collectItems(p, ctx, service, n, v.MapIndex(n).Interface())
}
case reflect.Slice:
for i := 0; i < v.Len(); i++ {
ctx.AddBindingItem(&pipeline.BindingItem{Name: fmt.Sprintf("%v_%v", prefix+k.String(), i), Value: v.Index(i).Interface(), Source: service})
}
default:
ctx.AddBindingItem(&pipeline.BindingItem{Name: prefix + k.String(), Value: v.Interface(), Source: service})
}
}
func requestRetry(ctx pipeline.Context, reason string, err error) {
ctx.RetryProcessing(err)
ctx.SetCondition(notCollectionReadyCond(reason, err))
}
func notCollectionReadyCond(reason string, err error) *metav1.Condition {
return apis.Conditions().NotCollectionReady().Reason(reason).Msg(err.Error()).Build()
}
func makeBindingDefinition(key string, value string, ctx pipeline.Context) (binding.Definition, error) {
return binding.NewDefinitionBuilder(key,
value,
func(namespace string, name string) (*unstructured.Unstructured, error) {
return ctx.ReadConfigMap(namespace, name)
},
func(namespace string, name string) (*unstructured.Unstructured, error) {
return ctx.ReadSecret(namespace, name)
}).Build()
}
func bindingAnnotations(crdDescription *olmv1alpha1.CRDDescription) map[string]string {
anns := make(map[string]string)
for _, sd := range crdDescription.StatusDescriptors {
objectType := getObjectType(sd.XDescriptors)
for _, xd := range sd.XDescriptors {
loadDescriptor(anns, sd.Path, xd, "status", objectType)
}
}
for _, sd := range crdDescription.SpecDescriptors {
objectType := getObjectType(sd.XDescriptors)
for _, xd := range sd.XDescriptors {
loadDescriptor(anns, sd.Path, xd, "spec", objectType)
}
}
return anns
}
func getObjectType(descriptors []string) string {
typeAnno := "urn:alm:descriptor:io.kubernetes:"
for _, desc := range descriptors {
if strings.HasPrefix(desc, typeAnno) {
return strings.TrimPrefix(desc, typeAnno)
}
}
return ""
}
func loadDescriptor(anns map[string]string, path string, descriptor string, root string, objectType string) {
if !strings.HasPrefix(descriptor, binding.AnnotationPrefix) {
return
}
keys := strings.Split(descriptor, ":")
key := binding.AnnotationPrefix
value := ""
if len(keys) > 1 {
key += "/" + keys[1]
} else {
key += "/" + path
}
p := []string{fmt.Sprintf("path={.%s.%s}", root, path)}
if len(keys) > 1 {
p = append(p, keys[2:]...)
}
if objectType != "" {
p = append(p, []string{fmt.Sprintf("objectType=%s", objectType)}...)
}
value += strings.Join(p, ",")
anns[key] = value
}

View File

@@ -0,0 +1,49 @@
package mapping
import (
"bytes"
"encoding/json"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
"text/template"
)
func Handle(ctx pipeline.Context) {
bindingItems := ctx.BindingItems()
templateVars := make(map[string]interface{})
services, _ := ctx.Services()
for _, s := range services {
if s.Id() != nil {
templateVars[*s.Id()] = s.Resource().Object
}
}
for _, bi := range bindingItems {
templateVars[bi.Name] = bi.Value
}
for name, valueTemplate := range ctx.Mappings() {
tmpl, err := template.New("mappings").Funcs(template.FuncMap{"json": marshalToJSON}).Parse(valueTemplate)
if err != nil {
ctx.StopProcessing()
ctx.Error(err)
return
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, templateVars)
if err != nil {
ctx.StopProcessing()
ctx.Error(err)
return
}
ctx.AddBindingItem(&pipeline.BindingItem{Name: name, Value: buf.String()})
}
}
func marshalToJSON(m interface{}) (string, error) {
bytes, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(bytes), nil
}

View File

@@ -0,0 +1,40 @@
package naming
import (
"github.com/redhat-developer/service-binding-operator/apis"
"github.com/redhat-developer/service-binding-operator/pkg/naming"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
)
const StrategyError = "NamingStrategyError"
func Handle(ctx pipeline.Context) {
for _, item := range ctx.BindingItems() {
if item.Source != nil {
template, err := naming.NewTemplate(ctx.NamingTemplate(), templateData(item.Source))
if err != nil {
stop(ctx, err)
return
}
item.Name, err = template.GetBindingName(item.Name)
if err != nil {
stop(ctx, err)
return
}
}
}
}
func templateData(service pipeline.Service) map[string]interface{} {
res := service.Resource()
return map[string]interface{}{
"kind": res.GetKind(),
"name": res.GetName(),
}
}
func stop(ctx pipeline.Context, err error) {
ctx.Error(err)
ctx.StopProcessing()
ctx.SetCondition(apis.Conditions().NotCollectionReady().Reason(StrategyError).Msg(err.Error()).Build())
}

View File

@@ -0,0 +1,402 @@
package project
import (
"errors"
"fmt"
"github.com/redhat-developer/service-binding-operator/apis"
"github.com/redhat-developer/service-binding-operator/pkg/converter"
"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"path"
"reflect"
"strings"
)
func PreFlightCheck(mandatoryBindingKeys ...string) func(pipeline.Context) {
return func(ctx pipeline.Context) {
ctx.SetCondition(apis.Conditions().CollectionReady().DataCollected().Build())
applications, err := ctx.Applications()
if err != nil {
ctx.RetryProcessing(err)
ctx.SetCondition(apis.Conditions().NotInjectionReady().ApplicationNotFound().Msg(err.Error()).Build())
return
}
if len(applications) == 0 {
ctx.SetCondition(apis.Conditions().NotInjectionReady().Reason(apis.EmptyApplicationReason).Build())
ctx.StopProcessing()
return
}
if len(mandatoryBindingKeys) > 0 {
items := ctx.BindingItems()
itemMap := items.AsMap()
for _, bk := range mandatoryBindingKeys {
if _, found := itemMap[bk]; !found {
err := fmt.Errorf("Mandatory binding '%v' not found", bk)
ctx.SetCondition(apis.Conditions().NotInjectionReady().Reason(apis.RequiredBindingNotFound).Msg(err.Error()).Build())
ctx.Error(err)
ctx.StopProcessing()
return
}
}
}
}
}
func PostFlightCheck(ctx pipeline.Context) {
ctx.SetCondition(apis.Conditions().InjectionReady().Reason("ApplicationUpdated").Build())
}
func InjectSecretRef(ctx pipeline.Context) {
applications, _ := ctx.Applications()
for _, app := range applications {
secretPath := app.SecretPath()
if secretPath == "" {
continue
}
err := unstructured.SetNestedField(app.Resource().Object, ctx.BindingSecretName(), strings.Split(secretPath, ".")...)
if err != nil {
stop(ctx, err)
return
}
}
}
func BindingsAsEnv(ctx pipeline.Context) {
envBindings := ctx.EnvBindings()
if ctx.BindAsFiles() && len(envBindings) == 0 {
return
}
secretName := ctx.BindingSecretName()
var envVars []interface{}
if len(envBindings) > 0 {
envVars = make([]interface{}, 0, len(envBindings))
for _, e := range envBindings {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&corev1.EnvVar{
Name: e.Var,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: e.Name,
},
},
})
if err != nil {
stop(ctx, err)
return
}
envVars = append(envVars, u)
}
}
applications, _ := ctx.Applications()
envFromSecret := corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
},
}
for _, app := range applications {
if app.SecretPath() != "" {
continue
}
containerResources, err := app.BindableContainers()
if containerResources == nil && err == nil {
err = errors.New("Containers not found in app resource")
}
if err != nil {
stop(ctx, err)
return
}
for _, container := range containerResources {
if !ctx.BindAsFiles() {
envFrom, found := container["envFrom"]
if !found {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&envFromSecret)
if err != nil {
stop(ctx, err)
return
}
container["envFrom"] = []interface{}{u}
continue
}
envFromSlice, ok := envFrom.([]interface{})
if !ok {
stop(ctx, errors.New("envFrom not a slice"))
return
}
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&envFromSecret)
if err != nil {
stop(ctx, err)
return
}
container["envFrom"] = append(envFromSlice, u)
continue
}
env, found := container["env"]
if !found {
container["env"] = envVars
continue
}
envSlice, ok := env.([]interface{})
if !ok {
stop(ctx, errors.New("env not a slice"))
return
}
container["env"] = append(envSlice, envVars...)
}
}
}
var volumesPath = []string{"spec", "template", "spec", "volumes"}
func BindingsAsFiles(ctx pipeline.Context) {
if !ctx.BindAsFiles() {
return
}
secretName := ctx.BindingSecretName()
bindingName := ctx.BindingName()
applications, _ := ctx.Applications()
for _, app := range applications {
if app.SecretPath() != "" {
continue
}
appResource := app.Resource()
volumerResources, found, err := converter.NestedResources(&corev1.Volume{}, appResource.Object, volumesPath...)
if err != nil {
stop(ctx, err)
return
}
volume, err := runtime.DefaultUnstructuredConverter.ToUnstructured(
&corev1.Volume{
Name: bindingName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
},
},
})
if err != nil {
stop(ctx, err)
return
}
if found {
var newVolumes []interface{}
exist := false
for _, v := range volumerResources {
if v["name"] == volume["name"] {
exist = true
if !reflect.DeepEqual(v["secret"], volume["secret"]) {
newVolumes = append(newVolumes, volume)
} else {
newVolumes = append(newVolumes, v)
}
} else {
newVolumes = append(newVolumes, v)
}
}
if !exist {
newVolumes = append(newVolumes, volume)
}
if err = unstructured.SetNestedSlice(appResource.Object, newVolumes, volumesPath...); err != nil {
stop(ctx, err)
return
}
} else {
if err = unstructured.SetNestedSlice(appResource.Object, []interface{}{volume}, volumesPath...); err != nil {
stop(ctx, err)
return
}
}
containerResources, err := app.BindableContainers()
if containerResources == nil && err == nil {
err = errors.New("Containers not found in app resource")
}
if err != nil {
stop(ctx, err)
return
}
for _, container := range containerResources {
mountPath, err := mountPath(container, ctx)
if err != nil {
stop(ctx, err)
return
}
volumeMounts, found, err := converter.NestedResources(&corev1.VolumeMount{}, container, "volumeMounts")
if err != nil {
stop(ctx, err)
return
}
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&corev1.VolumeMount{
Name: bindingName,
MountPath: mountPath,
})
if err != nil {
stop(ctx, err)
return
}
if found {
var newVolumeMounts []interface{}
exist := false
for _, vm := range volumeMounts {
if vm["name"] == u["name"] {
exist = true
if !reflect.DeepEqual(vm, u) {
newVolumeMounts = append(newVolumeMounts, u)
} else {
newVolumeMounts = append(newVolumeMounts, vm)
}
} else {
newVolumeMounts = append(newVolumeMounts, vm)
}
}
if !exist {
newVolumeMounts = append(newVolumeMounts, u)
}
container["volumeMounts"] = newVolumeMounts
} else {
if err := unstructured.SetNestedField(container, []interface{}{u}, "volumeMounts"); err != nil {
stop(ctx, err)
return
}
}
}
}
}
func Unbind(ctx pipeline.Context) {
if !ctx.UnbindRequested() {
return
}
applications, err := ctx.Applications()
if err != nil || len(applications) == 0 {
ctx.StopProcessing()
return
}
secretName := ctx.BindingSecretName()
bindingName := ctx.BindingName()
if secretName == "" {
ctx.StopProcessing()
return
}
for _, app := range applications {
appResource := app.Resource()
podSpec, found, err := unstructured.NestedFieldNoCopy(appResource.Object, volumesPath[:len(volumesPath)-1]...)
if !found || err != nil {
continue
}
podSpecMap, ok := podSpec.(map[string]interface{})
if !ok {
continue
}
volumeResources, found, _ := converter.NestedResources(&corev1.Volume{}, podSpecMap, "volumes")
if found {
for i, vol := range volumeResources {
if val, found, err := unstructured.NestedString(vol, "name"); found && err == nil && val == bindingName {
s := append(volumeResources[:i], volumeResources[i+1:]...)
if len(s) == 0 {
delete(podSpecMap, "volumes")
} else {
podSpecMap["volumes"] = s
}
break
}
}
}
containerResources, err := app.BindableContainers()
if containerResources == nil && err == nil {
ctx.StopProcessing()
return
}
for _, container := range containerResources {
envFrom, found, _ := converter.NestedResources(&corev1.EnvFromSource{}, container, "envFrom")
if found {
for i, envSource := range envFrom {
if val, found, err := unstructured.NestedString(envSource, "secretRef", "name"); found && err == nil && val == secretName {
s := append(envFrom[:i], envFrom[i+1:]...)
if len(s) == 0 {
delete(container, "envFrom")
} else {
container["envFrom"] = s
}
break
}
}
}
volumeMounts, found, _ := converter.NestedResources(&corev1.VolumeMount{}, container, "volumeMounts")
if found {
for i, vm := range volumeMounts {
if val, found, err := unstructured.NestedString(vm, "name"); found && err == nil && val == bindingName {
s := append(volumeMounts[:i], volumeMounts[i+1:]...)
if len(s) == 0 {
delete(container, "volumeMounts")
} else {
container["volumeMounts"] = s
}
break
}
}
}
}
}
ctx.StopProcessing()
}
const bindingRootEnvVar = "SERVICE_BINDING_ROOT"
func mountPath(container map[string]interface{}, ctx pipeline.Context) (string, error) {
envs, found, err := converter.NestedResources(&corev1.EnvVar{}, container, "env")
if err != nil {
return "", err
}
bindingRoot := ""
if found {
for _, e := range envs {
if e["name"] == bindingRootEnvVar {
bindingRoot = fmt.Sprintf("%v", e["value"])
return path.Join(bindingRoot, ctx.BindingName()), nil
}
}
}
mp := ctx.MountPath()
if mp == "" {
bindingRoot = "/bindings"
mp = path.Join(bindingRoot, ctx.BindingName())
} else {
return mp, nil
}
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&corev1.EnvVar{
Name: bindingRootEnvVar,
Value: bindingRoot,
})
if err != nil {
return "", err
}
envs = append(envs, u)
if found {
container["env"] = append(envs, u)
} else {
if err := unstructured.SetNestedField(container, []interface{}{u}, "env"); err != nil {
return "", err
}
}
return mp, nil
}
func stop(ctx pipeline.Context, err error) {
ctx.StopProcessing()
ctx.Error(err)
ctx.SetCondition(apis.Conditions().NotInjectionReady().Reason("Error").Msg(err.Error()).Build())
}

View File

@@ -0,0 +1,68 @@
package pipeline
import (
"encoding/base64"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var _ Bindings = &SecretBackedBindings{}
// bindings whose life-cycle is bound to k8s secret
type SecretBackedBindings struct {
// service associated to the bindings
Service Service
// secret containing the bindings
// each binding correspond to a (key, value) pair
Secret *unstructured.Unstructured
items BindingItems
}
func (s *SecretBackedBindings) Items() (BindingItems, error) {
if s.items != nil {
return s.items, nil
}
data, found, err := unstructured.NestedStringMap(s.Secret.Object, "data")
if err != nil {
return nil, err
}
if found {
for k, v := range data {
val, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
s.items = append(s.items, &BindingItem{
Name: k,
Value: string(val),
Source: s.Service,
})
}
} else {
s.items = make([]*BindingItem, 0)
}
return s.items, nil
}
func (s *SecretBackedBindings) Source() *corev1.ObjectReference {
ref := &corev1.ObjectReference{
Kind: s.Secret.GetKind(),
APIVersion: s.Secret.GetAPIVersion(),
Name: s.Secret.GetName(),
Namespace: s.Secret.GetNamespace(),
}
if s.items == nil {
return ref
}
val, found, err := unstructured.NestedStringMap(s.Secret.Object, "data")
if err != nil || !found {
return nil
}
for _, item := range s.items {
if _, ok := val[item.Name]; !ok {
return nil
}
}
return ref
}

View File

@@ -0,0 +1,8 @@
package util
func MergeMaps(dest map[string]string, src map[string]string) map[string]string {
for k, v := range src {
dest[k] = v
}
return dest
}

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View File

@@ -0,0 +1,9 @@
module gomodules.xyz/jsonpatch/v2
go 1.12
require (
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/pkg/errors v0.8.1 // indirect
github.com/stretchr/testify v1.3.0
)

View File

@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@@ -0,0 +1,336 @@
package jsonpatch
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
)
var errBadJSONDoc = fmt.Errorf("invalid JSON Document")
type JsonPatchOperation = Operation
type Operation struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
func (j *Operation) Json() string {
b, _ := json.Marshal(j)
return string(b)
}
func (j *Operation) MarshalJSON() ([]byte, error) {
var b bytes.Buffer
b.WriteString("{")
b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation))
b.WriteString(fmt.Sprintf(`,"path":"%s"`, j.Path))
// Consider omitting Value for non-nullable operations.
if j.Value != nil || j.Operation == "replace" || j.Operation == "add" {
v, err := json.Marshal(j.Value)
if err != nil {
return nil, err
}
b.WriteString(`,"value":`)
b.Write(v)
}
b.WriteString("}")
return b.Bytes(), nil
}
type ByPath []Operation
func (a ByPath) Len() int { return len(a) }
func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path }
func NewOperation(op, path string, value interface{}) Operation {
return Operation{Operation: op, Path: path, Value: value}
}
// CreatePatch creates a patch as specified in http://jsonpatch.com/
//
// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content.
// The function will return an array of JsonPatchOperations
//
// An error will be returned if any of the two documents are invalid.
func CreatePatch(a, b []byte) ([]Operation, error) {
var aI interface{}
var bI interface{}
err := json.Unmarshal(a, &aI)
if err != nil {
return nil, errBadJSONDoc
}
err = json.Unmarshal(b, &bI)
if err != nil {
return nil, errBadJSONDoc
}
return handleValues(aI, bI, "", []Operation{})
}
// Returns true if the values matches (must be json types)
// The types of the values must match, otherwise it will always return false
// If two map[string]interface{} are given, all elements must match.
func matchesValue(av, bv interface{}) bool {
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
return false
}
switch at := av.(type) {
case string:
bt, ok := bv.(string)
if ok && bt == at {
return true
}
case float64:
bt, ok := bv.(float64)
if ok && bt == at {
return true
}
case bool:
bt, ok := bv.(bool)
if ok && bt == at {
return true
}
case map[string]interface{}:
bt, ok := bv.(map[string]interface{})
if !ok {
return false
}
for key := range at {
if !matchesValue(at[key], bt[key]) {
return false
}
}
for key := range bt {
if !matchesValue(at[key], bt[key]) {
return false
}
}
return true
case []interface{}:
bt, ok := bv.([]interface{})
if !ok {
return false
}
if len(bt) != len(at) {
return false
}
for key := range at {
if !matchesValue(at[key], bt[key]) {
return false
}
}
for key := range bt {
if !matchesValue(at[key], bt[key]) {
return false
}
}
return true
}
return false
}
// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence. This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.
// TODO decode support:
// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")
func makePath(path string, newPart interface{}) string {
key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart))
if path == "" {
return "/" + key
}
if strings.HasSuffix(path, "/") {
return path + key
}
return path + "/" + key
}
// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations.
func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operation, error) {
for key, bv := range b {
p := makePath(path, key)
av, ok := a[key]
// value was added
if !ok {
patch = append(patch, NewOperation("add", p, bv))
continue
}
// Types are the same, compare values
var err error
patch, err = handleValues(av, bv, p, patch)
if err != nil {
return nil, err
}
}
// Now add all deleted values as nil
for key := range a {
_, found := b[key]
if !found {
p := makePath(path, key)
patch = append(patch, NewOperation("remove", p, nil))
}
}
return patch, nil
}
func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) {
{
at := reflect.TypeOf(av)
bt := reflect.TypeOf(bv)
if at == nil && bt == nil {
// do nothing
return patch, nil
} else if at == nil && bt != nil {
return append(patch, NewOperation("add", p, bv)), nil
} else if at != bt {
// If types have changed, replace completely (preserves null in destination)
return append(patch, NewOperation("replace", p, bv)), nil
}
}
var err error
switch at := av.(type) {
case map[string]interface{}:
bt := bv.(map[string]interface{})
patch, err = diff(at, bt, p, patch)
if err != nil {
return nil, err
}
case string, float64, bool:
if !matchesValue(av, bv) {
patch = append(patch, NewOperation("replace", p, bv))
}
case []interface{}:
bt := bv.([]interface{})
if isSimpleArray(at) && isSimpleArray(bt) {
patch = append(patch, compareEditDistance(at, bt, p)...)
} else {
n := min(len(at), len(bt))
for i := len(at) - 1; i >= n; i-- {
patch = append(patch, NewOperation("remove", makePath(p, i), nil))
}
for i := n; i < len(bt); i++ {
patch = append(patch, NewOperation("add", makePath(p, i), bt[i]))
}
for i := 0; i < n; i++ {
var err error
patch, err = handleValues(at[i], bt[i], makePath(p, i), patch)
if err != nil {
return nil, err
}
}
}
default:
panic(fmt.Sprintf("Unknown type:%T ", av))
}
return patch, nil
}
func isBasicType(a interface{}) bool {
switch a.(type) {
case string, float64, bool:
default:
return false
}
return true
}
func isSimpleArray(a []interface{}) bool {
for i := range a {
switch a[i].(type) {
case string, float64, bool:
default:
val := reflect.ValueOf(a[i])
if val.Kind() == reflect.Map {
for _, k := range val.MapKeys() {
av := val.MapIndex(k)
if av.Kind() == reflect.Ptr || av.Kind() == reflect.Interface {
if av.IsNil() {
continue
}
av = av.Elem()
}
if av.Kind() != reflect.String && av.Kind() != reflect.Float64 && av.Kind() != reflect.Bool {
return false
}
}
return true
}
return false
}
}
return true
}
// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
// Adapted from https://github.com/texttheater/golang-levenshtein
func compareEditDistance(s, t []interface{}, p string) []Operation {
m := len(s)
n := len(t)
d := make([][]int, m+1)
for i := 0; i <= m; i++ {
d[i] = make([]int, n+1)
d[i][0] = i
}
for j := 0; j <= n; j++ {
d[0][j] = j
}
for j := 1; j <= n; j++ {
for i := 1; i <= m; i++ {
if reflect.DeepEqual(s[i-1], t[j-1]) {
d[i][j] = d[i-1][j-1] // no op required
} else {
del := d[i-1][j] + 1
add := d[i][j-1] + 1
rep := d[i-1][j-1] + 1
d[i][j] = min(rep, min(add, del))
}
}
}
return backtrace(s, t, p, m, n, d)
}
func min(x int, y int) int {
if y < x {
return y
}
return x
}
func backtrace(s, t []interface{}, p string, i int, j int, matrix [][]int) []Operation {
if i > 0 && matrix[i-1][j]+1 == matrix[i][j] {
op := NewOperation("remove", makePath(p, i-1), nil)
return append([]Operation{op}, backtrace(s, t, p, i-1, j, matrix)...)
}
if j > 0 && matrix[i][j-1]+1 == matrix[i][j] {
op := NewOperation("add", makePath(p, i), t[j-1])
return append([]Operation{op}, backtrace(s, t, p, i, j-1, matrix)...)
}
if i > 0 && j > 0 && matrix[i-1][j-1]+1 == matrix[i][j] {
if isBasicType(s[0]) {
op := NewOperation("replace", makePath(p, i-1), t[j-1])
return append([]Operation{op}, backtrace(s, t, p, i-1, j-1, matrix)...)
}
p2, _ := handleValues(s[i-1], t[j-1], makePath(p, i-1), []Operation{})
return append(p2, backtrace(s, t, p, i-1, j-1, matrix)...)
}
if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] {
return backtrace(s, t, p, i-1, j-1, matrix)
}
return []Operation{}
}

View File

@@ -0,0 +1,5 @@
inverseRules:
# Allow use of this package in all k8s.io packages.
- selectorRegexp: k8s[.]io
allowedPrefixes:
- ''

View File

@@ -0,0 +1,59 @@
/*
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 v1beta1
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
func Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in *apiextensions.JSONSchemaProps, out *JSONSchemaProps, s conversion.Scope) error {
if err := autoConvert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in, out, s); err != nil {
return err
}
if in.Default != nil && *(in.Default) == nil {
out.Default = nil
}
if in.Example != nil && *(in.Example) == nil {
out.Example = nil
}
return nil
}
func Convert_apiextensions_JSON_To_v1beta1_JSON(in *apiextensions.JSON, out *JSON, s conversion.Scope) error {
raw, err := json.Marshal(*in)
if err != nil {
return err
}
out.Raw = raw
return nil
}
func Convert_v1beta1_JSON_To_apiextensions_JSON(in *JSON, out *apiextensions.JSON, s conversion.Scope) error {
if in != nil {
var i interface{}
if err := json.Unmarshal(in.Raw, &i); err != nil {
return err
}
*out = i
} else {
out = nil
}
return nil
}

View File

@@ -0,0 +1,270 @@
/*
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 v1beta1
// TODO: Update this after a tag is created for interface fields in DeepCopy
func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps {
if in == nil {
return nil
}
out := new(JSONSchemaProps)
*out = *in
if in.Ref != nil {
in, out := &in.Ref, &out.Ref
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.Maximum != nil {
in, out := &in.Maximum, &out.Maximum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.Minimum != nil {
in, out := &in.Minimum, &out.Minimum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.MaxLength != nil {
in, out := &in.MaxLength, &out.MaxLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinLength != nil {
in, out := &in.MinLength, &out.MinLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MaxItems != nil {
in, out := &in.MaxItems, &out.MaxItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinItems != nil {
in, out := &in.MinItems, &out.MinItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MultipleOf != nil {
in, out := &in.MultipleOf, &out.MultipleOf
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.MaxProperties != nil {
in, out := &in.MaxProperties, &out.MaxProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinProperties != nil {
in, out := &in.MinProperties, &out.MinProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.Required != nil {
in, out := &in.Required, &out.Required
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Items != nil {
in, out := &in.Items, &out.Items
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrArray)
(*in).DeepCopyInto(*out)
}
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.PatternProperties != nil {
in, out := &in.PatternProperties, &out.PatternProperties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(JSONSchemaDependencies, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.Definitions != nil {
in, out := &in.Definitions, &out.Definitions
*out = make(JSONSchemaDefinitions, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.ExternalDocs != nil {
in, out := &in.ExternalDocs, &out.ExternalDocs
if *in == nil {
*out = nil
} else {
*out = new(ExternalDocumentation)
(*in).DeepCopyInto(*out)
}
}
if in.XPreserveUnknownFields != nil {
in, out := &in.XPreserveUnknownFields, &out.XPreserveUnknownFields
if *in == nil {
*out = nil
} else {
*out = new(bool)
**out = **in
}
}
if in.XListMapKeys != nil {
in, out := &in.XListMapKeys, &out.XListMapKeys
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.XListType != nil {
in, out := &in.XListType, &out.XListType
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.XMapType != nil {
in, out := &in.XMapType, &out.XMapType
*out = new(string)
**out = **in
}
return out
}

View File

@@ -0,0 +1,82 @@
/*
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 v1beta1
import (
"strings"
"k8s.io/apimachinery/pkg/runtime"
utilpointer "k8s.io/utils/pointer"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}
func SetDefaults_CustomResourceDefinition(obj *CustomResourceDefinition) {
SetDefaults_CustomResourceDefinitionSpec(&obj.Spec)
if len(obj.Status.StoredVersions) == 0 {
for _, v := range obj.Spec.Versions {
if v.Storage {
obj.Status.StoredVersions = append(obj.Status.StoredVersions, v.Name)
break
}
}
}
}
func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec) {
if len(obj.Scope) == 0 {
obj.Scope = NamespaceScoped
}
if len(obj.Names.Singular) == 0 {
obj.Names.Singular = strings.ToLower(obj.Names.Kind)
}
if len(obj.Names.ListKind) == 0 && len(obj.Names.Kind) > 0 {
obj.Names.ListKind = obj.Names.Kind + "List"
}
// If there is no list of versions, create on using deprecated Version field.
if len(obj.Versions) == 0 && len(obj.Version) != 0 {
obj.Versions = []CustomResourceDefinitionVersion{{
Name: obj.Version,
Storage: true,
Served: true,
}}
}
// For backward compatibility set the version field to the first item in versions list.
if len(obj.Version) == 0 && len(obj.Versions) != 0 {
obj.Version = obj.Versions[0].Name
}
if obj.Conversion == nil {
obj.Conversion = &CustomResourceConversion{
Strategy: NoneConverter,
}
}
if obj.Conversion.Strategy == WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
obj.Conversion.ConversionReviewVersions = []string{SchemeGroupVersion.Version}
}
if obj.PreserveUnknownFields == nil {
obj.PreserveUnknownFields = utilpointer.BoolPtr(true)
}
}
// SetDefaults_ServiceReference sets defaults for Webhook's ServiceReference
func SetDefaults_ServiceReference(obj *ServiceReference) {
if obj.Port == nil {
obj.Port = utilpointer.Int32Ptr(443)
}
}

View File

@@ -0,0 +1,26 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// +k8s:protobuf-gen=package
// +k8s:conversion-gen=k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
// +k8s:defaulter-gen=TypeMeta
// +k8s:openapi-gen=true
// +k8s:prerelease-lifecycle-gen=true
// +groupName=apiextensions.k8s.io
// Package v1beta1 is the v1beta1 version of the API.
package v1beta1 // import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,686 @@
/*
Copyright 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.
*/
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = "proto2";
package k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1;
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1beta1";
// ConversionRequest describes the conversion request parameters.
message ConversionRequest {
// uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are
// otherwise identical (parallel requests, etc).
// The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
optional string uid = 1;
// desiredAPIVersion is the version to convert given objects to. e.g. "myapi.example.com/v1"
optional string desiredAPIVersion = 2;
// objects is the list of custom resource objects to be converted.
repeated k8s.io.apimachinery.pkg.runtime.RawExtension objects = 3;
}
// ConversionResponse describes a conversion response.
message ConversionResponse {
// uid is an identifier for the individual request/response.
// This should be copied over from the corresponding `request.uid`.
optional string uid = 1;
// convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty.
// The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list
// must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace).
// The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.
repeated k8s.io.apimachinery.pkg.runtime.RawExtension convertedObjects = 2;
// result contains the result of conversion with extra details if the conversion failed. `result.status` determines if
// the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the
// conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set
// `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message`
// will be used to construct an error message for the end user.
optional k8s.io.apimachinery.pkg.apis.meta.v1.Status result = 3;
}
// ConversionReview describes a conversion request/response.
message ConversionReview {
// request describes the attributes for the conversion request.
// +optional
optional ConversionRequest request = 1;
// response describes the attributes for the conversion response.
// +optional
optional ConversionResponse response = 2;
}
// CustomResourceColumnDefinition specifies a column for server side printing.
message CustomResourceColumnDefinition {
// name is a human readable name for the column.
optional string name = 1;
// type is an OpenAPI type definition for this column.
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
optional string type = 2;
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
// to the primary identifier column to assist in clients identifying column is the resource name.
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
// +optional
optional string format = 3;
// description is a human readable description of this column.
// +optional
optional string description = 4;
// priority is an integer defining the relative importance of this column compared to others. Lower
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
// should be given a priority greater than 0.
// +optional
optional int32 priority = 5;
// JSONPath is a simple JSON path (i.e. with array notation) which is evaluated against
// each custom resource to produce the value for this column.
optional string JSONPath = 6;
}
// CustomResourceConversion describes how to convert different versions of a CR.
message CustomResourceConversion {
// strategy specifies how custom resources are converted between versions. Allowed values are:
// - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource.
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhookClientConfig to be set.
optional string strategy = 1;
// webhookClientConfig is the instructions for how to call the webhook if strategy is `Webhook`.
// Required when `strategy` is set to `Webhook`.
// +optional
optional WebhookClientConfig webhookClientConfig = 2;
// conversionReviewVersions is an ordered list of preferred `ConversionReview`
// versions the Webhook expects. The API server will use the first version in
// the list which it supports. If none of the versions specified in this list
// are supported by API server, conversion will fail for the custom resource.
// If a persisted Webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail.
// Defaults to `["v1beta1"]`.
// +optional
repeated string conversionReviewVersions = 3;
}
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
// <.spec.name>.<.spec.group>.
// Deprecated in v1.16, planned for removal in v1.22. Use apiextensions.k8s.io/v1 CustomResourceDefinition instead.
message CustomResourceDefinition {
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
// spec describes how the user wants the resources to appear
optional CustomResourceDefinitionSpec spec = 2;
// status indicates the actual state of the CustomResourceDefinition
// +optional
optional CustomResourceDefinitionStatus status = 3;
}
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
message CustomResourceDefinitionCondition {
// type is the type of the condition. Types include Established, NamesAccepted and Terminating.
optional string type = 1;
// status is the status of the condition.
// Can be True, False, Unknown.
optional string status = 2;
// lastTransitionTime last time the condition transitioned from one status to another.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3;
// reason is a unique, one-word, CamelCase reason for the condition's last transition.
// +optional
optional string reason = 4;
// message is a human-readable message indicating details about last transition.
// +optional
optional string message = 5;
}
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
message CustomResourceDefinitionList {
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
// items list individual CustomResourceDefinition objects
repeated CustomResourceDefinition items = 2;
}
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
message CustomResourceDefinitionNames {
// plural is the plural name of the resource to serve.
// The custom resources are served under `/apis/<group>/<version>/.../<plural>`.
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
// Must be all lowercase.
optional string plural = 1;
// singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.
// +optional
optional string singular = 2;
// shortNames are short names for the resource, exposed in API discovery documents,
// and used by clients to support invocations like `kubectl get <shortname>`.
// It must be all lowercase.
// +optional
repeated string shortNames = 3;
// kind is the serialized kind of the resource. It is normally CamelCase and singular.
// Custom resource instances will use this value as the `kind` attribute in API calls.
optional string kind = 4;
// listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
// +optional
optional string listKind = 5;
// categories is a list of grouped resources this custom resource belongs to (e.g. 'all').
// This is published in API discovery documents, and used by clients to support invocations like
// `kubectl get all`.
// +optional
repeated string categories = 6;
}
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
message CustomResourceDefinitionSpec {
// group is the API group of the defined custom resource.
// The custom resources are served under `/apis/<group>/...`.
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
optional string group = 1;
// version is the API version of the defined custom resource.
// The custom resources are served under `/apis/<group>/<version>/...`.
// Must match the name of the first item in the `versions` list if `version` and `versions` are both specified.
// Optional if `versions` is specified.
// Deprecated: use `versions` instead.
// +optional
optional string version = 2;
// names specify the resource and kind names for the custom resource.
optional CustomResourceDefinitionNames names = 3;
// scope indicates whether the defined custom resource is cluster- or namespace-scoped.
// Allowed values are `Cluster` and `Namespaced`. Default is `Namespaced`.
optional string scope = 4;
// validation describes the schema used for validation and pruning of the custom resource.
// If present, this validation schema is used to validate all versions.
// Top-level and per-version schemas are mutually exclusive.
// +optional
optional CustomResourceValidation validation = 5;
// subresources specify what subresources the defined custom resource has.
// If present, this field configures subresources for all versions.
// Top-level and per-version subresources are mutually exclusive.
// +optional
optional CustomResourceSubresources subresources = 6;
// versions is the list of all API versions of the defined custom resource.
// Optional if `version` is specified.
// The name of the first item in the `versions` list must match the `version` field if `version` and `versions` are both specified.
// Version names are used to compute the order in which served versions are listed in API discovery.
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
// major version, then minor version. An example sorted list of versions:
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
// +optional
repeated CustomResourceDefinitionVersion versions = 7;
// additionalPrinterColumns specifies additional columns returned in Table output.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
// If present, this field configures columns for all versions.
// Top-level and per-version columns are mutually exclusive.
// If no top-level or per-version columns are specified, a single column displaying the age of the custom resource is used.
// +optional
repeated CustomResourceColumnDefinition additionalPrinterColumns = 8;
// conversion defines conversion settings for the CRD.
// +optional
optional CustomResourceConversion conversion = 9;
// preserveUnknownFields indicates that object fields which are not specified
// in the OpenAPI schema should be preserved when persisting to storage.
// apiVersion, kind, metadata and known fields inside metadata are always preserved.
// If false, schemas must be defined for all versions.
// Defaults to true in v1beta for backwards compatibility.
// Deprecated: will be required to be false in v1. Preservation of unknown fields can be specified
// in the validation schema using the `x-kubernetes-preserve-unknown-fields: true` extension.
// See https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#pruning-versus-preserving-unknown-fields for details.
// +optional
optional bool preserveUnknownFields = 10;
}
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
message CustomResourceDefinitionStatus {
// conditions indicate state for particular aspects of a CustomResourceDefinition
// +optional
repeated CustomResourceDefinitionCondition conditions = 1;
// acceptedNames are the names that are actually being used to serve discovery.
// They may be different than the names in spec.
// +optional
optional CustomResourceDefinitionNames acceptedNames = 2;
// storedVersions lists all versions of CustomResources that were ever persisted. Tracking these
// versions allows a migration path for stored versions in etcd. The field is mutable
// so a migration controller can finish a migration to another version (ensuring
// no old objects are left in storage), and then remove the rest of the
// versions from this list.
// Versions may not be removed from `spec.versions` while they exist in this list.
// +optional
repeated string storedVersions = 3;
}
// CustomResourceDefinitionVersion describes a version for CRD.
message CustomResourceDefinitionVersion {
// name is the version name, e.g. “v1”, “v2beta1”, etc.
// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.
optional string name = 1;
// served is a flag enabling/disabling this version from being served via REST APIs
optional bool served = 2;
// storage indicates this version should be used when persisting custom resources to storage.
// There must be exactly one version with storage=true.
optional bool storage = 3;
// deprecated indicates this version of the custom resource API is deprecated.
// When set to true, API requests to this version receive a warning header in the server response.
// Defaults to false.
// +optional
optional bool deprecated = 7;
// deprecationWarning overrides the default warning returned to API clients.
// May only be set when `deprecated` is true.
// The default warning indicates this version is deprecated and recommends use
// of the newest served version of equal or greater stability, if one exists.
// +optional
optional string deprecationWarning = 8;
// schema describes the schema used for validation and pruning of this version of the custom resource.
// Top-level and per-version schemas are mutually exclusive.
// Per-version schemas must not all be set to identical values (top-level validation schema should be used instead).
// +optional
optional CustomResourceValidation schema = 4;
// subresources specify what subresources this version of the defined custom resource have.
// Top-level and per-version subresources are mutually exclusive.
// Per-version subresources must not all be set to identical values (top-level subresources should be used instead).
// +optional
optional CustomResourceSubresources subresources = 5;
// additionalPrinterColumns specifies additional columns returned in Table output.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
// Top-level and per-version columns are mutually exclusive.
// Per-version columns must not all be set to identical values (top-level columns should be used instead).
// If no top-level or per-version columns are specified, a single column displaying the age of the custom resource is used.
// +optional
repeated CustomResourceColumnDefinition additionalPrinterColumns = 6;
}
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
message CustomResourceSubresourceScale {
// specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.spec`.
// If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.
optional string specReplicasPath = 1;
// statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.status`.
// If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource
// will default to 0.
optional string statusReplicasPath = 2;
// labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.status` or `.spec`.
// Must be set to work with HorizontalPodAutoscaler.
// The field pointed by this JSON path must be a string field (not a complex selector struct)
// which contains a serialized label selector in string form.
// More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource
// If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale`
// subresource will default to the empty string.
// +optional
optional string labelSelectorPath = 3;
}
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
// * exposes a /status subresource for the custom resource
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
message CustomResourceSubresourceStatus {
}
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
message CustomResourceSubresources {
// status indicates the custom resource should serve a `/status` subresource.
// When enabled:
// 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object.
// 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.
// +optional
optional CustomResourceSubresourceStatus status = 1;
// scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.
// +optional
optional CustomResourceSubresourceScale scale = 2;
}
// CustomResourceValidation is a list of validation methods for CustomResources.
message CustomResourceValidation {
// openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.
// +optional
optional JSONSchemaProps openAPIV3Schema = 1;
}
// ExternalDocumentation allows referencing an external resource for extended documentation.
message ExternalDocumentation {
optional string description = 1;
optional string url = 2;
}
// JSON represents any valid JSON value.
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
message JSON {
optional bytes raw = 1;
}
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
message JSONSchemaProps {
optional string id = 1;
optional string schema = 2;
optional string ref = 3;
optional string description = 4;
optional string type = 5;
// format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:
//
// - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
// - uri: an URI as parsed by Golang net/url.ParseRequestURI
// - email: an email address as parsed by Golang net/mail.ParseAddress
// - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
// - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
// - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
// - cidr: a CIDR as parsed by Golang net.ParseCIDR
// - mac: a MAC address as parsed by Golang net.ParseMAC
// - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
// - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
// - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
// - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
// - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
// - isbn10: an ISBN10 number string like "0321751043"
// - isbn13: an ISBN13 number string like "978-0321751041"
// - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
// - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
// - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
// - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
// - byte: base64 encoded binary data
// - password: any kind of string
// - date: a date string like "2006-01-02" as defined by full-date in RFC3339
// - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
// - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
optional string format = 6;
optional string title = 7;
// default is a default value for undefined object fields.
// Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
// CustomResourceDefinitions with defaults must be created using the v1 (or newer) CustomResourceDefinition API.
optional JSON default = 8;
optional double maximum = 9;
optional bool exclusiveMaximum = 10;
optional double minimum = 11;
optional bool exclusiveMinimum = 12;
optional int64 maxLength = 13;
optional int64 minLength = 14;
optional string pattern = 15;
optional int64 maxItems = 16;
optional int64 minItems = 17;
optional bool uniqueItems = 18;
optional double multipleOf = 19;
repeated JSON enum = 20;
optional int64 maxProperties = 21;
optional int64 minProperties = 22;
repeated string required = 23;
optional JSONSchemaPropsOrArray items = 24;
repeated JSONSchemaProps allOf = 25;
repeated JSONSchemaProps oneOf = 26;
repeated JSONSchemaProps anyOf = 27;
optional JSONSchemaProps not = 28;
map<string, JSONSchemaProps> properties = 29;
optional JSONSchemaPropsOrBool additionalProperties = 30;
map<string, JSONSchemaProps> patternProperties = 31;
map<string, JSONSchemaPropsOrStringArray> dependencies = 32;
optional JSONSchemaPropsOrBool additionalItems = 33;
map<string, JSONSchemaProps> definitions = 34;
optional ExternalDocumentation externalDocs = 35;
optional JSON example = 36;
optional bool nullable = 37;
// x-kubernetes-preserve-unknown-fields stops the API server
// decoding step from pruning fields which are not specified
// in the validation schema. This affects fields recursively,
// but switches back to normal pruning behaviour if nested
// properties or additionalProperties are specified in the schema.
// This can either be true or undefined. False is forbidden.
optional bool xKubernetesPreserveUnknownFields = 38;
// x-kubernetes-embedded-resource defines that the value is an
// embedded Kubernetes runtime.Object, with TypeMeta and
// ObjectMeta. The type must be object. It is allowed to further
// restrict the embedded object. kind, apiVersion and metadata
// are validated automatically. x-kubernetes-preserve-unknown-fields
// is allowed to be true, but does not have to be if the object
// is fully specified (up to kind, apiVersion, metadata).
optional bool xKubernetesEmbeddedResource = 39;
// x-kubernetes-int-or-string specifies that this value is
// either an integer or a string. If this is true, an empty
// type is allowed and type as child of anyOf is permitted
// if following one of the following patterns:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
optional bool xKubernetesIntOrString = 40;
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
// as the index of the map.
//
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
// extension set to "map". Also, the values specified for this attribute must
// be a scalar typed field of the child structure (no nesting is supported).
//
// The properties specified must either be required or have a default value,
// to ensure those properties are present for all list items.
//
// +optional
repeated string xKubernetesListMapKeys = 41;
// x-kubernetes-list-type annotates an array to further describe its topology.
// This extension must only be used on lists and may have 3 possible values:
//
// 1) `atomic`: the list is treated as a single entity, like a scalar.
// Atomic lists will be entirely replaced when updated. This extension
// may be used on any type of list (struct, scalar, ...).
// 2) `set`:
// Sets are lists that must not have multiple items with the same value. Each
// value must be a scalar, an object with x-kubernetes-map-type `atomic` or an
// array with x-kubernetes-list-type `atomic`.
// 3) `map`:
// These lists are like maps in that their elements have a non-index key
// used to identify them. Order is preserved upon merge. The map tag
// must only be used on a list with elements of type object.
// Defaults to atomic for arrays.
// +optional
optional string xKubernetesListType = 42;
// x-kubernetes-map-type annotates an object to further describe its topology.
// This extension must only be used when type is object and may have 2 possible values:
//
// 1) `granular`:
// These maps are actual maps (key-value pairs) and each fields are independent
// from each other (they can each be manipulated by separate actors). This is
// the default behaviour for all maps.
// 2) `atomic`: the list is treated as a single entity, like a scalar.
// Atomic maps will be entirely replaced when updated.
// +optional
optional string xKubernetesMapType = 43;
}
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
message JSONSchemaPropsOrArray {
optional JSONSchemaProps schema = 1;
repeated JSONSchemaProps jSONSchemas = 2;
}
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
// Defaults to true for the boolean property.
message JSONSchemaPropsOrBool {
optional bool allows = 1;
optional JSONSchemaProps schema = 2;
}
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
message JSONSchemaPropsOrStringArray {
optional JSONSchemaProps schema = 1;
repeated string property = 2;
}
// ServiceReference holds a reference to Service.legacy.k8s.io
message ServiceReference {
// namespace is the namespace of the service.
// Required
optional string namespace = 1;
// name is the name of the service.
// Required
optional string name = 2;
// path is an optional URL path at which the webhook will be contacted.
// +optional
optional string path = 3;
// port is an optional service port at which the webhook will be contacted.
// `port` should be a valid port number (1-65535, inclusive).
// Defaults to 443 for backward compatibility.
// +optional
optional int32 port = 4;
}
// WebhookClientConfig contains the information to make a TLS connection with the webhook.
message WebhookClientConfig {
// 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
optional string url = 3;
// 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`.
//
// +optional
optional ServiceReference service = 1;
// caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
// If unspecified, system trust roots on the apiserver are used.
// +optional
optional bytes caBundle = 2;
}

View File

@@ -0,0 +1,135 @@
/*
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 v1beta1
import (
"errors"
"k8s.io/apimachinery/pkg/util/json"
)
var jsTrue = []byte("true")
var jsFalse = []byte("false")
func (s JSONSchemaPropsOrBool) MarshalJSON() ([]byte, error) {
if s.Schema != nil {
return json.Marshal(s.Schema)
}
if s.Schema == nil && !s.Allows {
return jsFalse, nil
}
return jsTrue, nil
}
func (s *JSONSchemaPropsOrBool) UnmarshalJSON(data []byte) error {
var nw JSONSchemaPropsOrBool
switch {
case len(data) == 0:
case data[0] == '{':
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Allows = true
nw.Schema = &sch
case len(data) == 4 && string(data) == "true":
nw.Allows = true
case len(data) == 5 && string(data) == "false":
nw.Allows = false
default:
return errors.New("boolean or JSON schema expected")
}
*s = nw
return nil
}
func (s JSONSchemaPropsOrStringArray) MarshalJSON() ([]byte, error) {
if len(s.Property) > 0 {
return json.Marshal(s.Property)
}
if s.Schema != nil {
return json.Marshal(s.Schema)
}
return []byte("null"), nil
}
func (s *JSONSchemaPropsOrStringArray) UnmarshalJSON(data []byte) error {
var first byte
if len(data) > 1 {
first = data[0]
}
var nw JSONSchemaPropsOrStringArray
if first == '{' {
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
}
if first == '[' {
if err := json.Unmarshal(data, &nw.Property); err != nil {
return err
}
}
*s = nw
return nil
}
func (s JSONSchemaPropsOrArray) MarshalJSON() ([]byte, error) {
if len(s.JSONSchemas) > 0 {
return json.Marshal(s.JSONSchemas)
}
return json.Marshal(s.Schema)
}
func (s *JSONSchemaPropsOrArray) UnmarshalJSON(data []byte) error {
var nw JSONSchemaPropsOrArray
var first byte
if len(data) > 1 {
first = data[0]
}
if first == '{' {
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
}
if first == '[' {
if err := json.Unmarshal(data, &nw.JSONSchemas); err != nil {
return err
}
}
*s = nw
return nil
}
func (s JSON) MarshalJSON() ([]byte, error) {
if len(s.Raw) > 0 {
return s.Raw, nil
}
return []byte("null"), nil
}
func (s *JSON) UnmarshalJSON(data []byte) error {
if len(data) > 0 && string(data) != "null" {
s.Raw = data
}
return nil
}

View File

@@ -0,0 +1,62 @@
/*
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 v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "apiextensions.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CustomResourceDefinition{},
&CustomResourceDefinitionList{},
&ConversionReview{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addDefaultingFuncs)
}

View File

@@ -0,0 +1,522 @@
/*
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 v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
// ConversionStrategyType describes different conversion types.
type ConversionStrategyType string
const (
// KubeAPIApprovedAnnotation is an annotation that must be set to create a CRD for the k8s.io, *.k8s.io, kubernetes.io, or *.kubernetes.io namespaces.
// The value should be a link to a URL where the current spec was approved, so updates to the spec should also update the URL.
// If the API is unapproved, you may set the annotation to a string starting with `"unapproved"`. For instance, `"unapproved, temporarily squatting"` or `"unapproved, experimental-only"`. This is discouraged.
KubeAPIApprovedAnnotation = "api-approved.kubernetes.io"
// NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged.
NoneConverter ConversionStrategyType = "None"
// WebhookConverter is a converter that calls to an external webhook to convert the CR.
WebhookConverter ConversionStrategyType = "Webhook"
)
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
type CustomResourceDefinitionSpec struct {
// group is the API group of the defined custom resource.
// The custom resources are served under `/apis/<group>/...`.
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
Group string `json:"group" protobuf:"bytes,1,opt,name=group"`
// version is the API version of the defined custom resource.
// The custom resources are served under `/apis/<group>/<version>/...`.
// Must match the name of the first item in the `versions` list if `version` and `versions` are both specified.
// Optional if `versions` is specified.
// Deprecated: use `versions` instead.
// +optional
Version string `json:"version,omitempty" protobuf:"bytes,2,opt,name=version"`
// names specify the resource and kind names for the custom resource.
Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"`
// scope indicates whether the defined custom resource is cluster- or namespace-scoped.
// Allowed values are `Cluster` and `Namespaced`. Default is `Namespaced`.
Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"`
// validation describes the schema used for validation and pruning of the custom resource.
// If present, this validation schema is used to validate all versions.
// Top-level and per-version schemas are mutually exclusive.
// +optional
Validation *CustomResourceValidation `json:"validation,omitempty" protobuf:"bytes,5,opt,name=validation"`
// subresources specify what subresources the defined custom resource has.
// If present, this field configures subresources for all versions.
// Top-level and per-version subresources are mutually exclusive.
// +optional
Subresources *CustomResourceSubresources `json:"subresources,omitempty" protobuf:"bytes,6,opt,name=subresources"`
// versions is the list of all API versions of the defined custom resource.
// Optional if `version` is specified.
// The name of the first item in the `versions` list must match the `version` field if `version` and `versions` are both specified.
// Version names are used to compute the order in which served versions are listed in API discovery.
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
// major version, then minor version. An example sorted list of versions:
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
// +optional
Versions []CustomResourceDefinitionVersion `json:"versions,omitempty" protobuf:"bytes,7,rep,name=versions"`
// additionalPrinterColumns specifies additional columns returned in Table output.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
// If present, this field configures columns for all versions.
// Top-level and per-version columns are mutually exclusive.
// If no top-level or per-version columns are specified, a single column displaying the age of the custom resource is used.
// +optional
AdditionalPrinterColumns []CustomResourceColumnDefinition `json:"additionalPrinterColumns,omitempty" protobuf:"bytes,8,rep,name=additionalPrinterColumns"`
// conversion defines conversion settings for the CRD.
// +optional
Conversion *CustomResourceConversion `json:"conversion,omitempty" protobuf:"bytes,9,opt,name=conversion"`
// preserveUnknownFields indicates that object fields which are not specified
// in the OpenAPI schema should be preserved when persisting to storage.
// apiVersion, kind, metadata and known fields inside metadata are always preserved.
// If false, schemas must be defined for all versions.
// Defaults to true in v1beta for backwards compatibility.
// Deprecated: will be required to be false in v1. Preservation of unknown fields can be specified
// in the validation schema using the `x-kubernetes-preserve-unknown-fields: true` extension.
// See https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#pruning-versus-preserving-unknown-fields for details.
// +optional
PreserveUnknownFields *bool `json:"preserveUnknownFields,omitempty" protobuf:"varint,10,opt,name=preserveUnknownFields"`
}
// CustomResourceConversion describes how to convert different versions of a CR.
type CustomResourceConversion struct {
// strategy specifies how custom resources are converted between versions. Allowed values are:
// - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource.
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhookClientConfig to be set.
Strategy ConversionStrategyType `json:"strategy" protobuf:"bytes,1,name=strategy"`
// webhookClientConfig is the instructions for how to call the webhook if strategy is `Webhook`.
// Required when `strategy` is set to `Webhook`.
// +optional
WebhookClientConfig *WebhookClientConfig `json:"webhookClientConfig,omitempty" protobuf:"bytes,2,name=webhookClientConfig"`
// conversionReviewVersions is an ordered list of preferred `ConversionReview`
// versions the Webhook expects. The API server will use the first version in
// the list which it supports. If none of the versions specified in this list
// are supported by API server, conversion will fail for the custom resource.
// If a persisted Webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail.
// Defaults to `["v1beta1"]`.
// +optional
ConversionReviewVersions []string `json:"conversionReviewVersions,omitempty" protobuf:"bytes,3,rep,name=conversionReviewVersions"`
}
// 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`.
//
// +optional
Service *ServiceReference `json:"service,omitempty" protobuf:"bytes,1,opt,name=service"`
// caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
// If unspecified, system trust roots on the apiserver are used.
// +optional
CABundle []byte `json:"caBundle,omitempty" 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 at which the webhook will be contacted.
// +optional
Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"`
// port is an optional service port at which the webhook will be contacted.
// `port` should be a valid port number (1-65535, inclusive).
// Defaults to 443 for backward compatibility.
// +optional
Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"`
}
// CustomResourceDefinitionVersion describes a version for CRD.
type CustomResourceDefinitionVersion struct {
// name is the version name, e.g. “v1”, “v2beta1”, etc.
// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// served is a flag enabling/disabling this version from being served via REST APIs
Served bool `json:"served" protobuf:"varint,2,opt,name=served"`
// storage indicates this version should be used when persisting custom resources to storage.
// There must be exactly one version with storage=true.
Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"`
// deprecated indicates this version of the custom resource API is deprecated.
// When set to true, API requests to this version receive a warning header in the server response.
// Defaults to false.
// +optional
Deprecated bool `json:"deprecated,omitempty" protobuf:"varint,7,opt,name=deprecated"`
// deprecationWarning overrides the default warning returned to API clients.
// May only be set when `deprecated` is true.
// The default warning indicates this version is deprecated and recommends use
// of the newest served version of equal or greater stability, if one exists.
// +optional
DeprecationWarning *string `json:"deprecationWarning,omitempty" protobuf:"bytes,8,opt,name=deprecationWarning"`
// schema describes the schema used for validation and pruning of this version of the custom resource.
// Top-level and per-version schemas are mutually exclusive.
// Per-version schemas must not all be set to identical values (top-level validation schema should be used instead).
// +optional
Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
// subresources specify what subresources this version of the defined custom resource have.
// Top-level and per-version subresources are mutually exclusive.
// Per-version subresources must not all be set to identical values (top-level subresources should be used instead).
// +optional
Subresources *CustomResourceSubresources `json:"subresources,omitempty" protobuf:"bytes,5,opt,name=subresources"`
// additionalPrinterColumns specifies additional columns returned in Table output.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
// Top-level and per-version columns are mutually exclusive.
// Per-version columns must not all be set to identical values (top-level columns should be used instead).
// If no top-level or per-version columns are specified, a single column displaying the age of the custom resource is used.
// +optional
AdditionalPrinterColumns []CustomResourceColumnDefinition `json:"additionalPrinterColumns,omitempty" protobuf:"bytes,6,rep,name=additionalPrinterColumns"`
}
// CustomResourceColumnDefinition specifies a column for server side printing.
type CustomResourceColumnDefinition struct {
// name is a human readable name for the column.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// type is an OpenAPI type definition for this column.
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
Type string `json:"type" protobuf:"bytes,2,opt,name=type"`
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
// to the primary identifier column to assist in clients identifying column is the resource name.
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
// +optional
Format string `json:"format,omitempty" protobuf:"bytes,3,opt,name=format"`
// description is a human readable description of this column.
// +optional
Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"`
// priority is an integer defining the relative importance of this column compared to others. Lower
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
// should be given a priority greater than 0.
// +optional
Priority int32 `json:"priority,omitempty" protobuf:"bytes,5,opt,name=priority"`
// JSONPath is a simple JSON path (i.e. with array notation) which is evaluated against
// each custom resource to produce the value for this column.
JSONPath string `json:"JSONPath" protobuf:"bytes,6,opt,name=JSONPath"`
}
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
type CustomResourceDefinitionNames struct {
// plural is the plural name of the resource to serve.
// The custom resources are served under `/apis/<group>/<version>/.../<plural>`.
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
// Must be all lowercase.
Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"`
// singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.
// +optional
Singular string `json:"singular,omitempty" protobuf:"bytes,2,opt,name=singular"`
// shortNames are short names for the resource, exposed in API discovery documents,
// and used by clients to support invocations like `kubectl get <shortname>`.
// It must be all lowercase.
// +optional
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,3,opt,name=shortNames"`
// kind is the serialized kind of the resource. It is normally CamelCase and singular.
// Custom resource instances will use this value as the `kind` attribute in API calls.
Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"`
// listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
// +optional
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"`
// categories is a list of grouped resources this custom resource belongs to (e.g. 'all').
// This is published in API discovery documents, and used by clients to support invocations like
// `kubectl get all`.
// +optional
Categories []string `json:"categories,omitempty" protobuf:"bytes,6,rep,name=categories"`
}
// ResourceScope is an enum defining the different scopes available to a custom resource
type ResourceScope string
const (
ClusterScoped ResourceScope = "Cluster"
NamespaceScoped ResourceScope = "Namespaced"
)
type ConditionStatus string
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
ConditionTrue ConditionStatus = "True"
ConditionFalse ConditionStatus = "False"
ConditionUnknown ConditionStatus = "Unknown"
)
// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type
type CustomResourceDefinitionConditionType string
const (
// Established means that the resource has become active. A resource is established when all names are
// accepted without a conflict for the first time. A resource stays established until deleted, even during
// a later NamesAccepted due to changed names. Note that not all names can be changed.
Established CustomResourceDefinitionConditionType = "Established"
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
// the group and are therefore accepted.
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
// NonStructuralSchema means that one or more OpenAPI schema is not structural.
//
// A schema is structural if it specifies types for all values, with the only exceptions of those with
// - x-kubernetes-int-or-string: true — for fields which can be integer or string
// - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values
// and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions
// specified under allOf, anyOf, oneOf or not.
//
// Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be
// available for non-structural CRDs:
// - pruning
// - defaulting
// - read-only
// - OpenAPI publishing
// - webhook conversion
NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema"
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
Terminating CustomResourceDefinitionConditionType = "Terminating"
// KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs
// outside those groups, this condition will not be set. For CRDs inside those groups, the condition will
// be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false.
// See https://github.com/kubernetes/enhancements/pull/1111 for more details.
KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant"
)
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
type CustomResourceDefinitionCondition struct {
// type is the type of the condition. Types include Established, NamesAccepted and Terminating.
Type CustomResourceDefinitionConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType"`
// status is the status of the condition.
// Can be True, False, Unknown.
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
// lastTransitionTime last time the condition transitioned from one status to another.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
// reason is a unique, one-word, CamelCase reason for the condition's last transition.
// +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
// message is a human-readable message indicating details about last transition.
// +optional
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
}
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
type CustomResourceDefinitionStatus struct {
// conditions indicate state for particular aspects of a CustomResourceDefinition
// +optional
Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"`
// acceptedNames are the names that are actually being used to serve discovery.
// They may be different than the names in spec.
// +optional
AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"`
// storedVersions lists all versions of CustomResources that were ever persisted. Tracking these
// versions allows a migration path for stored versions in etcd. The field is mutable
// so a migration controller can finish a migration to another version (ensuring
// no old objects are left in storage), and then remove the rest of the
// versions from this list.
// Versions may not be removed from `spec.versions` while they exist in this list.
// +optional
StoredVersions []string `json:"storedVersions" protobuf:"bytes,3,rep,name=storedVersions"`
}
// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of
// a CustomResourceDefinition
const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io"
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.7
// +k8s:prerelease-lifecycle-gen:deprecated=1.16
// +k8s:prerelease-lifecycle-gen:removed=1.22
// +k8s:prerelease-lifecycle-gen:replacement=apiextensions.k8s.io,v1,CustomResourceDefinition
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
// <.spec.name>.<.spec.group>.
// Deprecated in v1.16, planned for removal in v1.22. Use apiextensions.k8s.io/v1 CustomResourceDefinition instead.
type CustomResourceDefinition struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// spec describes how the user wants the resources to appear
Spec CustomResourceDefinitionSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
// status indicates the actual state of the CustomResourceDefinition
// +optional
Status CustomResourceDefinitionStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.7
// +k8s:prerelease-lifecycle-gen:deprecated=1.16
// +k8s:prerelease-lifecycle-gen:removed=1.22
// +k8s:prerelease-lifecycle-gen:replacement=apiextensions.k8s.io,v1,CustomResourceDefinitionList
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
type CustomResourceDefinitionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// items list individual CustomResourceDefinition objects
Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// CustomResourceValidation is a list of validation methods for CustomResources.
type CustomResourceValidation struct {
// openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.
// +optional
OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"`
}
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
type CustomResourceSubresources struct {
// status indicates the custom resource should serve a `/status` subresource.
// When enabled:
// 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object.
// 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.
// +optional
Status *CustomResourceSubresourceStatus `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"`
// scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.
// +optional
Scale *CustomResourceSubresourceScale `json:"scale,omitempty" protobuf:"bytes,2,opt,name=scale"`
}
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
// * exposes a /status subresource for the custom resource
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
type CustomResourceSubresourceStatus struct{}
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
type CustomResourceSubresourceScale struct {
// specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.spec`.
// If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.
SpecReplicasPath string `json:"specReplicasPath" protobuf:"bytes,1,name=specReplicasPath"`
// statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.status`.
// If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource
// will default to 0.
StatusReplicasPath string `json:"statusReplicasPath" protobuf:"bytes,2,opt,name=statusReplicasPath"`
// labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`.
// Only JSON paths without the array notation are allowed.
// Must be a JSON Path under `.status` or `.spec`.
// Must be set to work with HorizontalPodAutoscaler.
// The field pointed by this JSON path must be a string field (not a complex selector struct)
// which contains a serialized label selector in string form.
// More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource
// If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale`
// subresource will default to the empty string.
// +optional
LabelSelectorPath *string `json:"labelSelectorPath,omitempty" protobuf:"bytes,3,opt,name=labelSelectorPath"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.13
// +k8s:prerelease-lifecycle-gen:deprecated=1.19
// This API is never served. It is used for outbound requests from apiservers. This will ensure it never gets served accidentally
// and having the generator against this group will protect future APIs which may be served.
// +k8s:prerelease-lifecycle-gen:replacement=apiextensions.k8s.io,v1,ConversionReview
// ConversionReview describes a conversion request/response.
type ConversionReview struct {
metav1.TypeMeta `json:",inline"`
// request describes the attributes for the conversion request.
// +optional
Request *ConversionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`
// response describes the attributes for the conversion response.
// +optional
Response *ConversionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
}
// ConversionRequest describes the conversion request parameters.
type ConversionRequest struct {
// uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are
// otherwise identical (parallel requests, etc).
// The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
UID types.UID `json:"uid" protobuf:"bytes,1,name=uid"`
// desiredAPIVersion is the version to convert given objects to. e.g. "myapi.example.com/v1"
DesiredAPIVersion string `json:"desiredAPIVersion" protobuf:"bytes,2,name=desiredAPIVersion"`
// objects is the list of custom resource objects to be converted.
Objects []runtime.RawExtension `json:"objects" protobuf:"bytes,3,rep,name=objects"`
}
// ConversionResponse describes a conversion response.
type ConversionResponse struct {
// uid is an identifier for the individual request/response.
// This should be copied over from the corresponding `request.uid`.
UID types.UID `json:"uid" protobuf:"bytes,1,name=uid"`
// convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty.
// The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list
// must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace).
// The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.
ConvertedObjects []runtime.RawExtension `json:"convertedObjects" protobuf:"bytes,2,rep,name=convertedObjects"`
// result contains the result of conversion with extra details if the conversion failed. `result.status` determines if
// the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the
// conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set
// `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message`
// will be used to construct an error message for the end user.
Result metav1.Status `json:"result" protobuf:"bytes,3,name=result"`
}

View File

@@ -0,0 +1,257 @@
/*
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 v1beta1
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
type JSONSchemaProps struct {
ID string `json:"id,omitempty" protobuf:"bytes,1,opt,name=id"`
Schema JSONSchemaURL `json:"$schema,omitempty" protobuf:"bytes,2,opt,name=schema"`
Ref *string `json:"$ref,omitempty" protobuf:"bytes,3,opt,name=ref"`
Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"`
Type string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"`
// format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:
//
// - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
// - uri: an URI as parsed by Golang net/url.ParseRequestURI
// - email: an email address as parsed by Golang net/mail.ParseAddress
// - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
// - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
// - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
// - cidr: a CIDR as parsed by Golang net.ParseCIDR
// - mac: a MAC address as parsed by Golang net.ParseMAC
// - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
// - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
// - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
// - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
// - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
// - isbn10: an ISBN10 number string like "0321751043"
// - isbn13: an ISBN13 number string like "978-0321751041"
// - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
// - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
// - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
// - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
// - byte: base64 encoded binary data
// - password: any kind of string
// - date: a date string like "2006-01-02" as defined by full-date in RFC3339
// - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
// - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
Format string `json:"format,omitempty" protobuf:"bytes,6,opt,name=format"`
Title string `json:"title,omitempty" protobuf:"bytes,7,opt,name=title"`
// default is a default value for undefined object fields.
// Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
// CustomResourceDefinitions with defaults must be created using the v1 (or newer) CustomResourceDefinition API.
Default *JSON `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"`
Maximum *float64 `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty" protobuf:"bytes,10,opt,name=exclusiveMaximum"`
Minimum *float64 `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty" protobuf:"bytes,12,opt,name=exclusiveMinimum"`
MaxLength *int64 `json:"maxLength,omitempty" protobuf:"bytes,13,opt,name=maxLength"`
MinLength *int64 `json:"minLength,omitempty" protobuf:"bytes,14,opt,name=minLength"`
Pattern string `json:"pattern,omitempty" protobuf:"bytes,15,opt,name=pattern"`
MaxItems *int64 `json:"maxItems,omitempty" protobuf:"bytes,16,opt,name=maxItems"`
MinItems *int64 `json:"minItems,omitempty" protobuf:"bytes,17,opt,name=minItems"`
UniqueItems bool `json:"uniqueItems,omitempty" protobuf:"bytes,18,opt,name=uniqueItems"`
MultipleOf *float64 `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"`
Enum []JSON `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"`
MaxProperties *int64 `json:"maxProperties,omitempty" protobuf:"bytes,21,opt,name=maxProperties"`
MinProperties *int64 `json:"minProperties,omitempty" protobuf:"bytes,22,opt,name=minProperties"`
Required []string `json:"required,omitempty" protobuf:"bytes,23,rep,name=required"`
Items *JSONSchemaPropsOrArray `json:"items,omitempty" protobuf:"bytes,24,opt,name=items"`
AllOf []JSONSchemaProps `json:"allOf,omitempty" protobuf:"bytes,25,rep,name=allOf"`
OneOf []JSONSchemaProps `json:"oneOf,omitempty" protobuf:"bytes,26,rep,name=oneOf"`
AnyOf []JSONSchemaProps `json:"anyOf,omitempty" protobuf:"bytes,27,rep,name=anyOf"`
Not *JSONSchemaProps `json:"not,omitempty" protobuf:"bytes,28,opt,name=not"`
Properties map[string]JSONSchemaProps `json:"properties,omitempty" protobuf:"bytes,29,rep,name=properties"`
AdditionalProperties *JSONSchemaPropsOrBool `json:"additionalProperties,omitempty" protobuf:"bytes,30,opt,name=additionalProperties"`
PatternProperties map[string]JSONSchemaProps `json:"patternProperties,omitempty" protobuf:"bytes,31,rep,name=patternProperties"`
Dependencies JSONSchemaDependencies `json:"dependencies,omitempty" protobuf:"bytes,32,opt,name=dependencies"`
AdditionalItems *JSONSchemaPropsOrBool `json:"additionalItems,omitempty" protobuf:"bytes,33,opt,name=additionalItems"`
Definitions JSONSchemaDefinitions `json:"definitions,omitempty" protobuf:"bytes,34,opt,name=definitions"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"`
Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"`
Nullable bool `json:"nullable,omitempty" protobuf:"bytes,37,opt,name=nullable"`
// x-kubernetes-preserve-unknown-fields stops the API server
// decoding step from pruning fields which are not specified
// in the validation schema. This affects fields recursively,
// but switches back to normal pruning behaviour if nested
// properties or additionalProperties are specified in the schema.
// This can either be true or undefined. False is forbidden.
XPreserveUnknownFields *bool `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"`
// x-kubernetes-embedded-resource defines that the value is an
// embedded Kubernetes runtime.Object, with TypeMeta and
// ObjectMeta. The type must be object. It is allowed to further
// restrict the embedded object. kind, apiVersion and metadata
// are validated automatically. x-kubernetes-preserve-unknown-fields
// is allowed to be true, but does not have to be if the object
// is fully specified (up to kind, apiVersion, metadata).
XEmbeddedResource bool `json:"x-kubernetes-embedded-resource,omitempty" protobuf:"bytes,39,opt,name=xKubernetesEmbeddedResource"`
// x-kubernetes-int-or-string specifies that this value is
// either an integer or a string. If this is true, an empty
// type is allowed and type as child of anyOf is permitted
// if following one of the following patterns:
//
// 1) anyOf:
// - type: integer
// - type: string
// 2) allOf:
// - anyOf:
// - type: integer
// - type: string
// - ... zero or more
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
// as the index of the map.
//
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
// extension set to "map". Also, the values specified for this attribute must
// be a scalar typed field of the child structure (no nesting is supported).
//
// The properties specified must either be required or have a default value,
// to ensure those properties are present for all list items.
//
// +optional
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
// x-kubernetes-list-type annotates an array to further describe its topology.
// This extension must only be used on lists and may have 3 possible values:
//
// 1) `atomic`: the list is treated as a single entity, like a scalar.
// Atomic lists will be entirely replaced when updated. This extension
// may be used on any type of list (struct, scalar, ...).
// 2) `set`:
// Sets are lists that must not have multiple items with the same value. Each
// value must be a scalar, an object with x-kubernetes-map-type `atomic` or an
// array with x-kubernetes-list-type `atomic`.
// 3) `map`:
// These lists are like maps in that their elements have a non-index key
// used to identify them. Order is preserved upon merge. The map tag
// must only be used on a list with elements of type object.
// Defaults to atomic for arrays.
// +optional
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
// x-kubernetes-map-type annotates an object to further describe its topology.
// This extension must only be used when type is object and may have 2 possible values:
//
// 1) `granular`:
// These maps are actual maps (key-value pairs) and each fields are independent
// from each other (they can each be manipulated by separate actors). This is
// the default behaviour for all maps.
// 2) `atomic`: the list is treated as a single entity, like a scalar.
// Atomic maps will be entirely replaced when updated.
// +optional
XMapType *string `json:"x-kubernetes-map-type,omitempty" protobuf:"bytes,43,opt,name=xKubernetesMapType"`
}
// JSON represents any valid JSON value.
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
type JSON struct {
Raw []byte `protobuf:"bytes,1,opt,name=raw"`
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSON) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
func (_ JSON) OpenAPISchemaFormat() string { return "" }
// JSONSchemaURL represents a schema url.
type JSONSchemaURL string
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
type JSONSchemaPropsOrArray struct {
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
JSONSchemas []JSONSchemaProps `protobuf:"bytes,2,rep,name=jSONSchemas"`
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrArray) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
func (_ JSONSchemaPropsOrArray) OpenAPISchemaFormat() string { return "" }
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
// Defaults to true for the boolean property.
type JSONSchemaPropsOrBool struct {
Allows bool `protobuf:"varint,1,opt,name=allows"`
Schema *JSONSchemaProps `protobuf:"bytes,2,opt,name=schema"`
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrBool) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
func (_ JSONSchemaPropsOrBool) OpenAPISchemaFormat() string { return "" }
// JSONSchemaDependencies represent a dependencies property.
type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
type JSONSchemaPropsOrStringArray struct {
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
Property []string `protobuf:"bytes,2,rep,name=property"`
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaFormat() string { return "" }
// JSONSchemaDefinitions contains the models explicitly defined in this spec.
type JSONSchemaDefinitions map[string]JSONSchemaProps
// ExternalDocumentation allows referencing an external resource for extended documentation.
type ExternalDocumentation struct {
Description string `json:"description,omitempty" protobuf:"bytes,1,opt,name=description"`
URL string `json:"url,omitempty" protobuf:"bytes,2,opt,name=url"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConversionRequest) DeepCopyInto(out *ConversionRequest) {
*out = *in
if in.Objects != nil {
in, out := &in.Objects, &out.Objects
*out = make([]runtime.RawExtension, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionRequest.
func (in *ConversionRequest) DeepCopy() *ConversionRequest {
if in == nil {
return nil
}
out := new(ConversionRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConversionResponse) DeepCopyInto(out *ConversionResponse) {
*out = *in
if in.ConvertedObjects != nil {
in, out := &in.ConvertedObjects, &out.ConvertedObjects
*out = make([]runtime.RawExtension, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Result.DeepCopyInto(&out.Result)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionResponse.
func (in *ConversionResponse) DeepCopy() *ConversionResponse {
if in == nil {
return nil
}
out := new(ConversionResponse)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConversionReview) DeepCopyInto(out *ConversionReview) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Request != nil {
in, out := &in.Request, &out.Request
*out = new(ConversionRequest)
(*in).DeepCopyInto(*out)
}
if in.Response != nil {
in, out := &in.Response, &out.Response
*out = new(ConversionResponse)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionReview.
func (in *ConversionReview) DeepCopy() *ConversionReview {
if in == nil {
return nil
}
out := new(ConversionReview)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ConversionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceColumnDefinition) DeepCopyInto(out *CustomResourceColumnDefinition) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceColumnDefinition.
func (in *CustomResourceColumnDefinition) DeepCopy() *CustomResourceColumnDefinition {
if in == nil {
return nil
}
out := new(CustomResourceColumnDefinition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion) {
*out = *in
if in.WebhookClientConfig != nil {
in, out := &in.WebhookClientConfig, &out.WebhookClientConfig
*out = new(WebhookClientConfig)
(*in).DeepCopyInto(*out)
}
if in.ConversionReviewVersions != nil {
in, out := &in.ConversionReviewVersions, &out.ConversionReviewVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceConversion.
func (in *CustomResourceConversion) DeepCopy() *CustomResourceConversion {
if in == nil {
return nil
}
out := new(CustomResourceConversion)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinition.
func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
if in == nil {
return nil
}
out := new(CustomResourceDefinition)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomResourceDefinition) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionCondition) DeepCopyInto(out *CustomResourceDefinitionCondition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionCondition.
func (in *CustomResourceDefinitionCondition) DeepCopy() *CustomResourceDefinitionCondition {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionList) DeepCopyInto(out *CustomResourceDefinitionList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CustomResourceDefinition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionList.
func (in *CustomResourceDefinitionList) DeepCopy() *CustomResourceDefinitionList {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomResourceDefinitionList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinitionNames) {
*out = *in
if in.ShortNames != nil {
in, out := &in.ShortNames, &out.ShortNames
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Categories != nil {
in, out := &in.Categories, &out.Categories
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionNames.
func (in *CustomResourceDefinitionNames) DeepCopy() *CustomResourceDefinitionNames {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionNames)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) {
*out = *in
in.Names.DeepCopyInto(&out.Names)
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
*out = new(CustomResourceValidation)
(*in).DeepCopyInto(*out)
}
if in.Subresources != nil {
in, out := &in.Subresources, &out.Subresources
*out = new(CustomResourceSubresources)
(*in).DeepCopyInto(*out)
}
if in.Versions != nil {
in, out := &in.Versions, &out.Versions
*out = make([]CustomResourceDefinitionVersion, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AdditionalPrinterColumns != nil {
in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
*out = make([]CustomResourceColumnDefinition, len(*in))
copy(*out, *in)
}
if in.Conversion != nil {
in, out := &in.Conversion, &out.Conversion
*out = new(CustomResourceConversion)
(*in).DeepCopyInto(*out)
}
if in.PreserveUnknownFields != nil {
in, out := &in.PreserveUnknownFields, &out.PreserveUnknownFields
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionSpec.
func (in *CustomResourceDefinitionSpec) DeepCopy() *CustomResourceDefinitionSpec {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionStatus) DeepCopyInto(out *CustomResourceDefinitionStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]CustomResourceDefinitionCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.AcceptedNames.DeepCopyInto(&out.AcceptedNames)
if in.StoredVersions != nil {
in, out := &in.StoredVersions, &out.StoredVersions
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionStatus.
func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionStatus {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceDefinitionVersion) DeepCopyInto(out *CustomResourceDefinitionVersion) {
*out = *in
if in.DeprecationWarning != nil {
in, out := &in.DeprecationWarning, &out.DeprecationWarning
*out = new(string)
**out = **in
}
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(CustomResourceValidation)
(*in).DeepCopyInto(*out)
}
if in.Subresources != nil {
in, out := &in.Subresources, &out.Subresources
*out = new(CustomResourceSubresources)
(*in).DeepCopyInto(*out)
}
if in.AdditionalPrinterColumns != nil {
in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
*out = make([]CustomResourceColumnDefinition, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionVersion.
func (in *CustomResourceDefinitionVersion) DeepCopy() *CustomResourceDefinitionVersion {
if in == nil {
return nil
}
out := new(CustomResourceDefinitionVersion)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceSubresourceScale) DeepCopyInto(out *CustomResourceSubresourceScale) {
*out = *in
if in.LabelSelectorPath != nil {
in, out := &in.LabelSelectorPath, &out.LabelSelectorPath
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceScale.
func (in *CustomResourceSubresourceScale) DeepCopy() *CustomResourceSubresourceScale {
if in == nil {
return nil
}
out := new(CustomResourceSubresourceScale)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceSubresourceStatus) DeepCopyInto(out *CustomResourceSubresourceStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceStatus.
func (in *CustomResourceSubresourceStatus) DeepCopy() *CustomResourceSubresourceStatus {
if in == nil {
return nil
}
out := new(CustomResourceSubresourceStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceSubresources) DeepCopyInto(out *CustomResourceSubresources) {
*out = *in
if in.Status != nil {
in, out := &in.Status, &out.Status
*out = new(CustomResourceSubresourceStatus)
**out = **in
}
if in.Scale != nil {
in, out := &in.Scale, &out.Scale
*out = new(CustomResourceSubresourceScale)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresources.
func (in *CustomResourceSubresources) DeepCopy() *CustomResourceSubresources {
if in == nil {
return nil
}
out := new(CustomResourceSubresources)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) {
*out = *in
if in.OpenAPIV3Schema != nil {
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceValidation.
func (in *CustomResourceValidation) DeepCopy() *CustomResourceValidation {
if in == nil {
return nil
}
out := new(CustomResourceValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
if in == nil {
return nil
}
out := new(ExternalDocumentation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSON) DeepCopyInto(out *JSON) {
*out = *in
if in.Raw != nil {
in, out := &in.Raw, &out.Raw
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSON.
func (in *JSON) DeepCopy() *JSON {
if in == nil {
return nil
}
out := new(JSON)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in JSONSchemaDefinitions) DeepCopyInto(out *JSONSchemaDefinitions) {
{
in := &in
*out = make(JSONSchemaDefinitions, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDefinitions.
func (in JSONSchemaDefinitions) DeepCopy() JSONSchemaDefinitions {
if in == nil {
return nil
}
out := new(JSONSchemaDefinitions)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in JSONSchemaDependencies) DeepCopyInto(out *JSONSchemaDependencies) {
{
in := &in
*out = make(JSONSchemaDependencies, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDependencies.
func (in JSONSchemaDependencies) DeepCopy() JSONSchemaDependencies {
if in == nil {
return nil
}
out := new(JSONSchemaDependencies)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) {
clone := in.DeepCopy()
*out = *clone
return
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrArray) DeepCopyInto(out *JSONSchemaPropsOrArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = (*in).DeepCopy()
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.JSONSchemas
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrArray.
func (in *JSONSchemaPropsOrArray) DeepCopy() *JSONSchemaPropsOrArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrArray)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrBool) DeepCopyInto(out *JSONSchemaPropsOrBool) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrBool.
func (in *JSONSchemaPropsOrBool) DeepCopy() *JSONSchemaPropsOrBool {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrBool)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrStringArray) DeepCopyInto(out *JSONSchemaPropsOrStringArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = (*in).DeepCopy()
}
if in.Property != nil {
in, out := &in.Property, &out.Property
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrStringArray.
func (in *JSONSchemaPropsOrStringArray) DeepCopy() *JSONSchemaPropsOrStringArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrStringArray)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceReference) DeepCopyInto(out *ServiceReference) {
*out = *in
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(string)
**out = **in
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(int32)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceReference.
func (in *ServiceReference) DeepCopy() *ServiceReference {
if in == nil {
return nil
}
out := new(ServiceReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
*out = *in
if in.URL != nil {
in, out := &in.URL, &out.URL
*out = new(string)
**out = **in
}
if in.Service != nil {
in, out := &in.Service, &out.Service
*out = new(ServiceReference)
(*in).DeepCopyInto(*out)
}
if in.CABundle != nil {
in, out := &in.CABundle, &out.CABundle
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookClientConfig.
func (in *WebhookClientConfig) DeepCopy() *WebhookClientConfig {
if in == nil {
return nil
}
out := new(WebhookClientConfig)
in.DeepCopyInto(out)
return out
}

View File

@@ -0,0 +1,55 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&CustomResourceDefinition{}, func(obj interface{}) { SetObjectDefaults_CustomResourceDefinition(obj.(*CustomResourceDefinition)) })
scheme.AddTypeDefaultingFunc(&CustomResourceDefinitionList{}, func(obj interface{}) {
SetObjectDefaults_CustomResourceDefinitionList(obj.(*CustomResourceDefinitionList))
})
return nil
}
func SetObjectDefaults_CustomResourceDefinition(in *CustomResourceDefinition) {
SetDefaults_CustomResourceDefinition(in)
SetDefaults_CustomResourceDefinitionSpec(&in.Spec)
if in.Spec.Conversion != nil {
if in.Spec.Conversion.WebhookClientConfig != nil {
if in.Spec.Conversion.WebhookClientConfig.Service != nil {
SetDefaults_ServiceReference(in.Spec.Conversion.WebhookClientConfig.Service)
}
}
}
}
func SetObjectDefaults_CustomResourceDefinitionList(in *CustomResourceDefinitionList) {
for i := range in.Items {
a := &in.Items[i]
SetObjectDefaults_CustomResourceDefinition(a)
}
}

View File

@@ -0,0 +1,97 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by prerelease-lifecycle-gen. DO NOT EDIT.
package v1beta1
import (
schema "k8s.io/apimachinery/pkg/runtime/schema"
)
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *ConversionReview) APILifecycleIntroduced() (major, minor int) {
return 1, 13
}
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *ConversionReview) APILifecycleDeprecated() (major, minor int) {
return 1, 19
}
// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.
// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<kind>" tags in types.go.
func (in *ConversionReview) APILifecycleReplacement() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "ConversionReview"}
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *ConversionReview) APILifecycleRemoved() (major, minor int) {
return 1, 22
}
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *CustomResourceDefinition) APILifecycleIntroduced() (major, minor int) {
return 1, 7
}
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *CustomResourceDefinition) APILifecycleDeprecated() (major, minor int) {
return 1, 16
}
// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.
// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<kind>" tags in types.go.
func (in *CustomResourceDefinition) APILifecycleReplacement() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *CustomResourceDefinition) APILifecycleRemoved() (major, minor int) {
return 1, 22
}
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *CustomResourceDefinitionList) APILifecycleIntroduced() (major, minor int) {
return 1, 7
}
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
func (in *CustomResourceDefinitionList) APILifecycleDeprecated() (major, minor int) {
return 1, 16
}
// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.
// It is controlled by "k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<kind>" tags in types.go.
func (in *CustomResourceDefinitionList) APILifecycleReplacement() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinitionList"}
}
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
func (in *CustomResourceDefinitionList) APILifecycleRemoved() (major, minor int) {
return 1, 22
}

27
vendor/k8s.io/apimachinery/pkg/util/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/*
Copyright 2014 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 uuid
import (
"github.com/google/uuid"
"k8s.io/apimachinery/pkg/types"
)
func NewUUID() types.UID {
return types.UID(uuid.New().String())
}

13
vendor/k8s.io/client-go/tools/leaderelection/OWNERS generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- mikedanese
- timothysc
reviewers:
- wojtek-t
- deads2k
- mikedanese
- gmarek
- timothysc
- ingvagabund
- resouer

View File

@@ -0,0 +1,69 @@
/*
Copyright 2015 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 leaderelection
import (
"net/http"
"sync"
"time"
)
// HealthzAdaptor associates the /healthz endpoint with the LeaderElection object.
// It helps deal with the /healthz endpoint being set up prior to the LeaderElection.
// This contains the code needed to act as an adaptor between the leader
// election code the health check code. It allows us to provide health
// status about the leader election. Most specifically about if the leader
// has failed to renew without exiting the process. In that case we should
// report not healthy and rely on the kubelet to take down the process.
type HealthzAdaptor struct {
pointerLock sync.Mutex
le *LeaderElector
timeout time.Duration
}
// Name returns the name of the health check we are implementing.
func (l *HealthzAdaptor) Name() string {
return "leaderElection"
}
// Check is called by the healthz endpoint handler.
// It fails (returns an error) if we own the lease but had not been able to renew it.
func (l *HealthzAdaptor) Check(req *http.Request) error {
l.pointerLock.Lock()
defer l.pointerLock.Unlock()
if l.le == nil {
return nil
}
return l.le.Check(l.timeout)
}
// SetLeaderElection ties a leader election object to a HealthzAdaptor
func (l *HealthzAdaptor) SetLeaderElection(le *LeaderElector) {
l.pointerLock.Lock()
defer l.pointerLock.Unlock()
l.le = le
}
// NewLeaderHealthzAdaptor creates a basic healthz adaptor to monitor a leader election.
// timeout determines the time beyond the lease expiry to be allowed for timeout.
// checks within the timeout period after the lease expires will still return healthy.
func NewLeaderHealthzAdaptor(timeout time.Duration) *HealthzAdaptor {
result := &HealthzAdaptor{
timeout: timeout,
}
return result
}

View File

@@ -0,0 +1,394 @@
/*
Copyright 2015 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 leaderelection implements leader election of a set of endpoints.
// It uses an annotation in the endpoints object to store the record of the
// election state. This implementation does not guarantee that only one
// client is acting as a leader (a.k.a. fencing).
//
// A client only acts on timestamps captured locally to infer the state of the
// leader election. The client does not consider timestamps in the leader
// election record to be accurate because these timestamps may not have been
// produced by a local clock. The implemention does not depend on their
// accuracy and only uses their change to indicate that another client has
// renewed the leader lease. Thus the implementation is tolerant to arbitrary
// clock skew, but is not tolerant to arbitrary clock skew rate.
//
// However the level of tolerance to skew rate can be configured by setting
// RenewDeadline and LeaseDuration appropriately. The tolerance expressed as a
// maximum tolerated ratio of time passed on the fastest node to time passed on
// the slowest node can be approximately achieved with a configuration that sets
// the same ratio of LeaseDuration to RenewDeadline. For example if a user wanted
// to tolerate some nodes progressing forward in time twice as fast as other nodes,
// the user could set LeaseDuration to 60 seconds and RenewDeadline to 30 seconds.
//
// While not required, some method of clock synchronization between nodes in the
// cluster is highly recommended. It's important to keep in mind when configuring
// this client that the tolerance to skew rate varies inversely to master
// availability.
//
// Larger clusters often have a more lenient SLA for API latency. This should be
// taken into account when configuring the client. The rate of leader transitions
// should be monitored and RetryPeriod and LeaseDuration should be increased
// until the rate is stable and acceptably low. It's important to keep in mind
// when configuring this client that the tolerance to API latency varies inversely
// to master availability.
//
// DISCLAIMER: this is an alpha API. This library will likely change significantly
// or even be removed entirely in subsequent releases. Depend on this API at
// your own risk.
package leaderelection
import (
"bytes"
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/klog/v2"
)
const (
JitterFactor = 1.2
)
// NewLeaderElector creates a LeaderElector from a LeaderElectionConfig
func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
if lec.LeaseDuration <= lec.RenewDeadline {
return nil, fmt.Errorf("leaseDuration must be greater than renewDeadline")
}
if lec.RenewDeadline <= time.Duration(JitterFactor*float64(lec.RetryPeriod)) {
return nil, fmt.Errorf("renewDeadline must be greater than retryPeriod*JitterFactor")
}
if lec.LeaseDuration < 1 {
return nil, fmt.Errorf("leaseDuration must be greater than zero")
}
if lec.RenewDeadline < 1 {
return nil, fmt.Errorf("renewDeadline must be greater than zero")
}
if lec.RetryPeriod < 1 {
return nil, fmt.Errorf("retryPeriod must be greater than zero")
}
if lec.Callbacks.OnStartedLeading == nil {
return nil, fmt.Errorf("OnStartedLeading callback must not be nil")
}
if lec.Callbacks.OnStoppedLeading == nil {
return nil, fmt.Errorf("OnStoppedLeading callback must not be nil")
}
if lec.Lock == nil {
return nil, fmt.Errorf("Lock must not be nil.")
}
le := LeaderElector{
config: lec,
clock: clock.RealClock{},
metrics: globalMetricsFactory.newLeaderMetrics(),
}
le.metrics.leaderOff(le.config.Name)
return &le, nil
}
type LeaderElectionConfig struct {
// Lock is the resource that will be used for locking
Lock rl.Interface
// LeaseDuration is the duration that non-leader candidates will
// wait to force acquire leadership. This is measured against time of
// last observed ack.
//
// A client needs to wait a full LeaseDuration without observing a change to
// the record before it can attempt to take over. When all clients are
// shutdown and a new set of clients are started with different names against
// the same leader record, they must wait the full LeaseDuration before
// attempting to acquire the lease. Thus LeaseDuration should be as short as
// possible (within your tolerance for clock skew rate) to avoid a possible
// long waits in the scenario.
//
// Core clients default this value to 15 seconds.
LeaseDuration time.Duration
// RenewDeadline is the duration that the acting master will retry
// refreshing leadership before giving up.
//
// Core clients default this value to 10 seconds.
RenewDeadline time.Duration
// RetryPeriod is the duration the LeaderElector clients should wait
// between tries of actions.
//
// Core clients default this value to 2 seconds.
RetryPeriod time.Duration
// Callbacks are callbacks that are triggered during certain lifecycle
// events of the LeaderElector
Callbacks LeaderCallbacks
// WatchDog is the associated health checker
// WatchDog may be null if its not needed/configured.
WatchDog *HealthzAdaptor
// ReleaseOnCancel should be set true if the lock should be released
// when the run context is cancelled. If you set this to true, you must
// ensure all code guarded by this lease has successfully completed
// prior to cancelling the context, or you may have two processes
// simultaneously acting on the critical path.
ReleaseOnCancel bool
// Name is the name of the resource lock for debugging
Name string
}
// LeaderCallbacks are callbacks that are triggered during certain
// lifecycle events of the LeaderElector. These are invoked asynchronously.
//
// possible future callbacks:
// * OnChallenge()
type LeaderCallbacks struct {
// OnStartedLeading is called when a LeaderElector client starts leading
OnStartedLeading func(context.Context)
// OnStoppedLeading is called when a LeaderElector client stops leading
OnStoppedLeading func()
// OnNewLeader is called when the client observes a leader that is
// not the previously observed leader. This includes the first observed
// leader when the client starts.
OnNewLeader func(identity string)
}
// LeaderElector is a leader election client.
type LeaderElector struct {
config LeaderElectionConfig
// internal bookkeeping
observedRecord rl.LeaderElectionRecord
observedRawRecord []byte
observedTime time.Time
// used to implement OnNewLeader(), may lag slightly from the
// value observedRecord.HolderIdentity if the transition has
// not yet been reported.
reportedLeader string
// clock is wrapper around time to allow for less flaky testing
clock clock.Clock
metrics leaderMetricsAdapter
}
// Run starts the leader election loop. Run will not return
// before leader election loop is stopped by ctx or it has
// stopped holding the leader lease
func (le *LeaderElector) Run(ctx context.Context) {
defer runtime.HandleCrash()
defer func() {
le.config.Callbacks.OnStoppedLeading()
}()
if !le.acquire(ctx) {
return // ctx signalled done
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go le.config.Callbacks.OnStartedLeading(ctx)
le.renew(ctx)
}
// RunOrDie starts a client with the provided config or panics if the config
// fails to validate. RunOrDie blocks until leader election loop is
// stopped by ctx or it has stopped holding the leader lease
func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
le, err := NewLeaderElector(lec)
if err != nil {
panic(err)
}
if lec.WatchDog != nil {
lec.WatchDog.SetLeaderElection(le)
}
le.Run(ctx)
}
// GetLeader returns the identity of the last observed leader or returns the empty string if
// no leader has yet been observed.
func (le *LeaderElector) GetLeader() string {
return le.observedRecord.HolderIdentity
}
// IsLeader returns true if the last observed leader was this client else returns false.
func (le *LeaderElector) IsLeader() bool {
return le.observedRecord.HolderIdentity == le.config.Lock.Identity()
}
// acquire loops calling tryAcquireOrRenew and returns true immediately when tryAcquireOrRenew succeeds.
// Returns false if ctx signals done.
func (le *LeaderElector) acquire(ctx context.Context) bool {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
succeeded := false
desc := le.config.Lock.Describe()
klog.Infof("attempting to acquire leader lease %v...", desc)
wait.JitterUntil(func() {
succeeded = le.tryAcquireOrRenew(ctx)
le.maybeReportTransition()
if !succeeded {
klog.V(4).Infof("failed to acquire lease %v", desc)
return
}
le.config.Lock.RecordEvent("became leader")
le.metrics.leaderOn(le.config.Name)
klog.Infof("successfully acquired lease %v", desc)
cancel()
}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
return succeeded
}
// renew loops calling tryAcquireOrRenew and returns immediately when tryAcquireOrRenew fails or ctx signals done.
func (le *LeaderElector) renew(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
wait.Until(func() {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
defer timeoutCancel()
err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
return le.tryAcquireOrRenew(timeoutCtx), nil
}, timeoutCtx.Done())
le.maybeReportTransition()
desc := le.config.Lock.Describe()
if err == nil {
klog.V(5).Infof("successfully renewed lease %v", desc)
return
}
le.config.Lock.RecordEvent("stopped leading")
le.metrics.leaderOff(le.config.Name)
klog.Infof("failed to renew lease %v: %v", desc, err)
cancel()
}, le.config.RetryPeriod, ctx.Done())
// if we hold the lease, give it up
if le.config.ReleaseOnCancel {
le.release()
}
}
// release attempts to release the leader lease if we have acquired it.
func (le *LeaderElector) release() bool {
if !le.IsLeader() {
return true
}
now := metav1.Now()
leaderElectionRecord := rl.LeaderElectionRecord{
LeaderTransitions: le.observedRecord.LeaderTransitions,
LeaseDurationSeconds: 1,
RenewTime: now,
AcquireTime: now,
}
if err := le.config.Lock.Update(context.TODO(), leaderElectionRecord); err != nil {
klog.Errorf("Failed to release lock: %v", err)
return false
}
le.observedRecord = leaderElectionRecord
le.observedTime = le.clock.Now()
return true
}
// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
// else it tries to renew the lease if it has already been acquired. Returns true
// on success else returns false.
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
now := metav1.Now()
leaderElectionRecord := rl.LeaderElectionRecord{
HolderIdentity: le.config.Lock.Identity(),
LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
RenewTime: now,
AcquireTime: now,
}
// 1. obtain or create the ElectionRecord
oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
if err != nil {
if !errors.IsNotFound(err) {
klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
return false
}
if err = le.config.Lock.Create(ctx, leaderElectionRecord); err != nil {
klog.Errorf("error initially creating leader election record: %v", err)
return false
}
le.observedRecord = leaderElectionRecord
le.observedTime = le.clock.Now()
return true
}
// 2. Record obtained, check the Identity & Time
if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
le.observedRecord = *oldLeaderElectionRecord
le.observedRawRecord = oldLeaderElectionRawRecord
le.observedTime = le.clock.Now()
}
if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
!le.IsLeader() {
klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
return false
}
// 3. We're going to try to update. The leaderElectionRecord is set to it's default
// here. Let's correct it before updating.
if le.IsLeader() {
leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
} else {
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
}
// update the lock itself
if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {
klog.Errorf("Failed to update lock: %v", err)
return false
}
le.observedRecord = leaderElectionRecord
le.observedTime = le.clock.Now()
return true
}
func (le *LeaderElector) maybeReportTransition() {
if le.observedRecord.HolderIdentity == le.reportedLeader {
return
}
le.reportedLeader = le.observedRecord.HolderIdentity
if le.config.Callbacks.OnNewLeader != nil {
go le.config.Callbacks.OnNewLeader(le.reportedLeader)
}
}
// Check will determine if the current lease is expired by more than timeout.
func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
if !le.IsLeader() {
// Currently not concerned with the case that we are hot standby
return nil
}
// If we are more than timeout seconds after the lease duration that is past the timeout
// on the lease renew. Time to start reporting ourselves as unhealthy. We should have
// died but conditions like deadlock can prevent this. (See #70819)
if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease {
return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name)
}
return nil
}

109
vendor/k8s.io/client-go/tools/leaderelection/metrics.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"sync"
)
// This file provides abstractions for setting the provider (e.g., prometheus)
// of metrics.
type leaderMetricsAdapter interface {
leaderOn(name string)
leaderOff(name string)
}
// GaugeMetric represents a single numerical value that can arbitrarily go up
// and down.
type SwitchMetric interface {
On(name string)
Off(name string)
}
type noopMetric struct{}
func (noopMetric) On(name string) {}
func (noopMetric) Off(name string) {}
// defaultLeaderMetrics expects the caller to lock before setting any metrics.
type defaultLeaderMetrics struct {
// leader's value indicates if the current process is the owner of name lease
leader SwitchMetric
}
func (m *defaultLeaderMetrics) leaderOn(name string) {
if m == nil {
return
}
m.leader.On(name)
}
func (m *defaultLeaderMetrics) leaderOff(name string) {
if m == nil {
return
}
m.leader.Off(name)
}
type noMetrics struct{}
func (noMetrics) leaderOn(name string) {}
func (noMetrics) leaderOff(name string) {}
// MetricsProvider generates various metrics used by the leader election.
type MetricsProvider interface {
NewLeaderMetric() SwitchMetric
}
type noopMetricsProvider struct{}
func (_ noopMetricsProvider) NewLeaderMetric() SwitchMetric {
return noopMetric{}
}
var globalMetricsFactory = leaderMetricsFactory{
metricsProvider: noopMetricsProvider{},
}
type leaderMetricsFactory struct {
metricsProvider MetricsProvider
onlyOnce sync.Once
}
func (f *leaderMetricsFactory) setProvider(mp MetricsProvider) {
f.onlyOnce.Do(func() {
f.metricsProvider = mp
})
}
func (f *leaderMetricsFactory) newLeaderMetrics() leaderMetricsAdapter {
mp := f.metricsProvider
if mp == (noopMetricsProvider{}) {
return noMetrics{}
}
return &defaultLeaderMetrics{
leader: mp.NewLeaderMetric(),
}
}
// SetProvider sets the metrics provider for all subsequently created work
// queues. Only the first call has an effect.
func SetProvider(metricsProvider MetricsProvider) {
globalMetricsFactory.setProvider(metricsProvider)
}

View File

@@ -0,0 +1,122 @@
/*
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 resourcelock
import (
"context"
"encoding/json"
"errors"
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
)
// TODO: This is almost a exact replica of Endpoints lock.
// going forwards as we self host more and more components
// and use ConfigMaps as the means to pass that configuration
// data we will likely move to deprecate the Endpoints lock.
type ConfigMapLock struct {
// ConfigMapMeta should contain a Name and a Namespace of a
// ConfigMapMeta object that the LeaderElector will attempt to lead.
ConfigMapMeta metav1.ObjectMeta
Client corev1client.ConfigMapsGetter
LockConfig ResourceLockConfig
cm *v1.ConfigMap
}
// Get returns the election record from a ConfigMap Annotation
func (cml *ConfigMapLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var record LeaderElectionRecord
var err error
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Get(ctx, cml.ConfigMapMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
if cml.cm.Annotations == nil {
cml.cm.Annotations = make(map[string]string)
}
recordStr, found := cml.cm.Annotations[LeaderElectionRecordAnnotationKey]
recordBytes := []byte(recordStr)
if found {
if err := json.Unmarshal(recordBytes, &record); err != nil {
return nil, nil, err
}
}
return &record, recordBytes, nil
}
// Create attempts to create a LeaderElectionRecord annotation
func (cml *ConfigMapLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
recordBytes, err := json.Marshal(ler)
if err != nil {
return err
}
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Create(ctx, &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cml.ConfigMapMeta.Name,
Namespace: cml.ConfigMapMeta.Namespace,
Annotations: map[string]string{
LeaderElectionRecordAnnotationKey: string(recordBytes),
},
},
}, metav1.CreateOptions{})
return err
}
// Update will update an existing annotation on a given resource.
func (cml *ConfigMapLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
if cml.cm == nil {
return errors.New("configmap not initialized, call get or create first")
}
recordBytes, err := json.Marshal(ler)
if err != nil {
return err
}
if cml.cm.Annotations == nil {
cml.cm.Annotations = make(map[string]string)
}
cml.cm.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
if err != nil {
return err
}
cml.cm = cm
return nil
}
// RecordEvent in leader election while adding meta-data
func (cml *ConfigMapLock) RecordEvent(s string) {
if cml.LockConfig.EventRecorder == nil {
return
}
events := fmt.Sprintf("%v %v", cml.LockConfig.Identity, s)
cml.LockConfig.EventRecorder.Eventf(&v1.ConfigMap{ObjectMeta: cml.cm.ObjectMeta}, v1.EventTypeNormal, "LeaderElection", events)
}
// Describe is used to convert details on current resource lock
// into a string
func (cml *ConfigMapLock) Describe() string {
return fmt.Sprintf("%v/%v", cml.ConfigMapMeta.Namespace, cml.ConfigMapMeta.Name)
}
// Identity returns the Identity of the lock
func (cml *ConfigMapLock) Identity() string {
return cml.LockConfig.Identity
}

View File

@@ -0,0 +1,117 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcelock
import (
"context"
"encoding/json"
"errors"
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
)
type EndpointsLock struct {
// EndpointsMeta should contain a Name and a Namespace of an
// Endpoints object that the LeaderElector will attempt to lead.
EndpointsMeta metav1.ObjectMeta
Client corev1client.EndpointsGetter
LockConfig ResourceLockConfig
e *v1.Endpoints
}
// Get returns the election record from a Endpoints Annotation
func (el *EndpointsLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var record LeaderElectionRecord
var err error
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(ctx, el.EndpointsMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
if el.e.Annotations == nil {
el.e.Annotations = make(map[string]string)
}
recordStr, found := el.e.Annotations[LeaderElectionRecordAnnotationKey]
recordBytes := []byte(recordStr)
if found {
if err := json.Unmarshal(recordBytes, &record); err != nil {
return nil, nil, err
}
}
return &record, recordBytes, nil
}
// Create attempts to create a LeaderElectionRecord annotation
func (el *EndpointsLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
recordBytes, err := json.Marshal(ler)
if err != nil {
return err
}
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Create(ctx, &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: el.EndpointsMeta.Name,
Namespace: el.EndpointsMeta.Namespace,
Annotations: map[string]string{
LeaderElectionRecordAnnotationKey: string(recordBytes),
},
},
}, metav1.CreateOptions{})
return err
}
// Update will update and existing annotation on a given resource.
func (el *EndpointsLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
if el.e == nil {
return errors.New("endpoint not initialized, call get or create first")
}
recordBytes, err := json.Marshal(ler)
if err != nil {
return err
}
if el.e.Annotations == nil {
el.e.Annotations = make(map[string]string)
}
el.e.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
e, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
if err != nil {
return err
}
el.e = e
return nil
}
// RecordEvent in leader election while adding meta-data
func (el *EndpointsLock) RecordEvent(s string) {
if el.LockConfig.EventRecorder == nil {
return
}
events := fmt.Sprintf("%v %v", el.LockConfig.Identity, s)
el.LockConfig.EventRecorder.Eventf(&v1.Endpoints{ObjectMeta: el.e.ObjectMeta}, v1.EventTypeNormal, "LeaderElection", events)
}
// Describe is used to convert details on current resource lock
// into a string
func (el *EndpointsLock) Describe() string {
return fmt.Sprintf("%v/%v", el.EndpointsMeta.Namespace, el.EndpointsMeta.Name)
}
// Identity returns the Identity of the lock
func (el *EndpointsLock) Identity() string {
return el.LockConfig.Identity
}

View File

@@ -0,0 +1,158 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcelock
import (
"context"
"fmt"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
const (
LeaderElectionRecordAnnotationKey = "control-plane.alpha.kubernetes.io/leader"
EndpointsResourceLock = "endpoints"
ConfigMapsResourceLock = "configmaps"
LeasesResourceLock = "leases"
EndpointsLeasesResourceLock = "endpointsleases"
ConfigMapsLeasesResourceLock = "configmapsleases"
)
// LeaderElectionRecord is the record that is stored in the leader election annotation.
// This information should be used for observational purposes only and could be replaced
// with a random string (e.g. UUID) with only slight modification of this code.
// TODO(mikedanese): this should potentially be versioned
type LeaderElectionRecord struct {
// HolderIdentity is the ID that owns the lease. If empty, no one owns this lease and
// all callers may acquire. Versions of this library prior to Kubernetes 1.14 will not
// attempt to acquire leases with empty identities and will wait for the full lease
// interval to expire before attempting to reacquire. This value is set to empty when
// a client voluntarily steps down.
HolderIdentity string `json:"holderIdentity"`
LeaseDurationSeconds int `json:"leaseDurationSeconds"`
AcquireTime metav1.Time `json:"acquireTime"`
RenewTime metav1.Time `json:"renewTime"`
LeaderTransitions int `json:"leaderTransitions"`
}
// EventRecorder records a change in the ResourceLock.
type EventRecorder interface {
Eventf(obj runtime.Object, eventType, reason, message string, args ...interface{})
}
// ResourceLockConfig common data that exists across different
// resource locks
type ResourceLockConfig struct {
// Identity is the unique string identifying a lease holder across
// all participants in an election.
Identity string
// EventRecorder is optional.
EventRecorder EventRecorder
}
// Interface offers a common interface for locking on arbitrary
// resources used in leader election. The Interface is used
// to hide the details on specific implementations in order to allow
// them to change over time. This interface is strictly for use
// by the leaderelection code.
type Interface interface {
// Get returns the LeaderElectionRecord
Get(ctx context.Context) (*LeaderElectionRecord, []byte, error)
// Create attempts to create a LeaderElectionRecord
Create(ctx context.Context, ler LeaderElectionRecord) error
// Update will update and existing LeaderElectionRecord
Update(ctx context.Context, ler LeaderElectionRecord) error
// RecordEvent is used to record events
RecordEvent(string)
// Identity will return the locks Identity
Identity() string
// Describe is used to convert details on current resource lock
// into a string
Describe() string
}
// Manufacture will create a lock of a given type according to the input parameters
func New(lockType string, ns string, name string, coreClient corev1.CoreV1Interface, coordinationClient coordinationv1.CoordinationV1Interface, rlc ResourceLockConfig) (Interface, error) {
endpointsLock := &EndpointsLock{
EndpointsMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Client: coreClient,
LockConfig: rlc,
}
configmapLock := &ConfigMapLock{
ConfigMapMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Client: coreClient,
LockConfig: rlc,
}
leaseLock := &LeaseLock{
LeaseMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Client: coordinationClient,
LockConfig: rlc,
}
switch lockType {
case EndpointsResourceLock:
return endpointsLock, nil
case ConfigMapsResourceLock:
return configmapLock, nil
case LeasesResourceLock:
return leaseLock, nil
case EndpointsLeasesResourceLock:
return &MultiLock{
Primary: endpointsLock,
Secondary: leaseLock,
}, nil
case ConfigMapsLeasesResourceLock:
return &MultiLock{
Primary: configmapLock,
Secondary: leaseLock,
}, nil
default:
return nil, fmt.Errorf("Invalid lock-type %s", lockType)
}
}
// NewFromKubeconfig will create a lock of a given type according to the input parameters.
func NewFromKubeconfig(lockType string, ns string, name string, rlc ResourceLockConfig, kubeconfig *restclient.Config, renewDeadline time.Duration) (Interface, error) {
// shallow copy, do not modify the kubeconfig
config := *kubeconfig
timeout := ((renewDeadline / time.Millisecond) / 2) * time.Millisecond
if timeout < time.Second {
timeout = time.Second
}
config.Timeout = timeout
leaderElectionClient := clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "leader-election"))
return New(lockType, ns, name, leaderElectionClient.CoreV1(), leaderElectionClient.CoordinationV1(), rlc)
}

View File

@@ -0,0 +1,135 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcelock
import (
"context"
"encoding/json"
"errors"
"fmt"
coordinationv1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
coordinationv1client "k8s.io/client-go/kubernetes/typed/coordination/v1"
)
type LeaseLock struct {
// LeaseMeta should contain a Name and a Namespace of a
// LeaseMeta object that the LeaderElector will attempt to lead.
LeaseMeta metav1.ObjectMeta
Client coordinationv1client.LeasesGetter
LockConfig ResourceLockConfig
lease *coordinationv1.Lease
}
// Get returns the election record from a Lease spec
func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var err error
ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
record := LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)
recordByte, err := json.Marshal(*record)
if err != nil {
return nil, nil, err
}
return record, recordByte, nil
}
// Create attempts to create a Lease
func (ll *LeaseLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
var err error
ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Create(ctx, &coordinationv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: ll.LeaseMeta.Name,
Namespace: ll.LeaseMeta.Namespace,
},
Spec: LeaderElectionRecordToLeaseSpec(&ler),
}, metav1.CreateOptions{})
return err
}
// Update will update an existing Lease spec.
func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
if ll.lease == nil {
return errors.New("lease not initialized, call get or create first")
}
ll.lease.Spec = LeaderElectionRecordToLeaseSpec(&ler)
lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
if err != nil {
return err
}
ll.lease = lease
return nil
}
// RecordEvent in leader election while adding meta-data
func (ll *LeaseLock) RecordEvent(s string) {
if ll.LockConfig.EventRecorder == nil {
return
}
events := fmt.Sprintf("%v %v", ll.LockConfig.Identity, s)
ll.LockConfig.EventRecorder.Eventf(&coordinationv1.Lease{ObjectMeta: ll.lease.ObjectMeta}, corev1.EventTypeNormal, "LeaderElection", events)
}
// Describe is used to convert details on current resource lock
// into a string
func (ll *LeaseLock) Describe() string {
return fmt.Sprintf("%v/%v", ll.LeaseMeta.Namespace, ll.LeaseMeta.Name)
}
// Identity returns the Identity of the lock
func (ll *LeaseLock) Identity() string {
return ll.LockConfig.Identity
}
func LeaseSpecToLeaderElectionRecord(spec *coordinationv1.LeaseSpec) *LeaderElectionRecord {
var r LeaderElectionRecord
if spec.HolderIdentity != nil {
r.HolderIdentity = *spec.HolderIdentity
}
if spec.LeaseDurationSeconds != nil {
r.LeaseDurationSeconds = int(*spec.LeaseDurationSeconds)
}
if spec.LeaseTransitions != nil {
r.LeaderTransitions = int(*spec.LeaseTransitions)
}
if spec.AcquireTime != nil {
r.AcquireTime = metav1.Time{spec.AcquireTime.Time}
}
if spec.RenewTime != nil {
r.RenewTime = metav1.Time{spec.RenewTime.Time}
}
return &r
}
func LeaderElectionRecordToLeaseSpec(ler *LeaderElectionRecord) coordinationv1.LeaseSpec {
leaseDurationSeconds := int32(ler.LeaseDurationSeconds)
leaseTransitions := int32(ler.LeaderTransitions)
return coordinationv1.LeaseSpec{
HolderIdentity: &ler.HolderIdentity,
LeaseDurationSeconds: &leaseDurationSeconds,
AcquireTime: &metav1.MicroTime{ler.AcquireTime.Time},
RenewTime: &metav1.MicroTime{ler.RenewTime.Time},
LeaseTransitions: &leaseTransitions,
}
}

View File

@@ -0,0 +1,104 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcelock
import (
"bytes"
"context"
"encoding/json"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
const (
UnknownLeader = "leaderelection.k8s.io/unknown"
)
// MultiLock is used for lock's migration
type MultiLock struct {
Primary Interface
Secondary Interface
}
// Get returns the older election record of the lock
func (ml *MultiLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
primary, primaryRaw, err := ml.Primary.Get(ctx)
if err != nil {
return nil, nil, err
}
secondary, secondaryRaw, err := ml.Secondary.Get(ctx)
if err != nil {
// Lock is held by old client
if apierrors.IsNotFound(err) && primary.HolderIdentity != ml.Identity() {
return primary, primaryRaw, nil
}
return nil, nil, err
}
if primary.HolderIdentity != secondary.HolderIdentity {
primary.HolderIdentity = UnknownLeader
primaryRaw, err = json.Marshal(primary)
if err != nil {
return nil, nil, err
}
}
return primary, ConcatRawRecord(primaryRaw, secondaryRaw), nil
}
// Create attempts to create both primary lock and secondary lock
func (ml *MultiLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
err := ml.Primary.Create(ctx, ler)
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
return ml.Secondary.Create(ctx, ler)
}
// Update will update and existing annotation on both two resources.
func (ml *MultiLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
err := ml.Primary.Update(ctx, ler)
if err != nil {
return err
}
_, _, err = ml.Secondary.Get(ctx)
if err != nil && apierrors.IsNotFound(err) {
return ml.Secondary.Create(ctx, ler)
}
return ml.Secondary.Update(ctx, ler)
}
// RecordEvent in leader election while adding meta-data
func (ml *MultiLock) RecordEvent(s string) {
ml.Primary.RecordEvent(s)
ml.Secondary.RecordEvent(s)
}
// Describe is used to convert details on current resource lock
// into a string
func (ml *MultiLock) Describe() string {
return ml.Primary.Describe()
}
// Identity returns the Identity of the lock
func (ml *MultiLock) Identity() string {
return ml.Primary.Identity()
}
func ConcatRawRecord(primaryRaw, secondaryRaw []byte) []byte {
return bytes.Join([][]byte{primaryRaw, secondaryRaw}, []byte(","))
}

14
vendor/k8s.io/component-base/config/OWNERS generated vendored Normal file
View File

@@ -0,0 +1,14 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- api-reviewers
- luxas
- mtaufen
- sttts
labels:
- kind/api-change

19
vendor/k8s.io/component-base/config/doc.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
package config // import "k8s.io/component-base/config"

91
vendor/k8s.io/component-base/config/types.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClientConnectionConfiguration contains details for constructing a client.
type ClientConnectionConfiguration struct {
// kubeconfig is the path to a KubeConfig file.
Kubeconfig string
// acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the
// default value of 'application/json'. This field will control all connections to the server used by a particular
// client.
AcceptContentTypes string
// contentType is the content type used when sending data to the server from this client.
ContentType string
// qps controls the number of queries per second allowed for this connection.
QPS float32
// burst allows extra queries to accumulate when a client is exceeding its rate.
Burst int32
}
// LeaderElectionConfiguration defines the configuration of leader election
// clients for components that can run with leader election enabled.
type LeaderElectionConfiguration struct {
// leaderElect enables a leader election client to gain leadership
// before executing the main loop. Enable this when running replicated
// components for high availability.
LeaderElect bool
// leaseDuration is the duration that non-leader candidates will wait
// after observing a leadership renewal until attempting to acquire
// leadership of a led but unrenewed leader slot. This is effectively the
// maximum duration that a leader can be stopped before it is replaced
// by another candidate. This is only applicable if leader election is
// enabled.
LeaseDuration metav1.Duration
// renewDeadline is the interval between attempts by the acting master to
// renew a leadership slot before it stops leading. This must be less
// than or equal to the lease duration. This is only applicable if leader
// election is enabled.
RenewDeadline metav1.Duration
// retryPeriod is the duration the clients should wait between attempting
// acquisition and renewal of a leadership. This is only applicable if
// leader election is enabled.
RetryPeriod metav1.Duration
// resourceLock indicates the resource object type that will be used to lock
// during leader election cycles.
ResourceLock string
// resourceName indicates the name of resource object that will be used to lock
// during leader election cycles.
ResourceName string
// resourceName indicates the namespace of resource object that will be used to lock
// during leader election cycles.
ResourceNamespace string
}
// DebuggingConfiguration holds configuration for Debugging related features.
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling bool
// enableContentionProfiling enables lock contention profiling, if
// enableProfiling is true.
EnableContentionProfiling bool
}
// LoggingConfiguration contains logging options
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string
// [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
// Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)
Sanitization bool
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/component-base/config"
)
// Important! The public back-and-forth conversion functions for the types in this generic
// package with ComponentConfig types need to be manually exposed like this in order for
// other packages that reference this package to be able to call these conversion functions
// in an autogenerated manner.
// TODO: Fix the bug in conversion-gen so it automatically discovers these Convert_* functions
// in autogenerated code as well.
func Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *config.ClientConnectionConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in, out, s)
}
func Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *config.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error {
return autoConvert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in, out, s)
}
func Convert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in *DebuggingConfiguration, out *config.DebuggingConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in, out, s)
}
func Convert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in *config.DebuggingConfiguration, out *DebuggingConfiguration, s conversion.Scope) error {
return autoConvert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in, out, s)
}
func Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *config.LeaderElectionConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in, out, s)
}
func Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *config.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error {
return autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in, out, s)
}
func Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in, out, s)
}
func Convert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error {
return autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in, out, s)
}

View File

@@ -0,0 +1,113 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
)
// RecommendedDefaultLeaderElectionConfiguration defaults a pointer to a
// LeaderElectionConfiguration struct. This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDefaultLeaderElectionConfiguration(obj *LeaderElectionConfiguration) {
zero := metav1.Duration{}
if obj.LeaseDuration == zero {
obj.LeaseDuration = metav1.Duration{Duration: 15 * time.Second}
}
if obj.RenewDeadline == zero {
obj.RenewDeadline = metav1.Duration{Duration: 10 * time.Second}
}
if obj.RetryPeriod == zero {
obj.RetryPeriod = metav1.Duration{Duration: 2 * time.Second}
}
if obj.ResourceLock == "" {
// TODO(#80289): Figure out how to migrate to LeaseLock at this point.
// This will most probably require going through EndpointsLease first.
obj.ResourceLock = EndpointsResourceLock
}
if obj.LeaderElect == nil {
obj.LeaderElect = utilpointer.BoolPtr(true)
}
}
// RecommendedDefaultClientConnectionConfiguration defaults a pointer to a
// ClientConnectionConfiguration struct. This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDefaultClientConnectionConfiguration(obj *ClientConnectionConfiguration) {
if len(obj.ContentType) == 0 {
obj.ContentType = "application/vnd.kubernetes.protobuf"
}
if obj.QPS == 0.0 {
obj.QPS = 50.0
}
if obj.Burst == 0 {
obj.Burst = 100
}
}
// RecommendedDebuggingConfiguration defaults profiling and debugging configuration.
// This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDebuggingConfiguration(obj *DebuggingConfiguration) {
if obj.EnableProfiling == nil {
obj.EnableProfiling = utilpointer.BoolPtr(true) // profile debugging is cheap to have exposed and standard on kube binaries
}
}
// NewRecommendedDebuggingConfiguration returns the current recommended DebuggingConfiguration.
// This may change between releases as recommendations shift.
func NewRecommendedDebuggingConfiguration() *DebuggingConfiguration {
ret := &DebuggingConfiguration{}
RecommendedDebuggingConfiguration(ret)
return ret
}
// RecommendedLoggingConfiguration defaults logging configuration.
// This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedLoggingConfiguration(obj *LoggingConfiguration) {
if obj.Format == "" {
obj.Format = "text"
}
}

20
vendor/k8s.io/component-base/config/v1alpha1/doc.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/component-base/config
package v1alpha1 // import "k8s.io/component-base/config/v1alpha1"

View File

@@ -0,0 +1,31 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
var (
// SchemeBuilder is the scheme builder with scheme init functions to run for this API package
SchemeBuilder runtime.SchemeBuilder
// localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package,
// defaulting and conversion init funcs are registered as well.
localSchemeBuilder = &SchemeBuilder
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)

93
vendor/k8s.io/component-base/config/v1alpha1/types.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const EndpointsResourceLock = "endpoints"
// LeaderElectionConfiguration defines the configuration of leader election
// clients for components that can run with leader election enabled.
type LeaderElectionConfiguration struct {
// leaderElect enables a leader election client to gain leadership
// before executing the main loop. Enable this when running replicated
// components for high availability.
LeaderElect *bool `json:"leaderElect"`
// leaseDuration is the duration that non-leader candidates will wait
// after observing a leadership renewal until attempting to acquire
// leadership of a led but unrenewed leader slot. This is effectively the
// maximum duration that a leader can be stopped before it is replaced
// by another candidate. This is only applicable if leader election is
// enabled.
LeaseDuration metav1.Duration `json:"leaseDuration"`
// renewDeadline is the interval between attempts by the acting master to
// renew a leadership slot before it stops leading. This must be less
// than or equal to the lease duration. This is only applicable if leader
// election is enabled.
RenewDeadline metav1.Duration `json:"renewDeadline"`
// retryPeriod is the duration the clients should wait between attempting
// acquisition and renewal of a leadership. This is only applicable if
// leader election is enabled.
RetryPeriod metav1.Duration `json:"retryPeriod"`
// resourceLock indicates the resource object type that will be used to lock
// during leader election cycles.
ResourceLock string `json:"resourceLock"`
// resourceName indicates the name of resource object that will be used to lock
// during leader election cycles.
ResourceName string `json:"resourceName"`
// resourceName indicates the namespace of resource object that will be used to lock
// during leader election cycles.
ResourceNamespace string `json:"resourceNamespace"`
}
// DebuggingConfiguration holds configuration for Debugging related features.
type DebuggingConfiguration struct {
// enableProfiling enables profiling via web interface host:port/debug/pprof/
EnableProfiling *bool `json:"enableProfiling,omitempty"`
// enableContentionProfiling enables lock contention profiling, if
// enableProfiling is true.
EnableContentionProfiling *bool `json:"enableContentionProfiling,omitempty"`
}
// ClientConnectionConfiguration contains details for constructing a client.
type ClientConnectionConfiguration struct {
// kubeconfig is the path to a KubeConfig file.
Kubeconfig string `json:"kubeconfig"`
// acceptContentTypes defines the Accept header sent by clients when connecting to a server, overriding the
// default value of 'application/json'. This field will control all connections to the server used by a particular
// client.
AcceptContentTypes string `json:"acceptContentTypes"`
// contentType is the content type used when sending data to the server from this client.
ContentType string `json:"contentType"`
// qps controls the number of queries per second allowed for this connection.
QPS float32 `json:"qps"`
// burst allows extra queries to accumulate when a client is exceeding its rate.
Burst int32 `json:"burst"`
}
// LoggingConfiguration contains logging options
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
type LoggingConfiguration struct {
// Format Flag specifies the structure of log messages.
// default value of format is `text`
Format string `json:"format,omitempty"`
// [Experimental] When enabled prevents logging of fields tagged as sensitive (passwords, keys, tokens).
// Runtime log sanitization may introduce significant computation overhead and therefore should not be enabled in production.`)
Sanitization bool `json:"sanitization,omitempty"`
}

View File

@@ -0,0 +1,154 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
config "k8s.io/component-base/config"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddConversionFunc((*config.ClientConnectionConfiguration)(nil), (*ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(a.(*config.ClientConnectionConfiguration), b.(*ClientConnectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.DebuggingConfiguration)(nil), (*DebuggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(a.(*config.DebuggingConfiguration), b.(*DebuggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.LeaderElectionConfiguration)(nil), (*LeaderElectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(a.(*config.LeaderElectionConfiguration), b.(*LeaderElectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.LoggingConfiguration)(nil), (*LoggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(a.(*config.LoggingConfiguration), b.(*LoggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ClientConnectionConfiguration)(nil), (*config.ClientConnectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(a.(*ClientConnectionConfiguration), b.(*config.ClientConnectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*DebuggingConfiguration)(nil), (*config.DebuggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(a.(*DebuggingConfiguration), b.(*config.DebuggingConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*LeaderElectionConfiguration)(nil), (*config.LeaderElectionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(a.(*LeaderElectionConfiguration), b.(*config.LeaderElectionConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*LoggingConfiguration)(nil), (*config.LoggingConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(a.(*LoggingConfiguration), b.(*config.LoggingConfiguration), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(in *ClientConnectionConfiguration, out *config.ClientConnectionConfiguration, s conversion.Scope) error {
out.Kubeconfig = in.Kubeconfig
out.AcceptContentTypes = in.AcceptContentTypes
out.ContentType = in.ContentType
out.QPS = in.QPS
out.Burst = in.Burst
return nil
}
func autoConvert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(in *config.ClientConnectionConfiguration, out *ClientConnectionConfiguration, s conversion.Scope) error {
out.Kubeconfig = in.Kubeconfig
out.AcceptContentTypes = in.AcceptContentTypes
out.ContentType = in.ContentType
out.QPS = in.QPS
out.Burst = in.Burst
return nil
}
func autoConvert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(in *DebuggingConfiguration, out *config.DebuggingConfiguration, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil {
return err
}
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableContentionProfiling, &out.EnableContentionProfiling, s); err != nil {
return err
}
return nil
}
func autoConvert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(in *config.DebuggingConfiguration, out *DebuggingConfiguration, s conversion.Scope) error {
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil {
return err
}
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableContentionProfiling, &out.EnableContentionProfiling, s); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(in *LeaderElectionConfiguration, out *config.LeaderElectionConfiguration, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil {
return err
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
out.ResourceLock = in.ResourceLock
out.ResourceName = in.ResourceName
out.ResourceNamespace = in.ResourceNamespace
return nil
}
func autoConvert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(in *config.LeaderElectionConfiguration, out *LeaderElectionConfiguration, s conversion.Scope) error {
if err := v1.Convert_bool_To_Pointer_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil {
return err
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
out.ResourceLock = in.ResourceLock
out.ResourceName = in.ResourceName
out.ResourceNamespace = in.ResourceNamespace
return nil
}
func autoConvert_v1alpha1_LoggingConfiguration_To_config_LoggingConfiguration(in *LoggingConfiguration, out *config.LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format
out.Sanitization = in.Sanitization
return nil
}
func autoConvert_config_LoggingConfiguration_To_v1alpha1_LoggingConfiguration(in *config.LoggingConfiguration, out *LoggingConfiguration, s conversion.Scope) error {
out.Format = in.Format
out.Sanitization = in.Sanitization
return nil
}

View File

@@ -0,0 +1,103 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration.
func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration {
if in == nil {
return nil
}
out := new(ClientConnectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DebuggingConfiguration) DeepCopyInto(out *DebuggingConfiguration) {
*out = *in
if in.EnableProfiling != nil {
in, out := &in.EnableProfiling, &out.EnableProfiling
*out = new(bool)
**out = **in
}
if in.EnableContentionProfiling != nil {
in, out := &in.EnableContentionProfiling, &out.EnableContentionProfiling
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DebuggingConfiguration.
func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
if in == nil {
return nil
}
out := new(DebuggingConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
if in.LeaderElect != nil {
in, out := &in.LeaderElect, &out.LeaderElect
*out = new(bool)
**out = **in
}
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfiguration.
func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
if in == nil {
return nil
}
out := new(LeaderElectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(out)
return out
}

View File

@@ -0,0 +1,88 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package config
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientConnectionConfiguration) DeepCopyInto(out *ClientConnectionConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientConnectionConfiguration.
func (in *ClientConnectionConfiguration) DeepCopy() *ClientConnectionConfiguration {
if in == nil {
return nil
}
out := new(ClientConnectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DebuggingConfiguration) DeepCopyInto(out *DebuggingConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DebuggingConfiguration.
func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
if in == nil {
return nil
}
out := new(DebuggingConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
*out = *in
out.LeaseDuration = in.LeaseDuration
out.RenewDeadline = in.RenewDeadline
out.RetryPeriod = in.RetryPeriod
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfiguration.
func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
if in == nil {
return nil
}
out := new(LeaderElectionConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration.
func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration {
if in == nil {
return nil
}
out := new(LoggingConfiguration)
in.DeepCopyInto(out)
return out
}

61
vendor/modules.txt vendored
View File

@@ -557,9 +557,23 @@ github.com/prometheus/common/model
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# github.com/redhat-developer/service-binding-operator v0.7.1
# github.com/redhat-developer/service-binding-operator v0.9.0
## explicit
github.com/redhat-developer/service-binding-operator/api/v1alpha1
github.com/redhat-developer/service-binding-operator/apis
github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1
github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha2
github.com/redhat-developer/service-binding-operator/pkg/binding
github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes
github.com/redhat-developer/service-binding-operator/pkg/converter
github.com/redhat-developer/service-binding-operator/pkg/naming
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/builder
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/collect
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/mapping
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/naming
github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/project
github.com/redhat-developer/service-binding-operator/pkg/util
# github.com/russross/blackfriday v1.5.2
github.com/russross/blackfriday
# github.com/securego/gosec/v2 v2.8.0
@@ -730,6 +744,8 @@ golang.org/x/tools/internal/typesinternal
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
golang.org/x/xerrors
golang.org/x/xerrors/internal
# gomodules.xyz/jsonpatch/v2 v2.1.0
gomodules.xyz/jsonpatch/v2
# google.golang.org/appengine v1.6.6
google.golang.org/appengine
google.golang.org/appengine/internal
@@ -847,6 +863,7 @@ k8s.io/api/storage/v1beta1
# k8s.io/apiextensions-apiserver v0.20.0
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
# k8s.io/apimachinery v0.20.1 => github.com/openshift/kubernetes-apimachinery v0.0.0-20210108114224-194a87c5b03a
## explicit
k8s.io/apimachinery/pkg/api/equality
@@ -892,6 +909,7 @@ k8s.io/apimachinery/pkg/util/remotecommand
k8s.io/apimachinery/pkg/util/runtime
k8s.io/apimachinery/pkg/util/sets
k8s.io/apimachinery/pkg/util/strategicpatch
k8s.io/apimachinery/pkg/util/uuid
k8s.io/apimachinery/pkg/util/validation
k8s.io/apimachinery/pkg/util/validation/field
k8s.io/apimachinery/pkg/util/wait
@@ -1046,6 +1064,8 @@ k8s.io/client-go/tools/clientcmd
k8s.io/client-go/tools/clientcmd/api
k8s.io/client-go/tools/clientcmd/api/latest
k8s.io/client-go/tools/clientcmd/api/v1
k8s.io/client-go/tools/leaderelection
k8s.io/client-go/tools/leaderelection/resourcelock
k8s.io/client-go/tools/metrics
k8s.io/client-go/tools/pager
k8s.io/client-go/tools/portforward
@@ -1066,6 +1086,8 @@ k8s.io/client-go/util/keyutil
k8s.io/client-go/util/retry
k8s.io/client-go/util/workqueue
# k8s.io/component-base v0.20.1
k8s.io/component-base/config
k8s.io/component-base/config/v1alpha1
k8s.io/component-base/metrics
k8s.io/component-base/metrics/legacyregistry
k8s.io/component-base/version
@@ -1096,9 +1118,44 @@ k8s.io/utils/integer
k8s.io/utils/pointer
k8s.io/utils/trace
# sigs.k8s.io/controller-runtime v0.7.0
## explicit
sigs.k8s.io/controller-runtime
sigs.k8s.io/controller-runtime/pkg/builder
sigs.k8s.io/controller-runtime/pkg/cache
sigs.k8s.io/controller-runtime/pkg/cache/internal
sigs.k8s.io/controller-runtime/pkg/client
sigs.k8s.io/controller-runtime/pkg/client/apiutil
sigs.k8s.io/controller-runtime/pkg/client/config
sigs.k8s.io/controller-runtime/pkg/config
sigs.k8s.io/controller-runtime/pkg/config/v1alpha1
sigs.k8s.io/controller-runtime/pkg/controller
sigs.k8s.io/controller-runtime/pkg/controller/controllerutil
sigs.k8s.io/controller-runtime/pkg/conversion
sigs.k8s.io/controller-runtime/pkg/event
sigs.k8s.io/controller-runtime/pkg/handler
sigs.k8s.io/controller-runtime/pkg/healthz
sigs.k8s.io/controller-runtime/pkg/internal/controller
sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics
sigs.k8s.io/controller-runtime/pkg/internal/log
sigs.k8s.io/controller-runtime/pkg/internal/recorder
sigs.k8s.io/controller-runtime/pkg/leaderelection
sigs.k8s.io/controller-runtime/pkg/log
sigs.k8s.io/controller-runtime/pkg/manager
sigs.k8s.io/controller-runtime/pkg/manager/signals
sigs.k8s.io/controller-runtime/pkg/metrics
sigs.k8s.io/controller-runtime/pkg/predicate
sigs.k8s.io/controller-runtime/pkg/ratelimiter
sigs.k8s.io/controller-runtime/pkg/reconcile
sigs.k8s.io/controller-runtime/pkg/recorder
sigs.k8s.io/controller-runtime/pkg/runtime/inject
sigs.k8s.io/controller-runtime/pkg/scheme
sigs.k8s.io/controller-runtime/pkg/source
sigs.k8s.io/controller-runtime/pkg/source/internal
sigs.k8s.io/controller-runtime/pkg/webhook
sigs.k8s.io/controller-runtime/pkg/webhook/admission
sigs.k8s.io/controller-runtime/pkg/webhook/conversion
sigs.k8s.io/controller-runtime/pkg/webhook/internal/certwatcher
sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics
# sigs.k8s.io/kustomize v2.0.3+incompatible
sigs.k8s.io/kustomize/pkg/commands/build
sigs.k8s.io/kustomize/pkg/constants

24
vendor/sigs.k8s.io/controller-runtime/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,24 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# editor and IDE paraphernalia
.idea
*.swp
*.swo
*~
# Vscode files
.vscode
# Tools binaries.
hack/tools/bin

35
vendor/sigs.k8s.io/controller-runtime/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,35 @@
run:
deadline: 5m
linters-settings:
lll:
line-length: 170
dupl:
threshold: 400
issues:
# don't skip warning about doc comments
exclude-use-default: false
# restore some of the defaults
# (fill in the rest as needed)
exclude-rules:
- linters: [errcheck]
text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked"
linters:
disable-all: true
enable:
- misspell
- structcheck
- golint
- govet
- deadcode
- errcheck
- varcheck
- goconst
- unparam
- ineffassign
- nakedret
- gocyclo
- lll
- dupl
- goimports
- golint

19
vendor/sigs.k8s.io/controller-runtime/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# Contributing guidelines
## Sign the CLA
Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests.
Please see https://git.k8s.io/community/CLA.md for more info
## Contributing steps
1. Submit an issue describing your proposed change to the repo in question.
1. The [repo owners](OWNERS) will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).
1. Fork the desired repo, develop and test your code changes.
1. Submit a pull request.
## Test locally
Run the command `make test` to test the changes locally.

81
vendor/sigs.k8s.io/controller-runtime/FAQ.md generated vendored Normal file
View File

@@ -0,0 +1,81 @@
# FAQ
### Q: How do I know which type of object a controller references?
**A**: Each controller should only reconcile one object type. Other
affected objects should be mapped to a single type of root object, using
the `EnqueueRequestForOwner` or `EnqueueRequestsFromMapFunc` event
handlers, and potentially indices. Then, your Reconcile method should
attempt to reconcile *all* state for that given root objects.
### Q: How do I have different logic in my reconciler for different types of events (e.g. create, update, delete)?
**A**: You should not. Reconcile functions should be idempotent, and
should always reconcile state by reading all the state it needs, then
writing updates. This allows your reconciler to correctly respond to
generic events, adjust to skipped or coalesced events, and easily deal
with application startup. The controller will enqueue reconcile requests
for both old and new objects if a mapping changes, but it's your
responsibility to make sure you have enough information to be able clean
up state that's no longer referenced.
### Q: My cache might be stale if I read from a cache! How should I deal with that?
**A**: There are several different approaches that can be taken, depending
on your situation.
- When you can, take advantage of optimistic locking: use deterministic
names for objects you create, so that the Kubernetes API server will
warn you if the object already exists. Many controllers in Kubernetes
take this approach: the StatefulSet controller appends a specific number
to each pod that it creates, while the Deployment controller hashes the
pod template spec and appends that.
- In the few cases when you cannot take advantage of deterministic names
(e.g. when using generateName), it may be useful in to track which
actions you took, and assume that they need to be repeated if they don't
occur after a given time (e.g. using a requeue result). This is what
the ReplicaSet controller does.
In general, write your controller with the assumption that information
will eventually be correct, but may be slightly out of date. Make sure
that your reconcile function enforces the entire state of the world each
time it runs. If none of this works for you, you can always construct
a client that reads directly from the API server, but this is generally
considered to be a last resort, and the two approaches above should
generally cover most circumstances.
### Q: Where's the fake client? How do I use it?
**A**: The fake client
[exists](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/client/fake),
but we generally recommend using
[envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
to test against a real API server. In our experience, tests using fake
clients gradually re-implement poorly-written impressions of a real API
server, which leads to hard-to-maintain, complex test code.
### Q: How should I write tests? Any suggestions for getting started?
- Use the aforementioned
[envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment)
to spin up a real API server instead of trying to mock one out.
- Structure your tests to check that the state of the world is as you
expect it, *not* that a particular set of API calls were made, when
working with Kubernetes APIs. This will allow you to more easily
refactor and improve the internals of your controllers without changing
your tests.
- Remember that any time you're interacting with the API server, changes
may have some delay between write time and reconcile time.
### Q: What are these errors about no Kind being registered for a type?
**A**: You're probably missing a fully-set-up Scheme. Schemes record the
mapping between Go types and group-version-kinds in Kubernetes. In
general, your application should have its own Scheme containing the types
from the API groups that it needs (be they Kubernetes types or your own).
See the [scheme builder
docs](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/scheme) for
more information.

110
vendor/sigs.k8s.io/controller-runtime/Makefile generated vendored Normal file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# Copyright 2020 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.
# If you update this file, please follow
# https://suva.sh/posts/well-documented-makefiles
## --------------------------------------
## General
## --------------------------------------
SHELL:=/usr/bin/env bash
.DEFAULT_GOAL:=help
# Use GOPROXY environment variable if set
GOPROXY := $(shell go env GOPROXY)
ifeq ($(GOPROXY),)
GOPROXY := https://proxy.golang.org
endif
export GOPROXY
# Active module mode, as we use go modules to manage dependencies
export GO111MODULE=on
# Tools.
TOOLS_DIR := hack/tools
TOOLS_BIN_DIR := $(TOOLS_DIR)/bin
GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/golangci-lint)
GO_APIDIFF := $(TOOLS_BIN_DIR)/go-apidiff
CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen
# The help will print out all targets with their descriptions organized bellow their categories. The categories are represented by `##@` and the target descriptions by `##`.
# The awk commands is responsible to read the entire set of makefiles included in this invocation, looking for lines of the file as xyz: ## something, and then pretty-format the target and help. Then, if there's a line with ##@ something, that gets pretty-printed as a category.
# More info over the usage of ANSI control characters for terminal formatting: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info over awk command: http://linuxcommand.org/lc3_adv_awk.php
.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
## --------------------------------------
## Testing
## --------------------------------------
.PHONY: test
test: ## Run the script check-everything.sh which will check all.
TRACE=1 ./hack/check-everything.sh
## --------------------------------------
## Binaries
## --------------------------------------
$(GOLANGCI_LINT): $(TOOLS_DIR)/go.mod # Build golangci-lint from tools folder.
cd $(TOOLS_DIR) && go build -tags=tools -o bin/golangci-lint github.com/golangci/golangci-lint/cmd/golangci-lint
$(GO_APIDIFF): $(TOOLS_DIR)/go.mod # Build go-apidiff from tools folder.
cd $(TOOLS_DIR) && go build -tags=tools -o bin/go-apidiff github.com/joelanford/go-apidiff
$(CONTROLLER_GEN): $(TOOLS_DIR)/go.mod # Build controller-gen from tools folder.
cd $(TOOLS_DIR) && go build -tags=tools -o bin/controller-gen sigs.k8s.io/controller-tools/cmd/controller-gen
## --------------------------------------
## Linting
## --------------------------------------
.PHONY: lint
lint: $(GOLANGCI_LINT) ## Lint codebase.
$(GOLANGCI_LINT) run -v
## --------------------------------------
## Generate
## --------------------------------------
.PHONY: modules
modules: ## Runs go mod to ensure modules are up to date.
go mod tidy
cd $(TOOLS_DIR); go mod tidy
.PHONY: generate
generate: $(CONTROLLER_GEN) ## Runs controller-gen for internal types for config file
$(CONTROLLER_GEN) object paths="./pkg/config/v1alpha1/...;./examples/configfile/custom/v1alpha1/..."
## --------------------------------------
## Cleanup / Verification
## --------------------------------------
.PHONY: clean
clean: ## Cleanup.
$(MAKE) clean-bin
.PHONY: clean-bin
clean-bin: ## Remove all generated binaries.
rm -rf hack/tools/bin
.PHONY: verify-modules
verify-modules: modules
@if !(git diff --quiet HEAD -- go.sum go.mod); then \
echo "go module files are out of date, please run 'make modules'"; exit 1; \
fi

10
vendor/sigs.k8s.io/controller-runtime/OWNERS generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
approvers:
- controller-runtime-admins
- controller-runtime-maintainers
- controller-runtime-approvers
reviewers:
- controller-runtime-admins
- controller-runtime-reviewers
- controller-runtime-approvers

Some files were not shown because too many files have changed in this diff Show More