✨ use vgo
This commit is contained in:
11
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/examplecrd1.yaml
generated
vendored
Normal file
11
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/examplecrd1.yaml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: foos.bar.example.com
|
||||
spec:
|
||||
group: bar.example.com
|
||||
names:
|
||||
kind: Foo
|
||||
plural: foos
|
||||
scope: Namespaced
|
||||
version: "v1beta1"
|
||||
11
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/examplecrd2.yaml
generated
vendored
Normal file
11
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/examplecrd2.yaml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: bazs.qux.example.com
|
||||
spec:
|
||||
group: qux.example.com
|
||||
names:
|
||||
kind: Baz
|
||||
plural: bazs
|
||||
scope: Namespaced
|
||||
version: "v1beta1"
|
||||
18
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/notcrd.yaml
generated
vendored
Normal file
18
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/notcrd.yaml
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
356
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go
generated
vendored
356
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go
generated
vendored
@@ -1,356 +0,0 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
admissionregistration "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
|
||||
)
|
||||
|
||||
// setDefault does defaulting for the Server.
|
||||
func (s *Server) setDefault() {
|
||||
s.setServerDefault()
|
||||
s.setBootstrappingDefault()
|
||||
}
|
||||
|
||||
// setServerDefault does defaulting for the ServerOptions.
|
||||
func (s *Server) setServerDefault() {
|
||||
if len(s.Name) == 0 {
|
||||
s.Name = "default-k8s-webhook-server"
|
||||
}
|
||||
if s.registry == nil {
|
||||
s.registry = map[string]Webhook{}
|
||||
}
|
||||
if s.sMux == nil {
|
||||
s.sMux = http.DefaultServeMux
|
||||
}
|
||||
if s.Port <= 0 {
|
||||
s.Port = 443
|
||||
}
|
||||
if len(s.CertDir) == 0 {
|
||||
s.CertDir = path.Join("k8s-webhook-server", "cert")
|
||||
}
|
||||
if s.DisableWebhookConfigInstaller == nil {
|
||||
diwc := false
|
||||
s.DisableWebhookConfigInstaller = &diwc
|
||||
}
|
||||
|
||||
if s.Client == nil {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
s.Client, err = client.New(cfg, client.Options{})
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setBootstrappingDefault does defaulting for the Server bootstrapping.
|
||||
func (s *Server) setBootstrappingDefault() {
|
||||
if s.BootstrapOptions == nil {
|
||||
s.BootstrapOptions = &BootstrapOptions{}
|
||||
}
|
||||
if len(s.MutatingWebhookConfigName) == 0 {
|
||||
s.MutatingWebhookConfigName = "mutating-webhook-configuration"
|
||||
}
|
||||
if len(s.ValidatingWebhookConfigName) == 0 {
|
||||
s.ValidatingWebhookConfigName = "validating-webhook-configuration"
|
||||
}
|
||||
if s.Host == nil && s.Service == nil {
|
||||
varString := "localhost"
|
||||
s.Host = &varString
|
||||
}
|
||||
|
||||
var certWriter writer.CertWriter
|
||||
var err error
|
||||
if s.Secret != nil {
|
||||
certWriter, err = writer.NewSecretCertWriter(
|
||||
writer.SecretCertWriterOptions{
|
||||
Secret: s.Secret,
|
||||
Client: s.Client,
|
||||
})
|
||||
} else {
|
||||
certWriter, err = writer.NewFSCertWriter(
|
||||
writer.FSCertWriterOptions{
|
||||
Path: s.CertDir,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
s.certProvisioner = &cert.Provisioner{
|
||||
CertWriter: certWriter,
|
||||
}
|
||||
}
|
||||
|
||||
// InstallWebhookManifests creates the admissionWebhookConfiguration objects and service if any.
|
||||
// It also provisions the certificate for the admission server.
|
||||
func (s *Server) InstallWebhookManifests() error {
|
||||
// do defaulting if necessary
|
||||
s.once.Do(s.setDefault)
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
|
||||
var err error
|
||||
s.webhookConfigurations, err = s.whConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
svc := s.service()
|
||||
objects := append(s.webhookConfigurations, svc)
|
||||
|
||||
cc, err := s.getClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Provision the cert by creating new one or refreshing existing one.
|
||||
_, err = s.certProvisioner.Provision(cert.Options{
|
||||
ClientConfig: cc,
|
||||
Objects: s.webhookConfigurations,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batchCreateOrReplace(s.Client, objects...)
|
||||
}
|
||||
|
||||
func (s *Server) getClientConfig() (*admissionregistration.WebhookClientConfig, error) {
|
||||
if s.Host != nil && s.Service != nil {
|
||||
return nil, errors.New("URL and Service can't be set at the same time")
|
||||
}
|
||||
cc := &admissionregistration.WebhookClientConfig{
|
||||
CABundle: []byte{},
|
||||
}
|
||||
if s.Host != nil {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(*s.Host, strconv.Itoa(int(s.Port))),
|
||||
}
|
||||
urlString := u.String()
|
||||
cc.URL = &urlString
|
||||
}
|
||||
if s.Service != nil {
|
||||
cc.Service = &admissionregistration.ServiceReference{
|
||||
Name: s.Service.Name,
|
||||
Namespace: s.Service.Namespace,
|
||||
// Path will be set later
|
||||
}
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// getClientConfigWithPath constructs a WebhookClientConfig based on the server options.
|
||||
// It will use path to the set the path in WebhookClientConfig.
|
||||
func (s *Server) getClientConfigWithPath(path string) (*admissionregistration.WebhookClientConfig, error) {
|
||||
cc, err := s.getClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc, setPath(cc, path)
|
||||
}
|
||||
|
||||
// setPath sets the path in the WebhookClientConfig.
|
||||
func setPath(cc *admissionregistration.WebhookClientConfig, path string) error {
|
||||
if cc.URL != nil {
|
||||
u, err := url.Parse(*cc.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Path = path
|
||||
urlString := u.String()
|
||||
cc.URL = &urlString
|
||||
}
|
||||
if cc.Service != nil {
|
||||
cc.Service.Path = &path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// whConfigs creates a mutatingWebhookConfiguration and(or) a validatingWebhookConfiguration based on registry.
|
||||
// For the same type of webhook configuration, it generates a webhook entry per endpoint.
|
||||
func (s *Server) whConfigs() ([]runtime.Object, error) {
|
||||
objs := []runtime.Object{}
|
||||
mutatingWH, err := s.mutatingWHConfigs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mutatingWH != nil {
|
||||
objs = append(objs, mutatingWH)
|
||||
}
|
||||
validatingWH, err := s.validatingWHConfigs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if validatingWH != nil {
|
||||
objs = append(objs, validatingWH)
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (s *Server) mutatingWHConfigs() (runtime.Object, error) {
|
||||
mutatingWebhooks := []v1beta1.Webhook{}
|
||||
for path, webhook := range s.registry {
|
||||
if webhook.GetType() != types.WebhookTypeMutating {
|
||||
continue
|
||||
}
|
||||
|
||||
admissionWebhook := webhook.(*admission.Webhook)
|
||||
wh, err := s.admissionWebhook(path, admissionWebhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutatingWebhooks = append(mutatingWebhooks, *wh)
|
||||
}
|
||||
|
||||
sort.Slice(mutatingWebhooks, func(i, j int) bool {
|
||||
return mutatingWebhooks[i].Name < mutatingWebhooks[j].Name
|
||||
})
|
||||
|
||||
if len(mutatingWebhooks) > 0 {
|
||||
return &admissionregistration.MutatingWebhookConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: fmt.Sprintf("%s/%s", admissionregistration.GroupName, "v1beta1"),
|
||||
Kind: "MutatingWebhookConfiguration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s.MutatingWebhookConfigName,
|
||||
},
|
||||
Webhooks: mutatingWebhooks,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Server) validatingWHConfigs() (runtime.Object, error) {
|
||||
validatingWebhooks := []v1beta1.Webhook{}
|
||||
for path, webhook := range s.registry {
|
||||
var admissionWebhook *admission.Webhook
|
||||
if webhook.GetType() != types.WebhookTypeValidating {
|
||||
continue
|
||||
}
|
||||
|
||||
admissionWebhook = webhook.(*admission.Webhook)
|
||||
wh, err := s.admissionWebhook(path, admissionWebhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatingWebhooks = append(validatingWebhooks, *wh)
|
||||
}
|
||||
|
||||
sort.Slice(validatingWebhooks, func(i, j int) bool {
|
||||
return validatingWebhooks[i].Name < validatingWebhooks[j].Name
|
||||
})
|
||||
|
||||
if len(validatingWebhooks) > 0 {
|
||||
return &admissionregistration.ValidatingWebhookConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: fmt.Sprintf("%s/%s", admissionregistration.GroupName, "v1beta1"),
|
||||
Kind: "ValidatingWebhookConfiguration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s.ValidatingWebhookConfigName,
|
||||
},
|
||||
Webhooks: validatingWebhooks,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Server) admissionWebhook(path string, wh *admission.Webhook) (*admissionregistration.Webhook, error) {
|
||||
if wh.NamespaceSelector == nil && s.Service != nil && len(s.Service.Namespace) > 0 {
|
||||
wh.NamespaceSelector = &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "control-plane",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
webhook := &admissionregistration.Webhook{
|
||||
Name: wh.GetName(),
|
||||
Rules: wh.Rules,
|
||||
FailurePolicy: wh.FailurePolicy,
|
||||
NamespaceSelector: wh.NamespaceSelector,
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
// The reason why we assign an empty byte array to CABundle is that
|
||||
// CABundle field will be updated by the Provisioner.
|
||||
CABundle: []byte{},
|
||||
},
|
||||
}
|
||||
cc, err := s.getClientConfigWithPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhook.ClientConfig = *cc
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// service creates a corev1.service object fronting the admission server.
|
||||
func (s *Server) service() runtime.Object {
|
||||
if s.Service == nil {
|
||||
return nil
|
||||
}
|
||||
svc := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s.Service.Name,
|
||||
Namespace: s.Service.Namespace,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: s.Service.Selectors,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
// When using service, kube-apiserver will send admission request to port 443.
|
||||
Port: 443,
|
||||
TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: s.Port},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return svc
|
||||
}
|
||||
94
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go
generated
vendored
94
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go
generated
vendored
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
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 webhook provides methods to build and bootstrap a webhook server.
|
||||
|
||||
Currently, it only supports admission webhooks. It will support CRD conversion webhooks in the near future.
|
||||
|
||||
Build webhooks
|
||||
|
||||
// mgr is the manager that runs the server.
|
||||
webhook1, err := NewWebhookBuilder().
|
||||
Name("foo.k8s.io").
|
||||
Mutating().
|
||||
Path("/mutating-pods").
|
||||
Operations(admissionregistrationv1beta1.Create).
|
||||
ForType(&corev1.Pod{}).
|
||||
WithManager(mgr).
|
||||
Handlers(mutatingHandler1, mutatingHandler2).
|
||||
Build()
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
webhook2, err := NewWebhookBuilder().
|
||||
Name("bar.k8s.io").
|
||||
Validating().
|
||||
Path("/validating-deployment").
|
||||
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
|
||||
ForType(&appsv1.Deployment{}).
|
||||
WithManager(mgr).
|
||||
Handlers(validatingHandler1).
|
||||
Build()
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Create a webhook server.
|
||||
|
||||
as, err := NewServer("baz-admission-server", mgr, ServerOptions{
|
||||
CertDir: "/tmp/cert",
|
||||
BootstrapOptions: &BootstrapOptions{
|
||||
Secret: &apitypes.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "foo-admission-server-secret",
|
||||
},
|
||||
Service: &Service{
|
||||
Namespace: "default",
|
||||
Name: "foo-admission-server-service",
|
||||
// Selectors should select the pods that runs this webhook server.
|
||||
Selectors: map[string]string{
|
||||
"app": "foo-admission-server",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Register the webhooks in the server.
|
||||
|
||||
err = as.Register(webhook1, webhook2)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Start the server by starting the manager
|
||||
|
||||
err := mrg.Start(signals.SetupSignalHandler())
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
*/
|
||||
package webhook
|
||||
|
||||
import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
)
|
||||
|
||||
var log = logf.KBLog.WithName("webhook")
|
||||
36
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go
generated
vendored
36
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
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 cert provides functions to manage certificates for webhookClientConfiguration.
|
||||
|
||||
Create a Provisioner with a CertWriter.
|
||||
|
||||
provisioner := Provisioner{
|
||||
CertWriter: admission.NewSecretCertWriter(admission.SecretCertWriterOptions{...}),
|
||||
}
|
||||
|
||||
Provision the certificates for the webhookClientConfig
|
||||
|
||||
err := provisioner.Provision(Options{
|
||||
ClientConfig: webhookClientConfig,
|
||||
Objects: []runtime.Object{mutatingWebhookConfiguration, validatingWebhookConfiguration}
|
||||
})
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
*/
|
||||
package cert
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
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 generator
|
||||
|
||||
// Artifacts hosts a private key, its corresponding serving certificate and
|
||||
// the CA certificate that signs the serving certificate.
|
||||
type Artifacts struct {
|
||||
// PEM encoded private key
|
||||
Key []byte
|
||||
// PEM encoded serving certificate
|
||||
Cert []byte
|
||||
// PEM encoded CA private key
|
||||
CAKey []byte
|
||||
// PEM encoded CA certificate
|
||||
CACert []byte
|
||||
}
|
||||
|
||||
// CertGenerator is an interface to provision the serving certificate.
|
||||
type CertGenerator interface {
|
||||
// Generate returns a Artifacts struct.
|
||||
Generate(CommonName string) (*Artifacts, error)
|
||||
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||
SetCA(caKey, caCert []byte)
|
||||
}
|
||||
30
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/doc.go
generated
vendored
30
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/doc.go
generated
vendored
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
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 generator provides an interface and implementation to provision certificates.
|
||||
|
||||
Create an instance of certGenerator.
|
||||
|
||||
cg := SelfSignedCertGenerator{}
|
||||
|
||||
Generate the certificates.
|
||||
certs, err := cg.Generate("foo.bar.com")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
*/
|
||||
package generator
|
||||
117
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/selfsigned.go
generated
vendored
117
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/selfsigned.go
generated
vendored
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
// ServiceToCommonName generates the CommonName for the certificate when using a k8s service.
|
||||
func ServiceToCommonName(serviceNamespace, serviceName string) string {
|
||||
return fmt.Sprintf("%s.%s.svc", serviceName, serviceNamespace)
|
||||
}
|
||||
|
||||
// SelfSignedCertGenerator implements the certGenerator interface.
|
||||
// It provisions self-signed certificates.
|
||||
type SelfSignedCertGenerator struct {
|
||||
caKey []byte
|
||||
caCert []byte
|
||||
}
|
||||
|
||||
var _ CertGenerator = &SelfSignedCertGenerator{}
|
||||
|
||||
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||
func (cp *SelfSignedCertGenerator) SetCA(caKey, caCert []byte) {
|
||||
cp.caKey = caKey
|
||||
cp.caCert = caCert
|
||||
}
|
||||
|
||||
// Generate creates and returns a CA certificate, certificate and
|
||||
// key for the server. serverKey and serverCert are used by the server
|
||||
// to establish trust for clients, CA certificate is used by the
|
||||
// client to verify the server authentication chain.
|
||||
// The cert will be valid for 365 days.
|
||||
func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, error) {
|
||||
var signingKey *rsa.PrivateKey
|
||||
var signingCert *x509.Certificate
|
||||
var valid bool
|
||||
var err error
|
||||
|
||||
valid, signingKey, signingCert = cp.validCACert()
|
||||
if !valid {
|
||||
signingKey, err = cert.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
|
||||
}
|
||||
signingCert, err = cert.NewSelfSignedCACert(cert.Config{CommonName: "webhook-cert-ca"}, signingKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the CA cert: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
key, err := cert.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the private key: %v", err)
|
||||
}
|
||||
signedCert, err := cert.NewSignedCert(
|
||||
cert.Config{
|
||||
CommonName: commonName,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
},
|
||||
key, signingCert, signingKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the cert: %v", err)
|
||||
}
|
||||
return &Artifacts{
|
||||
Key: cert.EncodePrivateKeyPEM(key),
|
||||
Cert: cert.EncodeCertPEM(signedCert),
|
||||
CAKey: cert.EncodePrivateKeyPEM(signingKey),
|
||||
CACert: cert.EncodeCertPEM(signingCert),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cp *SelfSignedCertGenerator) validCACert() (bool, *rsa.PrivateKey, *x509.Certificate) {
|
||||
if !ValidCACert(cp.caKey, cp.caCert, cp.caCert, "",
|
||||
time.Now().AddDate(1, 0, 0)) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
key, err := cert.ParsePrivateKeyPEM(cp.caKey)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
privateKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
certs, err := cert.ParseCertsPEM(cp.caCert)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
if len(certs) != 1 {
|
||||
return false, nil, nil
|
||||
}
|
||||
return true, privateKey, certs[0]
|
||||
}
|
||||
61
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/util.go
generated
vendored
61
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/util.go
generated
vendored
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ValidCACert think cert and key are valid if they meet the following requirements:
|
||||
// - key and cert are valid pair
|
||||
// - caCert is the root ca of cert
|
||||
// - cert is for dnsName
|
||||
// - cert won't expire before time
|
||||
func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool {
|
||||
if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 {
|
||||
return false
|
||||
}
|
||||
// Verify key and cert are valid pair
|
||||
_, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify cert is valid for at least 1 year.
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(caCert) {
|
||||
return false
|
||||
}
|
||||
block, _ := pem.Decode([]byte(cert))
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
c, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ops := x509.VerifyOptions{
|
||||
DNSName: dnsName,
|
||||
Roots: pool,
|
||||
CurrentTime: time,
|
||||
}
|
||||
_, err = c.Verify(ops)
|
||||
return err == nil
|
||||
}
|
||||
133
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go
generated
vendored
133
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go
generated
vendored
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
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 cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
|
||||
)
|
||||
|
||||
// Provisioner provisions certificates for webhook configurations and writes them to an output
|
||||
// destination - such as a Secret or local file. Provisioner can update the CA field of
|
||||
// certain resources with the CA of the certs.
|
||||
type Provisioner struct {
|
||||
// CertWriter knows how to persist the certificate.
|
||||
CertWriter writer.CertWriter
|
||||
}
|
||||
|
||||
// Options are options for provisioning the certificate.
|
||||
type Options struct {
|
||||
// ClientConfig is the WebhookClientCert that contains the information to generate
|
||||
// the certificate. The CA Certificate will be updated in the ClientConfig.
|
||||
// The updated ClientConfig will be used to inject into other runtime.Objects,
|
||||
// e.g. MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
|
||||
ClientConfig *admissionregistrationv1beta1.WebhookClientConfig
|
||||
// Objects are the objects that will use the ClientConfig above.
|
||||
Objects []runtime.Object
|
||||
}
|
||||
|
||||
// Provision provisions certificates for the WebhookClientConfig.
|
||||
// It ensures the cert and CA are valid and not expiring.
|
||||
// It updates the CABundle in the webhookClientConfig if necessary.
|
||||
// It inject the WebhookClientConfig into options.Objects.
|
||||
func (cp *Provisioner) Provision(options Options) (bool, error) {
|
||||
if cp.CertWriter == nil {
|
||||
return false, errors.New("CertWriter need to be set")
|
||||
}
|
||||
|
||||
dnsName, err := dnsNameFromClientConfig(options.ClientConfig)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
certs, changed, err := cp.CertWriter.EnsureCert(dnsName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
caBundle := options.ClientConfig.CABundle
|
||||
caCert := certs.CACert
|
||||
// TODO(mengqiy): limit the size of the CABundle by GC the old CA certificate
|
||||
// this is important since the max record size in etcd is 1MB (latest version is 1.5MB).
|
||||
if !bytes.Contains(caBundle, caCert) {
|
||||
// Ensure the CA bundle in the webhook configuration has the signing CA.
|
||||
options.ClientConfig.CABundle = append(caBundle, caCert...)
|
||||
changed = true
|
||||
}
|
||||
return changed, cp.inject(options.ClientConfig, options.Objects)
|
||||
}
|
||||
|
||||
// Inject the ClientConfig to the objects.
|
||||
// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
|
||||
func (cp *Provisioner) inject(cc *admissionregistrationv1beta1.WebhookClientConfig, objs []runtime.Object) error {
|
||||
if cc == nil {
|
||||
return nil
|
||||
}
|
||||
for i := range objs {
|
||||
switch typed := objs[i].(type) {
|
||||
case *admissionregistrationv1beta1.MutatingWebhookConfiguration:
|
||||
injectForEachWebhook(cc, typed.Webhooks)
|
||||
case *admissionregistrationv1beta1.ValidatingWebhookConfiguration:
|
||||
injectForEachWebhook(cc, typed.Webhooks)
|
||||
default:
|
||||
return fmt.Errorf("%#v is not supported for injecting a webhookClientConfig",
|
||||
objs[i].GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
}
|
||||
return cp.CertWriter.Inject(objs...)
|
||||
}
|
||||
|
||||
func injectForEachWebhook(
|
||||
cc *admissionregistrationv1beta1.WebhookClientConfig,
|
||||
webhooks []admissionregistrationv1beta1.Webhook) {
|
||||
for i := range webhooks {
|
||||
// only replacing the CA bundle to preserve the path in the WebhookClientConfig
|
||||
webhooks[i].ClientConfig.CABundle = cc.CABundle
|
||||
}
|
||||
}
|
||||
|
||||
func dnsNameFromClientConfig(config *admissionregistrationv1beta1.WebhookClientConfig) (string, error) {
|
||||
if config == nil {
|
||||
return "", errors.New("clientConfig should not be empty")
|
||||
}
|
||||
if config.Service != nil && config.URL != nil {
|
||||
return "", fmt.Errorf("service and URL can't be set at the same time in a webhook: %v", config)
|
||||
}
|
||||
if config.Service == nil && config.URL == nil {
|
||||
return "", fmt.Errorf("one of service and URL need to be set in a webhook: %v", config)
|
||||
}
|
||||
if config.Service != nil {
|
||||
return generator.ServiceToCommonName(config.Service.Namespace, config.Service.Name), nil
|
||||
}
|
||||
u, err := url.Parse(*config.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
host, _, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return u.Host, nil
|
||||
}
|
||||
return host, err
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
/*
|
||||
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 atomic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileNameLength = 255
|
||||
maxPathLength = 4096
|
||||
)
|
||||
|
||||
// AtomicWriter handles atomically projecting content for a set of files into
|
||||
// a target directory.
|
||||
//
|
||||
// Note:
|
||||
//
|
||||
// 1. AtomicWriter reserves the set of pathnames starting with `..`.
|
||||
// 2. AtomicWriter offers no concurrency guarantees and must be synchronized
|
||||
// by the caller.
|
||||
//
|
||||
// The visible files in this volume are symlinks to files in the writer's data
|
||||
// directory. Actual files are stored in a hidden timestamped directory which
|
||||
// is symlinked to by the data directory. The timestamped directory and
|
||||
// data directory symlink are created in the writer's target dir. This scheme
|
||||
// allows the files to be atomically updated by changing the target of the
|
||||
// data directory symlink.
|
||||
//
|
||||
// Consumers of the target directory can monitor the ..data symlink using
|
||||
// inotify or fanotify to receive events when the content in the volume is
|
||||
// updated.
|
||||
type AtomicWriter struct {
|
||||
targetDir string
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
type FileProjection struct {
|
||||
Data []byte
|
||||
Mode int32
|
||||
}
|
||||
|
||||
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
||||
// target directory, or returns an error if the target directory does not exist.
|
||||
func NewAtomicWriter(targetDir string, log logr.Logger) (*AtomicWriter, error) {
|
||||
_, err := os.Stat(targetDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriter{targetDir: targetDir, log: log}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
dataDirName = "..data"
|
||||
newDataDirName = "..data_tmp"
|
||||
)
|
||||
|
||||
// Write does an atomic projection of the given payload into the writer's target
|
||||
// directory. Input paths must not begin with '..'.
|
||||
//
|
||||
// The Write algorithm is:
|
||||
//
|
||||
// 1. The payload is validated; if the payload is invalid, the function returns
|
||||
// 2. The current timestamped directory is detected by reading the data directory
|
||||
// symlink
|
||||
// 3. The old version of the volume is walked to determine whether any
|
||||
// portion of the payload was deleted and is still present on disk.
|
||||
// 4. The data in the current timestamped directory is compared to the projected
|
||||
// data to determine if an update is required.
|
||||
// 5. A new timestamped dir is created
|
||||
// 6. The payload is written to the new timestamped directory
|
||||
// 7. Symlinks and directory for new user-visible files are created (if needed).
|
||||
//
|
||||
// For example, consider the files:
|
||||
// <target-dir>/podName
|
||||
// <target-dir>/user/labels
|
||||
// <target-dir>/k8s/annotations
|
||||
//
|
||||
// The user visible files are symbolic links into the internal data directory:
|
||||
// <target-dir>/podName -> ..data/podName
|
||||
// <target-dir>/usr -> ..data/usr
|
||||
// <target-dir>/k8s -> ..data/k8s
|
||||
//
|
||||
// The data directory itself is a link to a timestamped directory with
|
||||
// the real data:
|
||||
// <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
|
||||
// 8. A symlink to the new timestamped directory ..data_tmp is created that will
|
||||
// become the new data directory
|
||||
// 9. The new data directory symlink is renamed to the data directory; rename is atomic
|
||||
// 10. Old paths are removed from the user-visible portion of the target directory
|
||||
// 11. The previous timestamped directory is removed, if it exists
|
||||
func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
||||
// (1)
|
||||
cleanPayload, err := validatePayload(payload)
|
||||
if err != nil {
|
||||
w.log.Error(err, "invalid payload")
|
||||
return err
|
||||
}
|
||||
|
||||
// (2)
|
||||
dataDirPath := path.Join(w.targetDir, dataDirName)
|
||||
oldTsDir, err := os.Readlink(dataDirPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
w.log.Error(err, "unable to read link for data directory")
|
||||
return err
|
||||
}
|
||||
// although Readlink() returns "" on err, don't be fragile by relying on it (since it's not specified in docs)
|
||||
// empty oldTsDir indicates that it didn't exist
|
||||
oldTsDir = ""
|
||||
}
|
||||
oldTsPath := path.Join(w.targetDir, oldTsDir)
|
||||
|
||||
var pathsToRemove sets.String
|
||||
// if there was no old version, there's nothing to remove
|
||||
if len(oldTsDir) != 0 {
|
||||
// (3)
|
||||
pathsToRemove, err = w.pathsToRemove(cleanPayload, oldTsPath)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to determine user-visible files to remove")
|
||||
return err
|
||||
}
|
||||
|
||||
// (4)
|
||||
if should, err := shouldWritePayload(cleanPayload, oldTsPath); err != nil {
|
||||
w.log.Error(err, "unable to determine whether payload should be written to disk")
|
||||
return err
|
||||
} else if !should && len(pathsToRemove) == 0 {
|
||||
w.log.V(1).Info("no update required for target directory", "directory", w.targetDir)
|
||||
return nil
|
||||
} else {
|
||||
w.log.V(1).Info("write required for target directory", "directory", w.targetDir)
|
||||
}
|
||||
}
|
||||
|
||||
// (5)
|
||||
tsDir, err := w.newTimestampDir()
|
||||
if err != nil {
|
||||
w.log.Error(err, "error creating new ts data directory")
|
||||
return err
|
||||
}
|
||||
tsDirName := filepath.Base(tsDir)
|
||||
|
||||
// (6)
|
||||
if err = w.writePayloadToDir(cleanPayload, tsDir); err != nil {
|
||||
w.log.Error(err, "unable to write payload to ts data directory", "ts directory", tsDir)
|
||||
return err
|
||||
} else {
|
||||
w.log.V(1).Info("performed write of new data to ts data directory", "ts directory", tsDir)
|
||||
}
|
||||
|
||||
// (7)
|
||||
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
||||
w.log.Error(err, "unable to create visible symlinks in target directory", "target directory", w.targetDir)
|
||||
return err
|
||||
}
|
||||
|
||||
// (8)
|
||||
newDataDirPath := path.Join(w.targetDir, newDataDirName)
|
||||
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
||||
os.RemoveAll(tsDir)
|
||||
w.log.Error(err, "unable to create symbolic link for atomic update")
|
||||
return err
|
||||
}
|
||||
|
||||
// (9)
|
||||
if runtime.GOOS == "windows" {
|
||||
os.Remove(dataDirPath)
|
||||
err = os.Symlink(tsDirName, dataDirPath)
|
||||
os.Remove(newDataDirPath)
|
||||
} else {
|
||||
err = os.Rename(newDataDirPath, dataDirPath)
|
||||
}
|
||||
if err != nil {
|
||||
os.Remove(newDataDirPath)
|
||||
os.RemoveAll(tsDir)
|
||||
w.log.Error(err, "unable to rename symbolic link for data directory", "data directory", newDataDirPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// (10)
|
||||
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
||||
w.log.Error(err, "unable to remove old visible symlinks")
|
||||
return err
|
||||
}
|
||||
|
||||
// (11)
|
||||
if len(oldTsDir) > 0 {
|
||||
if err = os.RemoveAll(oldTsPath); err != nil {
|
||||
w.log.Error(err, "unable to remove old data directory", "data directory", oldTsDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayload returns an error if any path in the payload returns a copy of the payload with the paths cleaned.
|
||||
func validatePayload(payload map[string]FileProjection) (map[string]FileProjection, error) {
|
||||
cleanPayload := make(map[string]FileProjection)
|
||||
for k, content := range payload {
|
||||
if err := validatePath(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanPayload[filepath.Clean(k)] = content
|
||||
}
|
||||
|
||||
return cleanPayload, nil
|
||||
}
|
||||
|
||||
// validatePath validates a single path, returning an error if the path is
|
||||
// invalid. paths may not:
|
||||
//
|
||||
// 1. be absolute
|
||||
// 2. contain '..' as an element
|
||||
// 3. start with '..'
|
||||
// 4. contain filenames larger than 255 characters
|
||||
// 5. be longer than 4096 characters
|
||||
func validatePath(targetPath string) error {
|
||||
// TODO: somehow unify this with the similar api validation,
|
||||
// validateVolumeSourcePath; the error semantics are just different enough
|
||||
// from this that it was time-prohibitive trying to find the right
|
||||
// refactoring to re-use.
|
||||
if targetPath == "" {
|
||||
return fmt.Errorf("invalid path: must not be empty: %q", targetPath)
|
||||
}
|
||||
if path.IsAbs(targetPath) {
|
||||
return fmt.Errorf("invalid path: must be relative path: %s", targetPath)
|
||||
}
|
||||
|
||||
if len(targetPath) > maxPathLength {
|
||||
return fmt.Errorf("invalid path: must be less than or equal to %d characters", maxPathLength)
|
||||
}
|
||||
|
||||
items := strings.Split(targetPath, string(os.PathSeparator))
|
||||
for _, item := range items {
|
||||
if item == ".." {
|
||||
return fmt.Errorf("invalid path: must not contain '..': %s", targetPath)
|
||||
}
|
||||
if len(item) > maxFileNameLength {
|
||||
return fmt.Errorf("invalid path: filenames must be less than or equal to %d characters", maxFileNameLength)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
|
||||
return fmt.Errorf("invalid path: must not start with '..': %s", targetPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldWritePayload returns whether the payload should be written to disk.
|
||||
func shouldWritePayload(payload map[string]FileProjection, oldTsDir string) (bool, error) {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
shouldWrite, err := shouldWriteFile(path.Join(oldTsDir, userVisiblePath), fileProjection.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if shouldWrite {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// shouldWriteFile returns whether a new version of a file should be written to disk.
|
||||
func shouldWriteFile(path string, content []byte) (bool, error) {
|
||||
_, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentOnFs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return (bytes.Compare(content, contentOnFs) != 0), nil
|
||||
}
|
||||
|
||||
// pathsToRemove walks the current version of the data directory and
|
||||
// determines which paths should be removed (if any) after the payload is
|
||||
// written to the target directory.
|
||||
func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir string) (sets.String, error) {
|
||||
paths := sets.NewString()
|
||||
visitor := func(path string, info os.FileInfo, err error) error {
|
||||
relativePath := strings.TrimPrefix(path, oldTsDir)
|
||||
relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator))
|
||||
if relativePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
paths.Insert(relativePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(oldTsDir, visitor)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.log.V(1).Info("current paths", "target directory", w.targetDir, "paths", paths.List())
|
||||
|
||||
newPaths := sets.NewString()
|
||||
for file := range payload {
|
||||
// add all subpaths for the payload to the set of new paths
|
||||
// to avoid attempting to remove non-empty dirs
|
||||
for subPath := file; subPath != ""; {
|
||||
newPaths.Insert(subPath)
|
||||
subPath, _ = filepath.Split(subPath)
|
||||
subPath = strings.TrimSuffix(subPath, string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
w.log.V(1).Info("new paths", "target directory", w.targetDir, "paths", newPaths.List())
|
||||
|
||||
result := paths.Difference(newPaths)
|
||||
w.log.V(1).Info("paths to remove", "target directory", w.targetDir, "paths", result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTimestampDir creates a new timestamp directory
|
||||
func (w *AtomicWriter) newTimestampDir() (string, error) {
|
||||
tsDir, err := ioutil.TempDir(w.targetDir, time.Now().UTC().Format("..2006_01_02_15_04_05."))
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to create new temp directory")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 0755 permissions are needed to allow 'group' and 'other' to recurse the
|
||||
// directory tree. do a chmod here to ensure that permissions are set correctly
|
||||
// regardless of the process' umask.
|
||||
err = os.Chmod(tsDir, 0755)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to set mode on new temp directory")
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tsDir, nil
|
||||
}
|
||||
|
||||
// writePayloadToDir writes the given payload to the given directory. The
|
||||
// directory must exist.
|
||||
func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir string) error {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
content := fileProjection.Data
|
||||
mode := os.FileMode(fileProjection.Mode)
|
||||
fullPath := path.Join(dir, userVisiblePath)
|
||||
baseDir, _ := filepath.Split(fullPath)
|
||||
|
||||
err := os.MkdirAll(baseDir, os.ModePerm)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to create directory", "directory", baseDir)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(fullPath, content, mode)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to write file", "file", fullPath, "mode", mode)
|
||||
return err
|
||||
}
|
||||
// Chmod is needed because ioutil.WriteFile() ends up calling
|
||||
// open(2) to create the file, so the final mode used is "mode &
|
||||
// ~umask". But we want to make sure the specified mode is used
|
||||
// in the file no matter what the umask is.
|
||||
err = os.Chmod(fullPath, mode)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to write file", "file", fullPath, "mode", mode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createUserVisibleFiles creates the relative symlinks for all the
|
||||
// files configured in the payload. If the directory in a file path does not
|
||||
// exist, it is created.
|
||||
//
|
||||
// Viz:
|
||||
// For files: "bar", "foo/bar", "baz/bar", "foo/baz/blah"
|
||||
// the following symlinks are created:
|
||||
// bar -> ..data/bar
|
||||
// foo -> ..data/foo
|
||||
// baz -> ..data/baz
|
||||
func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error {
|
||||
for userVisiblePath := range payload {
|
||||
slashpos := strings.Index(userVisiblePath, string(os.PathSeparator))
|
||||
if slashpos == -1 {
|
||||
slashpos = len(userVisiblePath)
|
||||
}
|
||||
linkname := userVisiblePath[:slashpos]
|
||||
_, err := os.Readlink(path.Join(w.targetDir, linkname))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// The link into the data directory for this path doesn't exist; create it
|
||||
visibleFile := path.Join(w.targetDir, linkname)
|
||||
dataDirFile := path.Join(dataDirName, linkname)
|
||||
|
||||
err = os.Symlink(dataDirFile, visibleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeUserVisiblePaths removes the set of paths from the user-visible
|
||||
// portion of the writer's target directory.
|
||||
func (w *AtomicWriter) removeUserVisiblePaths(paths sets.String) error {
|
||||
ps := string(os.PathSeparator)
|
||||
var lasterr error
|
||||
for p := range paths {
|
||||
// only remove symlinks from the volume root directory (i.e. items that don't contain '/')
|
||||
if strings.Contains(p, ps) {
|
||||
continue
|
||||
}
|
||||
if err := os.Remove(path.Join(w.targetDir, p)); err != nil {
|
||||
w.log.Error(err, "unable to prune old user-visible path", "path", p)
|
||||
lasterr = err
|
||||
}
|
||||
}
|
||||
|
||||
return lasterr
|
||||
}
|
||||
137
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go
generated
vendored
137
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go
generated
vendored
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
)
|
||||
|
||||
const (
|
||||
// CAKeyName is the name of the CA private key
|
||||
CAKeyName = "ca-key.pem"
|
||||
// CACertName is the name of the CA certificate
|
||||
CACertName = "ca-cert.pem"
|
||||
// ServerKeyName is the name of the server private key
|
||||
ServerKeyName = "key.pem"
|
||||
// ServerCertName is the name of the serving certificate
|
||||
ServerCertName = "cert.pem"
|
||||
)
|
||||
|
||||
// CertWriter provides method to handle webhooks.
|
||||
type CertWriter interface {
|
||||
// EnsureCert provisions the cert for the webhookClientConfig.
|
||||
EnsureCert(dnsName string) (*generator.Artifacts, bool, error)
|
||||
// Inject injects the necessary information given the objects.
|
||||
// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
|
||||
Inject(objs ...runtime.Object) error
|
||||
}
|
||||
|
||||
// handleCommon ensures the given webhook has a proper certificate.
|
||||
// It uses the given certReadWriter to read and (or) write the certificate.
|
||||
func handleCommon(dnsName string, ch certReadWriter) (*generator.Artifacts, bool, error) {
|
||||
if len(dnsName) == 0 {
|
||||
return nil, false, errors.New("dnsName should not be empty")
|
||||
}
|
||||
if ch == nil {
|
||||
return nil, false, errors.New("certReaderWriter should not be nil")
|
||||
}
|
||||
|
||||
certs, changed, err := createIfNotExists(ch)
|
||||
if err != nil {
|
||||
return nil, changed, err
|
||||
}
|
||||
|
||||
// Recreate the cert if it's invalid.
|
||||
valid := validCert(certs, dnsName)
|
||||
if !valid {
|
||||
log.Info("cert is invalid or expiring, regenerating a new one")
|
||||
certs, err = ch.overwrite()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
return certs, changed, nil
|
||||
}
|
||||
|
||||
func createIfNotExists(ch certReadWriter) (*generator.Artifacts, bool, error) {
|
||||
// Try to read first
|
||||
certs, err := ch.read()
|
||||
if isNotFound(err) {
|
||||
// Create if not exists
|
||||
certs, err = ch.write()
|
||||
switch {
|
||||
// This may happen if there is another racer.
|
||||
case isAlreadyExists(err):
|
||||
certs, err = ch.read()
|
||||
return certs, true, err
|
||||
default:
|
||||
return certs, true, err
|
||||
}
|
||||
}
|
||||
return certs, false, err
|
||||
}
|
||||
|
||||
// certReadWriter provides methods for reading and writing certificates.
|
||||
type certReadWriter interface {
|
||||
// read reads a webhook name and returns the certs for it.
|
||||
read() (*generator.Artifacts, error)
|
||||
// write writes the certs and return the certs it wrote.
|
||||
write() (*generator.Artifacts, error)
|
||||
// overwrite overwrites the existing certs and return the certs it wrote.
|
||||
overwrite() (*generator.Artifacts, error)
|
||||
}
|
||||
|
||||
func validCert(certs *generator.Artifacts, dnsName string) bool {
|
||||
if certs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify key and cert are valid pair
|
||||
_, err := tls.X509KeyPair(certs.Cert, certs.Key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time.
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(certs.CACert) {
|
||||
return false
|
||||
}
|
||||
block, _ := pem.Decode([]byte(certs.Cert))
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ops := x509.VerifyOptions{
|
||||
DNSName: dnsName,
|
||||
Roots: pool,
|
||||
CurrentTime: time.Now().AddDate(0, 6, 0),
|
||||
}
|
||||
_, err = cert.Verify(ops)
|
||||
return err == nil
|
||||
}
|
||||
64
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go
generated
vendored
64
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
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 writer provides method to provision and persist the certificates.
|
||||
|
||||
It will create the certificates if they don't exist.
|
||||
It will ensure the certificates are valid and not expiring. If not, it will recreate them.
|
||||
|
||||
Create a CertWriter that can write the certificate to secret
|
||||
|
||||
writer, err := NewSecretCertWriter(SecretCertWriterOptions{
|
||||
Secret: types.NamespacedName{Namespace: "foo", Name: "bar"},
|
||||
Client: client,
|
||||
})
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Create a CertWriter that can write the certificate to the filesystem.
|
||||
|
||||
writer, err := NewFSCertWriter(FSCertWriterOptions{
|
||||
Path: "path/to/cert/",
|
||||
})
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Provision the certificates using the CertWriter. The certificate will be available in the desired secret or
|
||||
the desired path.
|
||||
|
||||
// writer can be either one of the CertWriters created above
|
||||
certs, changed, err := writer.EnsureCerts("admissionwebhook.k8s.io", false)
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Inject necessary information given the objects.
|
||||
|
||||
err = writer.Inject(objs...)
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
*/
|
||||
package writer
|
||||
|
||||
import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
)
|
||||
|
||||
var log = logf.KBLog.WithName("admission").WithName("cert").WithName("writer")
|
||||
43
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go
generated
vendored
43
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go
generated
vendored
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
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 writer
|
||||
|
||||
type notFoundError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e notFoundError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
_, ok := err.(notFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type alreadyExistError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e alreadyExistError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func isAlreadyExists(err error) bool {
|
||||
_, ok := err.(alreadyExistError)
|
||||
return ok
|
||||
}
|
||||
216
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go
generated
vendored
216
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go
generated
vendored
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic"
|
||||
)
|
||||
|
||||
// fsCertWriter provisions the certificate by reading and writing to the filesystem.
|
||||
type fsCertWriter struct {
|
||||
// dnsName is the DNS name that the certificate is for.
|
||||
dnsName string
|
||||
|
||||
*FSCertWriterOptions
|
||||
}
|
||||
|
||||
// FSCertWriterOptions are options for constructing a FSCertWriter.
|
||||
type FSCertWriterOptions struct {
|
||||
// certGenerator generates the certificates.
|
||||
CertGenerator generator.CertGenerator
|
||||
// path is the directory that the certificate and private key and CA certificate will be written.
|
||||
Path string
|
||||
}
|
||||
|
||||
var _ CertWriter = &fsCertWriter{}
|
||||
|
||||
func (ops *FSCertWriterOptions) setDefaults() {
|
||||
if ops.CertGenerator == nil {
|
||||
ops.CertGenerator = &generator.SelfSignedCertGenerator{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ops *FSCertWriterOptions) validate() error {
|
||||
if len(ops.Path) == 0 {
|
||||
return errors.New("path must be set in FSCertWriterOptions")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFSCertWriter constructs a CertWriter that persists the certificate on filesystem.
|
||||
func NewFSCertWriter(ops FSCertWriterOptions) (CertWriter, error) {
|
||||
ops.setDefaults()
|
||||
err := ops.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fsCertWriter{
|
||||
FSCertWriterOptions: &ops,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates in the filesystem.
|
||||
func (f *fsCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
|
||||
// create or refresh cert and write it to fs
|
||||
f.dnsName = dnsName
|
||||
return handleCommon(f.dnsName, f)
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) write() (*generator.Artifacts, error) {
|
||||
return f.doWrite()
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) overwrite() (*generator.Artifacts, error) {
|
||||
return f.doWrite()
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) doWrite() (*generator.Artifacts, error) {
|
||||
certs, err := f.CertGenerator.Generate(f.dnsName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// AtomicWriter's algorithm only manages files using symbolic link.
|
||||
// If a file is not a symbolic link, will ignore the update for it.
|
||||
// We want to cleanup for AtomicWriter by removing old files that are not symbolic links.
|
||||
err = prepareToWrite(f.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aw, err := atomic.NewAtomicWriter(f.Path, log.WithName("atomic-writer").
|
||||
WithValues("task", "processing webhook"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = aw.Write(certToProjectionMap(certs))
|
||||
return certs, err
|
||||
}
|
||||
|
||||
// prepareToWrite ensures it directory is compatible with the atomic.Writer library.
|
||||
func prepareToWrite(dir string) error {
|
||||
_, err := os.Stat(dir)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
log.Info("cert directory doesn't exist, creating", "directory", dir)
|
||||
// TODO: figure out if we can reduce the permission. (Now it's 0777)
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create dir: %v", dir)
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
filenames := []string{CAKeyName, CACertName, ServerCertName, ServerKeyName}
|
||||
for _, f := range filenames {
|
||||
abspath := path.Join(dir, f)
|
||||
_, err := os.Stat(abspath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Error(err, "unable to stat file", "file", abspath)
|
||||
}
|
||||
_, err = os.Readlink(abspath)
|
||||
// if it's not a symbolic link
|
||||
if err != nil {
|
||||
err = os.Remove(abspath)
|
||||
if err != nil {
|
||||
log.Error(err, "unable to remove old file", "file", abspath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) read() (*generator.Artifacts, error) {
|
||||
if err := ensureExist(f.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caKeyBytes, err := ioutil.ReadFile(path.Join(f.Path, CAKeyName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertBytes, err := ioutil.ReadFile(path.Join(f.Path, CACertName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerCertName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerKeyName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &generator.Artifacts{
|
||||
CAKey: caKeyBytes,
|
||||
CACert: caCertBytes,
|
||||
Cert: certBytes,
|
||||
Key: keyBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ensureExist(dir string) error {
|
||||
filenames := []string{CAKeyName, CACertName, ServerCertName, ServerKeyName}
|
||||
for _, filename := range filenames {
|
||||
_, err := os.Stat(path.Join(dir, filename))
|
||||
switch {
|
||||
case err == nil:
|
||||
continue
|
||||
case os.IsNotExist(err):
|
||||
return notFoundError{err}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func certToProjectionMap(cert *generator.Artifacts) map[string]atomic.FileProjection {
|
||||
// TODO: figure out if we can reduce the permission. (Now it's 0666)
|
||||
return map[string]atomic.FileProjection{
|
||||
CAKeyName: {
|
||||
Data: cert.CAKey,
|
||||
Mode: 0666,
|
||||
},
|
||||
CACertName: {
|
||||
Data: cert.CACert,
|
||||
Mode: 0666,
|
||||
},
|
||||
ServerCertName: {
|
||||
Data: cert.Cert,
|
||||
Mode: 0666,
|
||||
},
|
||||
ServerKeyName: {
|
||||
Data: cert.Key,
|
||||
Mode: 0666,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) Inject(objs ...runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
184
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go
generated
vendored
184
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go
generated
vendored
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
)
|
||||
|
||||
// secretCertWriter provisions the certificate by reading and writing to the k8s secrets.
|
||||
type secretCertWriter struct {
|
||||
*SecretCertWriterOptions
|
||||
|
||||
// dnsName is the DNS name that the certificate is for.
|
||||
dnsName string
|
||||
}
|
||||
|
||||
// SecretCertWriterOptions is options for constructing a secretCertWriter.
|
||||
type SecretCertWriterOptions struct {
|
||||
// client talks to a kubernetes cluster for creating the secret.
|
||||
Client client.Client
|
||||
// certGenerator generates the certificates.
|
||||
CertGenerator generator.CertGenerator
|
||||
// secret points the secret that contains certificates that written by the CertWriter.
|
||||
Secret *types.NamespacedName
|
||||
}
|
||||
|
||||
var _ CertWriter = &secretCertWriter{}
|
||||
|
||||
func (ops *SecretCertWriterOptions) setDefaults() {
|
||||
if ops.CertGenerator == nil {
|
||||
ops.CertGenerator = &generator.SelfSignedCertGenerator{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ops *SecretCertWriterOptions) validate() error {
|
||||
if ops.Client == nil {
|
||||
return errors.New("client must be set in SecretCertWriterOptions")
|
||||
}
|
||||
if ops.Secret == nil {
|
||||
return errors.New("secret must be set in SecretCertWriterOptions")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSecretCertWriter constructs a CertWriter that persists the certificate in a k8s secret.
|
||||
func NewSecretCertWriter(ops SecretCertWriterOptions) (CertWriter, error) {
|
||||
ops.setDefaults()
|
||||
err := ops.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &secretCertWriter{
|
||||
SecretCertWriterOptions: &ops,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates to a k8s secret.
|
||||
func (s *secretCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
|
||||
// Create or refresh the certs based on clientConfig
|
||||
s.dnsName = dnsName
|
||||
return handleCommon(s.dnsName, s)
|
||||
}
|
||||
|
||||
var _ certReadWriter = &secretCertWriter{}
|
||||
|
||||
func (s *secretCertWriter) buildSecret() (*corev1.Secret, *generator.Artifacts, error) {
|
||||
certs, err := s.CertGenerator.Generate(s.dnsName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
secret := certsToSecret(certs, *s.Secret)
|
||||
return secret, certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) write() (*generator.Artifacts, error) {
|
||||
secret, certs, err := s.buildSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.Client.Create(nil, secret)
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
return nil, alreadyExistError{err}
|
||||
}
|
||||
return certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) overwrite() (
|
||||
*generator.Artifacts, error) {
|
||||
secret, certs, err := s.buildSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.Client.Update(nil, secret)
|
||||
return certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) read() (*generator.Artifacts, error) {
|
||||
secret := &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
}
|
||||
err := s.Client.Get(nil, *s.Secret, secret)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, notFoundError{err}
|
||||
}
|
||||
certs := secretToCerts(secret)
|
||||
if certs != nil {
|
||||
// Store the CA for next usage.
|
||||
s.CertGenerator.SetCA(certs.CAKey, certs.CACert)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
func secretToCerts(secret *corev1.Secret) *generator.Artifacts {
|
||||
if secret.Data == nil {
|
||||
return nil
|
||||
}
|
||||
return &generator.Artifacts{
|
||||
CAKey: secret.Data[CAKeyName],
|
||||
CACert: secret.Data[CACertName],
|
||||
Cert: secret.Data[ServerCertName],
|
||||
Key: secret.Data[ServerKeyName],
|
||||
}
|
||||
}
|
||||
|
||||
func certsToSecret(certs *generator.Artifacts, sec types.NamespacedName) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: sec.Namespace,
|
||||
Name: sec.Name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
CAKeyName: certs.CAKey,
|
||||
CACertName: certs.CACert,
|
||||
ServerKeyName: certs.Key,
|
||||
ServerCertName: certs.Cert,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Inject sets the ownerReference in the secret.
|
||||
func (s *secretCertWriter) Inject(objs ...runtime.Object) error {
|
||||
// TODO: figure out how to get the UID
|
||||
//for i := range objs {
|
||||
// accessor, err := meta.Accessor(objs[i])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// err = controllerutil.SetControllerReference(accessor, s.sec, scheme.Scheme)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
//return s.client.Update(context.Background(), s.sec)
|
||||
return nil
|
||||
}
|
||||
308
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go
generated
vendored
308
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go
generated
vendored
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
|
||||
atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
|
||||
)
|
||||
|
||||
// default interval for checking cert is 90 days (~3 months)
|
||||
var defaultCertRefreshInterval = 3 * 30 * 24 * time.Hour
|
||||
|
||||
// ServerOptions are options for configuring an admission webhook server.
|
||||
type ServerOptions struct {
|
||||
// Port is the port number that the server will serve.
|
||||
// It will be defaulted to 443 if unspecified.
|
||||
Port int32
|
||||
|
||||
// CertDir is the directory that contains the server key and certificate.
|
||||
// If using FSCertWriter in Provisioner, the server itself will provision the certificate and
|
||||
// store it in this directory.
|
||||
// If using SecretCertWriter in Provisioner, the server will provision the certificate in a secret,
|
||||
// the user is responsible to mount the secret to the this location for the server to consume.
|
||||
CertDir string
|
||||
|
||||
// Client is a client defined in controller-runtime instead of a client-go client.
|
||||
// It knows how to talk to a kubernetes cluster.
|
||||
// Client will be injected by the manager if not set.
|
||||
Client client.Client
|
||||
|
||||
// DisableWebhookConfigInstaller controls if the server will automatically create webhook related objects
|
||||
// during bootstrapping. e.g. webhookConfiguration, service and secret.
|
||||
// If false, the server will install the webhook config objects. It is defaulted to false.
|
||||
DisableWebhookConfigInstaller *bool
|
||||
|
||||
// BootstrapOptions contains the options for bootstrapping the admission server.
|
||||
*BootstrapOptions
|
||||
}
|
||||
|
||||
// BootstrapOptions are options for bootstrapping an admission webhook server.
|
||||
type BootstrapOptions struct {
|
||||
// MutatingWebhookConfigName is the name that used for creating the MutatingWebhookConfiguration object.
|
||||
MutatingWebhookConfigName string
|
||||
// ValidatingWebhookConfigName is the name that used for creating the ValidatingWebhookConfiguration object.
|
||||
ValidatingWebhookConfigName string
|
||||
|
||||
// Secret is the location for storing the certificate for the admission server.
|
||||
// The server should have permission to create a secret in the namespace.
|
||||
// This is optional. If unspecified, it will write to the filesystem.
|
||||
// It the secret already exists and is different from the desired, it will be replaced.
|
||||
Secret *apitypes.NamespacedName
|
||||
|
||||
// Deprecated: Writer will not be used anywhere.
|
||||
Writer io.Writer
|
||||
|
||||
// Service is k8s service fronting the webhook server pod(s).
|
||||
// This field is optional. But one and only one of Service and Host need to be set.
|
||||
// This maps to field .webhooks.getClientConfig.service
|
||||
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L260
|
||||
Service *Service
|
||||
// Host is the host name of .webhooks.clientConfig.url
|
||||
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L250
|
||||
// This field is optional. But one and only one of Service and Host need to be set.
|
||||
// If neither Service nor Host is unspecified, Host will be defaulted to "localhost".
|
||||
Host *string
|
||||
|
||||
// certProvisioner is constructed using certGenerator and certWriter
|
||||
certProvisioner *cert.Provisioner // nolint: structcheck
|
||||
|
||||
// err will be non-nil if there is an error occur during initialization.
|
||||
err error // nolint: structcheck
|
||||
}
|
||||
|
||||
// Service contains information for creating a service
|
||||
type Service struct {
|
||||
// Name of the service
|
||||
Name string
|
||||
// Namespace of the service
|
||||
Namespace string
|
||||
// Selectors is the selector of the service.
|
||||
// This must select the pods that runs this webhook server.
|
||||
Selectors map[string]string
|
||||
}
|
||||
|
||||
// Server is an admission webhook server that can serve traffic and
|
||||
// generates related k8s resources for deploying.
|
||||
type Server struct {
|
||||
// Name is the name of server
|
||||
Name string
|
||||
|
||||
// ServerOptions contains options for configuring the admission server.
|
||||
ServerOptions
|
||||
|
||||
sMux *http.ServeMux
|
||||
// registry maps a path to a http.Handler.
|
||||
registry map[string]Webhook
|
||||
|
||||
// mutatingWebhookConfiguration and validatingWebhookConfiguration are populated during server bootstrapping.
|
||||
// They can be nil, if there is no webhook registered under it.
|
||||
webhookConfigurations []runtime.Object
|
||||
|
||||
// manager is the manager that this webhook server will be registered.
|
||||
manager manager.Manager
|
||||
|
||||
// httpServer is the actual server that serves the traffic.
|
||||
httpServer *http.Server
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Webhook defines the basics that a webhook should support.
|
||||
type Webhook interface {
|
||||
// GetName returns the name of the webhook.
|
||||
GetName() string
|
||||
// GetPath returns the path that the webhook registered.
|
||||
GetPath() string
|
||||
// GetType returns the Type of the webhook.
|
||||
// e.g. mutating or validating
|
||||
GetType() types.WebhookType
|
||||
// Handler returns a http.Handler for the webhook.
|
||||
Handler() http.Handler
|
||||
// Validate validates if the webhook itself is valid.
|
||||
// If invalid, a non-nil error will be returned.
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// NewServer creates a new admission webhook server.
|
||||
func NewServer(name string, mgr manager.Manager, options ServerOptions) (*Server, error) {
|
||||
as := &Server{
|
||||
Name: name,
|
||||
sMux: http.NewServeMux(),
|
||||
registry: map[string]Webhook{},
|
||||
ServerOptions: options,
|
||||
manager: mgr,
|
||||
}
|
||||
|
||||
return as, nil
|
||||
}
|
||||
|
||||
// Register validates and registers webhook(s) in the server
|
||||
func (s *Server) Register(webhooks ...Webhook) error {
|
||||
for i, webhook := range webhooks {
|
||||
// validate the webhook before registering it.
|
||||
err := webhook.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, found := s.registry[webhook.GetPath()]
|
||||
if found {
|
||||
return fmt.Errorf("can't register duplicate path: %v", webhook.GetPath())
|
||||
}
|
||||
s.registry[webhook.GetPath()] = webhooks[i]
|
||||
s.sMux.Handle(webhook.GetPath(), webhook.Handler())
|
||||
}
|
||||
|
||||
// Lazily add Server to manager.
|
||||
// Because the all webhook handlers to be in place, so we can inject the things they need.
|
||||
return s.manager.Add(s)
|
||||
}
|
||||
|
||||
// Handle registers a http.Handler for the given pattern.
|
||||
func (s *Server) Handle(pattern string, handler http.Handler) {
|
||||
s.sMux.Handle(pattern, handler)
|
||||
}
|
||||
|
||||
var _ manager.Runnable = &Server{}
|
||||
|
||||
// Start runs the server.
|
||||
// It will install the webhook related resources depend on the server configuration.
|
||||
func (s *Server) Start(stop <-chan struct{}) error {
|
||||
s.once.Do(s.setDefault)
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
|
||||
if s.DisableWebhookConfigInstaller != nil && !*s.DisableWebhookConfigInstaller {
|
||||
log.Info("installing webhook configuration in cluster")
|
||||
err := s.InstallWebhookManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Info("webhook installer is disabled")
|
||||
}
|
||||
|
||||
return s.run(stop)
|
||||
}
|
||||
|
||||
func (s *Server) run(stop <-chan struct{}) error { // nolint: gocyclo
|
||||
errCh := make(chan error)
|
||||
serveFn := func() {
|
||||
s.httpServer = &http.Server{
|
||||
Addr: fmt.Sprintf(":%v", s.Port),
|
||||
Handler: s.sMux,
|
||||
}
|
||||
log.Info("starting the webhook server.")
|
||||
errCh <- s.httpServer.ListenAndServeTLS(path.Join(s.CertDir, writer.ServerCertName), path.Join(s.CertDir, writer.ServerKeyName))
|
||||
}
|
||||
|
||||
shutdownHappend := false
|
||||
timer := time.Tick(wait.Jitter(defaultCertRefreshInterval, 0.1))
|
||||
go serveFn()
|
||||
for {
|
||||
select {
|
||||
case <-timer:
|
||||
changed, err := s.RefreshCert()
|
||||
if err != nil {
|
||||
log.Error(err, "encountering error when refreshing the certificate")
|
||||
return err
|
||||
}
|
||||
if !changed {
|
||||
log.Info("no need to reload the certificates.")
|
||||
continue
|
||||
}
|
||||
log.Info("server is shutting down to reload the certificates.")
|
||||
shutdownHappend = true
|
||||
err = s.httpServer.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
log.Error(err, "encountering error when shutting down")
|
||||
return err
|
||||
}
|
||||
timer = time.Tick(wait.Jitter(defaultCertRefreshInterval, 0.1))
|
||||
go serveFn()
|
||||
case <-stop:
|
||||
return s.httpServer.Shutdown(context.Background())
|
||||
case e := <-errCh:
|
||||
// Don't exit when getting an http.ErrServerClosed error due to restarting the server.
|
||||
if shutdownHappend && e == http.ErrServerClosed {
|
||||
shutdownHappend = false
|
||||
} else if e != nil {
|
||||
log.Error(e, "server returns an unexpected error")
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshCert refreshes the certificate using Server's Provisioner if the certificate is expiring.
|
||||
func (s *Server) RefreshCert() (bool, error) {
|
||||
cc, err := s.getClientConfig()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
changed, err := s.certProvisioner.Provision(cert.Options{
|
||||
ClientConfig: cc,
|
||||
Objects: s.webhookConfigurations,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return changed, batchCreateOrReplace(s.Client, s.webhookConfigurations...)
|
||||
}
|
||||
|
||||
var _ inject.Client = &Server{}
|
||||
|
||||
// InjectClient injects the client into the server
|
||||
func (s *Server) InjectClient(c client.Client) error {
|
||||
s.Client = c
|
||||
for _, wh := range s.registry {
|
||||
if _, err := inject.ClientInto(c, wh.Handler()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ inject.Decoder = &Server{}
|
||||
|
||||
// InjectDecoder injects the client into the server
|
||||
func (s *Server) InjectDecoder(d atypes.Decoder) error {
|
||||
for _, wh := range s.registry {
|
||||
if _, err := inject.DecoderInto(d, wh.Handler()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
115
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go
generated
vendored
115
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go
generated
vendored
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
admissionregistration "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type mutateFn func(current, desired *runtime.Object) error
|
||||
|
||||
var serviceFn = func(current, desired *runtime.Object) error {
|
||||
typedC := (*current).(*corev1.Service)
|
||||
typedD := (*desired).(*corev1.Service)
|
||||
typedC.Spec.Selector = typedD.Spec.Selector
|
||||
return nil
|
||||
}
|
||||
|
||||
var mutatingWebhookConfigFn = func(current, desired *runtime.Object) error {
|
||||
typedC := (*current).(*admissionregistration.MutatingWebhookConfiguration)
|
||||
typedD := (*desired).(*admissionregistration.MutatingWebhookConfiguration)
|
||||
typedC.Webhooks = typedD.Webhooks
|
||||
return nil
|
||||
}
|
||||
|
||||
var validatingWebhookConfigFn = func(current, desired *runtime.Object) error {
|
||||
typedC := (*current).(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
typedD := (*desired).(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
typedC.Webhooks = typedD.Webhooks
|
||||
return nil
|
||||
}
|
||||
|
||||
var genericFn = func(current, desired *runtime.Object) error {
|
||||
*current = *desired
|
||||
return nil
|
||||
}
|
||||
|
||||
// createOrReplaceHelper creates the object if it doesn't exist;
|
||||
// otherwise, it will replace it.
|
||||
// When replacing, fn should know how to preserve existing fields in the object GET from the APIServer.
|
||||
// TODO: use the helper in #98 when it merges.
|
||||
func createOrReplaceHelper(c client.Client, obj runtime.Object, fn mutateFn) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
err := c.Create(context.Background(), obj)
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
// TODO: retry mutiple times with backoff if necessary.
|
||||
existing := obj.DeepCopyObject()
|
||||
objectKey, err := client.ObjectKeyFromObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Get(context.Background(), objectKey, existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fn(&existing, &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Update(context.Background(), existing)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// createOrReplace creates the object if it doesn't exist;
|
||||
// otherwise, it will replace it.
|
||||
// When replacing, it knows how to preserve existing fields in the object GET from the APIServer.
|
||||
// It currently only support MutatingWebhookConfiguration, ValidatingWebhookConfiguration and Service.
|
||||
// For other kinds, it uses genericFn to replace the whole object.
|
||||
func createOrReplace(c client.Client, obj runtime.Object) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
switch obj.(type) {
|
||||
case *admissionregistration.MutatingWebhookConfiguration:
|
||||
return createOrReplaceHelper(c, obj, mutatingWebhookConfigFn)
|
||||
case *admissionregistration.ValidatingWebhookConfiguration:
|
||||
return createOrReplaceHelper(c, obj, validatingWebhookConfigFn)
|
||||
case *corev1.Service:
|
||||
return createOrReplaceHelper(c, obj, serviceFn)
|
||||
default:
|
||||
return createOrReplaceHelper(c, obj, genericFn)
|
||||
}
|
||||
}
|
||||
|
||||
func batchCreateOrReplace(c client.Client, objs ...runtime.Object) error {
|
||||
for i := range objs {
|
||||
err := createOrReplace(c, objs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
201
vendor/sigs.k8s.io/controller-tools/LICENSE
generated
vendored
201
vendor/sigs.k8s.io/controller-tools/LICENSE
generated
vendored
@@ -1,201 +0,0 @@
|
||||
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.
|
||||
194
vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go
generated
vendored
194
vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go
generated
vendored
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
crdgenerator "sigs.k8s.io/controller-tools/pkg/crd/generator"
|
||||
"sigs.k8s.io/controller-tools/pkg/rbac"
|
||||
"sigs.k8s.io/controller-tools/pkg/webhook"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "controller-gen",
|
||||
Short: "A reference implementation generation tool for Kubernetes APIs.",
|
||||
Long: `A reference implementation generation tool for Kubernetes APIs.`,
|
||||
Example: ` # Generate RBAC manifests for a project
|
||||
controller-gen rbac
|
||||
|
||||
# Generate CRD manifests for a project
|
||||
controller-gen crd
|
||||
|
||||
# Run all the generators for a given project
|
||||
controller-gen all
|
||||
`,
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(
|
||||
newRBACCmd(),
|
||||
newCRDCmd(),
|
||||
newWebhookCmd(),
|
||||
newAllSubCmd(),
|
||||
)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func newRBACCmd() *cobra.Command {
|
||||
o := &rbac.ManifestOptions{}
|
||||
o.SetDefaults()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rbac",
|
||||
Short: "Generates RBAC manifests",
|
||||
Long: `Generate RBAC manifests from the RBAC annotations in Go source files.
|
||||
Usage:
|
||||
# controller-gen rbac [--name manager] [--input-dir input_dir] [--output-dir output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := rbac.Generate(o); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("RBAC manifests generated under '%s' directory\n", o.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.Name, "name", o.Name, "Name to be used as prefix in identifier for manifests")
|
||||
f.StringVar(&o.InputDir, "input-dir", o.InputDir, "input directory pointing to Go source files")
|
||||
f.StringVar(&o.OutputDir, "output-dir", o.OutputDir, "output directory where generated manifests will be saved.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newCRDCmd() *cobra.Command {
|
||||
g := &crdgenerator.Generator{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "crd",
|
||||
Short: "Generates CRD manifests",
|
||||
Long: `Generate CRD manifests from the Type definitions in Go source files.
|
||||
Usage:
|
||||
# controller-gen crd [--domain k8s.io] [--root-path input_dir] [--output-dir output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := g.ValidateAndInitFields(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := g.Do(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("CRD files generated, files can be found under path %s.\n", g.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&g.RootPath, "root-path", "", "working dir, must have PROJECT file under the path or parent path if domain not set")
|
||||
f.StringVar(&g.OutputDir, "output-dir", "", "output directory, default to 'config/crds' under root path")
|
||||
f.StringVar(&g.Domain, "domain", "", "domain of the resources, will try to fetch it from PROJECT file if not specified")
|
||||
f.StringVar(&g.Namespace, "namespace", "", "CRD namespace, treat it as cluster scoped if not set")
|
||||
f.BoolVar(&g.SkipMapValidation, "skip-map-validation", true, "if set to true, skip generating OpenAPI validation schema for map type in CRD.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newAllSubCmd() *cobra.Command {
|
||||
var (
|
||||
projectDir, namespace string
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "all",
|
||||
Short: "runs all generators for a project",
|
||||
Long: `Run all available generators for a given project
|
||||
Usage:
|
||||
# controller-gen all
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if projectDir == "" {
|
||||
currDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("project-dir missing, failed to use current directory: %v", err)
|
||||
}
|
||||
projectDir = currDir
|
||||
}
|
||||
crdGen := &crdgenerator.Generator{
|
||||
RootPath: projectDir,
|
||||
OutputDir: filepath.Join(projectDir, "config", "crds"),
|
||||
Namespace: namespace,
|
||||
SkipMapValidation: true,
|
||||
}
|
||||
if err := crdGen.ValidateAndInitFields(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := crdGen.Do(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("CRD manifests generated under '%s' \n", crdGen.OutputDir)
|
||||
|
||||
// RBAC generation
|
||||
rbacOptions := &rbac.ManifestOptions{
|
||||
InputDir: filepath.Join(projectDir, "pkg"),
|
||||
OutputDir: filepath.Join(projectDir, "config", "rbac"),
|
||||
Name: "manager",
|
||||
}
|
||||
if err := rbac.Generate(rbacOptions); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("RBAC manifests generated under '%s' \n", rbacOptions.OutputDir)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&projectDir, "project-dir", "", "project directory, it must have PROJECT file")
|
||||
f.StringVar(&namespace, "namespace", "", "CRD namespace, treat it as cluster scoped if not set")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newWebhookCmd() *cobra.Command {
|
||||
o := &webhook.ManifestOptions{}
|
||||
o.SetDefaults()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "webhook",
|
||||
Short: "Generates webhook related manifests",
|
||||
Long: `Generate webhook related manifests from the webhook annotations in Go source files.
|
||||
Usage:
|
||||
# controller-gen webhook [--input-dir input_dir] [--output-dir output_dir] [--patch-output-dir patch-output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := webhook.Generate(o); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("webhook manifests generated under '%s' directory\n", o.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.InputDir, "input-dir", o.InputDir, "input directory pointing to Go source files")
|
||||
f.StringVar(&o.OutputDir, "output-dir", o.OutputDir, "output directory where generated manifests will be saved.")
|
||||
f.StringVar(&o.PatchOutputDir, "patch-output-dir", o.PatchOutputDir, "output directory where generated kustomize patch will be saved.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
187
vendor/sigs.k8s.io/controller-tools/pkg/crd/generator/generator.go
generated
vendored
187
vendor/sigs.k8s.io/controller-tools/pkg/crd/generator/generator.go
generated
vendored
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/afero"
|
||||
extensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/types"
|
||||
crdutil "sigs.k8s.io/controller-tools/pkg/crd/util"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen/parse"
|
||||
"sigs.k8s.io/controller-tools/pkg/util"
|
||||
)
|
||||
|
||||
// Generator generates CRD manifests from API resource definitions defined in Go source files.
|
||||
type Generator struct {
|
||||
RootPath string
|
||||
OutputDir string
|
||||
Domain string
|
||||
Namespace string
|
||||
SkipMapValidation bool
|
||||
|
||||
// OutFs is filesystem to be used for writing out the result
|
||||
OutFs afero.Fs
|
||||
|
||||
// apisPkg is the absolute Go pkg name for current project's 'pkg/apis' pkg.
|
||||
// This is needed to determine if a Type belongs to the project or it is a referred Type.
|
||||
apisPkg string
|
||||
}
|
||||
|
||||
// ValidateAndInitFields validate and init generator fields.
|
||||
func (c *Generator) ValidateAndInitFields() error {
|
||||
var err error
|
||||
|
||||
if c.OutFs == nil {
|
||||
c.OutFs = afero.NewOsFs()
|
||||
}
|
||||
|
||||
if len(c.RootPath) == 0 {
|
||||
// Take current path as root path if not specified.
|
||||
c.RootPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Validate root path is under go src path
|
||||
if !crdutil.IsUnderGoSrcPath(c.RootPath) {
|
||||
return fmt.Errorf("command must be run from path under $GOPATH/src/<package>")
|
||||
}
|
||||
|
||||
// If Domain is not explicitly specified,
|
||||
// try to search for PROJECT file as a basis.
|
||||
if len(c.Domain) == 0 {
|
||||
if !crdutil.PathHasProjectFile(c.RootPath) {
|
||||
return fmt.Errorf("PROJECT file missing in dir %s", c.RootPath)
|
||||
}
|
||||
c.Domain = crdutil.GetDomainFromProject(c.RootPath)
|
||||
}
|
||||
|
||||
// Validate apis directory exists under working path
|
||||
apisPath := path.Join(c.RootPath, "pkg/apis")
|
||||
if _, err := os.Stat(apisPath); err != nil {
|
||||
return fmt.Errorf("error validating apis path %s: %v", apisPath, err)
|
||||
}
|
||||
|
||||
c.apisPkg, err = crdutil.DirToGoPkg(apisPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Init output directory
|
||||
if c.OutputDir == "" {
|
||||
c.OutputDir = path.Join(c.RootPath, "config/crds")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do manages CRD generation.
|
||||
func (c *Generator) Do() error {
|
||||
arguments := args.Default()
|
||||
b, err := arguments.NewBuilder()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed making a parser: %v", err)
|
||||
}
|
||||
|
||||
// Switch working directory to root path.
|
||||
if err := os.Chdir(c.RootPath); err != nil {
|
||||
return fmt.Errorf("failed switching working dir: %v", err)
|
||||
}
|
||||
|
||||
if err := b.AddDirRecursive("./pkg/apis"); err != nil {
|
||||
return fmt.Errorf("failed making a parser: %v", err)
|
||||
}
|
||||
ctx, err := parse.NewContext(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed making a context: %v", err)
|
||||
}
|
||||
|
||||
arguments.CustomArgs = &parse.Options{SkipMapValidation: c.SkipMapValidation}
|
||||
|
||||
// TODO: find an elegant way to fulfill the domain in APIs.
|
||||
p := parse.NewAPIs(ctx, arguments, c.Domain, c.apisPkg)
|
||||
crds := c.getCrds(p)
|
||||
|
||||
return c.writeCRDs(crds)
|
||||
}
|
||||
|
||||
func (c *Generator) writeCRDs(crds map[string][]byte) error {
|
||||
// Ensure output dir exists.
|
||||
if err := c.OutFs.MkdirAll(c.OutputDir, os.FileMode(0700)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for file, crd := range crds {
|
||||
outFile := path.Join(c.OutputDir, file)
|
||||
if err := (&util.FileWriter{Fs: c.OutFs}).WriteFile(outFile, crd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCRDFileName(resource *codegen.APIResource) string {
|
||||
elems := []string{resource.Group, resource.Version, strings.ToLower(resource.Kind)}
|
||||
return strings.Join(elems, "_") + ".yaml"
|
||||
}
|
||||
|
||||
func (c *Generator) getCrds(p *parse.APIs) map[string][]byte {
|
||||
crds := map[string]extensionsv1beta1.CustomResourceDefinition{}
|
||||
for _, g := range p.APIs.Groups {
|
||||
for _, v := range g.Versions {
|
||||
for _, r := range v.Resources {
|
||||
crd := r.CRD
|
||||
// ignore types which do not belong to this project
|
||||
if !c.belongsToAPIsPkg(r.Type) {
|
||||
continue
|
||||
}
|
||||
if len(c.Namespace) > 0 {
|
||||
crd.Namespace = c.Namespace
|
||||
}
|
||||
fileName := getCRDFileName(r)
|
||||
crds[fileName] = crd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string][]byte{}
|
||||
for file, crd := range crds {
|
||||
b, err := yaml.Marshal(crd)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
result[file] = b
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// belongsToAPIsPkg returns true if type t is defined under pkg/apis pkg of
|
||||
// current project.
|
||||
func (c *Generator) belongsToAPIsPkg(t *types.Type) bool {
|
||||
return strings.HasPrefix(t.Name.Package, c.apisPkg)
|
||||
}
|
||||
117
vendor/sigs.k8s.io/controller-tools/pkg/crd/util/util.go
generated
vendored
117
vendor/sigs.k8s.io/controller-tools/pkg/crd/util/util.go
generated
vendored
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
gobuild "go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsGoSrcPath validate if given path is of path $GOPATH/src.
|
||||
func IsGoSrcPath(filePath string) bool {
|
||||
for _, gopath := range getGoPaths() {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if filePath == goSrc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnderGoSrcPath validate if given path is under path $GOPATH/src.
|
||||
func IsUnderGoSrcPath(filePath string) bool {
|
||||
for _, gopath := range getGoPaths() {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if strings.HasPrefix(filepath.Dir(filePath), goSrc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DirToGoPkg returns the Gopkg for the given directory if it exists
|
||||
// under a GOPATH otherwise returns error. For example,
|
||||
// /Users/x/go/src/github.com/y/z ==> github.com/y/z
|
||||
func DirToGoPkg(dir string) (pkg string, err error) {
|
||||
goPaths := getGoPaths()
|
||||
for _, gopath := range goPaths {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if !strings.HasPrefix(dir, goSrc) {
|
||||
continue
|
||||
}
|
||||
pkg, err := filepath.Rel(goSrc, dir)
|
||||
if err == nil {
|
||||
return pkg, err
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("dir '%s' does not exist under any GOPATH %v", dir, goPaths)
|
||||
}
|
||||
|
||||
func getGoPaths() []string {
|
||||
gopaths := os.Getenv("GOPATH")
|
||||
if len(gopaths) == 0 {
|
||||
gopaths = gobuild.Default.GOPATH
|
||||
}
|
||||
return filepath.SplitList(gopaths)
|
||||
}
|
||||
|
||||
// PathHasProjectFile validate if PROJECT file exists under the path.
|
||||
func PathHasProjectFile(filePath string) bool {
|
||||
if _, err := os.Stat(path.Join(filePath, "PROJECT")); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetDomainFromProject get domain information from the PROJECT file under the path.
|
||||
func GetDomainFromProject(rootPath string) string {
|
||||
var domain string
|
||||
|
||||
file, err := os.Open(path.Join(rootPath, "PROJECT"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "domain:") {
|
||||
domainInfo := strings.Split(scanner.Text(), ":")
|
||||
if len(domainInfo) != 2 {
|
||||
log.Fatalf("Unexpected domain info: %s", scanner.Text())
|
||||
}
|
||||
domain = strings.Replace(domainInfo[1], " ", "", -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
type genUnversionedType struct {
|
||||
Type *types.Type
|
||||
Resource *codegen.APIResource
|
||||
}
|
||||
|
||||
func (b *APIs) parseAPIs() {
|
||||
apis := &codegen.APIs{
|
||||
Domain: b.Domain,
|
||||
Package: b.APIsPkg,
|
||||
Groups: map[string]*codegen.APIGroup{},
|
||||
Rules: b.Rules,
|
||||
Informers: b.Informers,
|
||||
}
|
||||
|
||||
for group, versionMap := range b.ByGroupVersionKind {
|
||||
apiGroup := &codegen.APIGroup{
|
||||
Group: group,
|
||||
GroupTitle: strings.Title(group),
|
||||
Domain: b.Domain,
|
||||
Versions: map[string]*codegen.APIVersion{},
|
||||
UnversionedResources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
|
||||
for version, kindMap := range versionMap {
|
||||
apiVersion := &codegen.APIVersion{
|
||||
Domain: b.Domain,
|
||||
Group: group,
|
||||
Version: version,
|
||||
Resources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
for kind, resource := range kindMap {
|
||||
apiResource := &codegen.APIResource{
|
||||
Domain: resource.Domain,
|
||||
Version: resource.Version,
|
||||
Group: resource.Group,
|
||||
Resource: resource.Resource,
|
||||
Type: resource.Type,
|
||||
REST: resource.REST,
|
||||
Kind: resource.Kind,
|
||||
Subresources: resource.Subresources,
|
||||
StatusStrategy: resource.StatusStrategy,
|
||||
Strategy: resource.Strategy,
|
||||
NonNamespaced: resource.NonNamespaced,
|
||||
ShortName: resource.ShortName,
|
||||
}
|
||||
parseDoc(resource, apiResource)
|
||||
apiVersion.Resources[kind] = apiResource
|
||||
// Set the package for the api version
|
||||
apiVersion.Pkg = b.context.Universe[resource.Type.Name.Package]
|
||||
// Set the package for the api group
|
||||
apiGroup.Pkg = b.context.Universe[filepath.Dir(resource.Type.Name.Package)]
|
||||
if apiGroup.Pkg != nil {
|
||||
apiGroup.PkgPath = apiGroup.Pkg.Path
|
||||
}
|
||||
|
||||
apiGroup.UnversionedResources[kind] = apiResource
|
||||
}
|
||||
|
||||
apiGroup.Versions[version] = apiVersion
|
||||
}
|
||||
b.parseStructs(apiGroup)
|
||||
apis.Groups[group] = apiGroup
|
||||
}
|
||||
apis.Pkg = b.context.Universe[b.APIsPkg]
|
||||
b.APIs = apis
|
||||
}
|
||||
|
||||
func (b *APIs) parseStructs(apigroup *codegen.APIGroup) {
|
||||
remaining := []genUnversionedType{}
|
||||
for _, version := range apigroup.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
remaining = append(remaining, genUnversionedType{resource.Type, resource})
|
||||
}
|
||||
}
|
||||
for _, version := range b.SubByGroupVersionKind[apigroup.Group] {
|
||||
for _, kind := range version {
|
||||
remaining = append(remaining, genUnversionedType{kind, nil})
|
||||
}
|
||||
}
|
||||
|
||||
done := sets.String{}
|
||||
for len(remaining) > 0 {
|
||||
// Pop the next element from the list
|
||||
next := remaining[0]
|
||||
remaining[0] = remaining[len(remaining)-1]
|
||||
remaining = remaining[:len(remaining)-1]
|
||||
|
||||
// Already processed this type. Skip it
|
||||
if done.Has(next.Type.Name.Name) {
|
||||
continue
|
||||
}
|
||||
done.Insert(next.Type.Name.Name)
|
||||
|
||||
// Generate the struct and append to the list
|
||||
result, additionalTypes := parseType(next.Type)
|
||||
|
||||
// This is a resource, so generate the client
|
||||
if b.genClient(next.Type) {
|
||||
result.GenClient = true
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
|
||||
if next.Resource != nil {
|
||||
result.NonNamespaced = IsNonNamespaced(next.Type)
|
||||
}
|
||||
|
||||
if b.genDeepCopy(next.Type) {
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
apigroup.Structs = append(apigroup.Structs, result)
|
||||
|
||||
// Add the newly discovered subtypes
|
||||
for _, at := range additionalTypes {
|
||||
remaining = append(remaining, genUnversionedType{at, nil})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseType parses the type into a Struct, and returns a list of types that
|
||||
// need to be parsed
|
||||
func parseType(t *types.Type) (*codegen.Struct, []*types.Type) {
|
||||
remaining := []*types.Type{}
|
||||
|
||||
s := &codegen.Struct{
|
||||
Name: t.Name.Name,
|
||||
GenClient: false,
|
||||
GenUnversioned: true, // Generate unversioned structs by default
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genregister:unversioned=false") {
|
||||
// Don't generate the unversioned struct
|
||||
s.GenUnversioned = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, member := range t.Members {
|
||||
uType := member.Type.Name.Name
|
||||
memberName := member.Name
|
||||
uImport := ""
|
||||
|
||||
// Use the element type for Pointers, Maps and Slices
|
||||
mSubType := member.Type
|
||||
hasElem := false
|
||||
for mSubType.Elem != nil {
|
||||
mSubType = mSubType.Elem
|
||||
hasElem = true
|
||||
}
|
||||
if hasElem {
|
||||
// Strip the package from the field type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package+".", "", 1)
|
||||
}
|
||||
|
||||
base := filepath.Base(member.Type.String())
|
||||
samepkg := t.Name.Package == mSubType.Name.Package
|
||||
|
||||
// If not in the same package, calculate the import pkg
|
||||
if !samepkg {
|
||||
parts := strings.Split(base, ".")
|
||||
if len(parts) > 1 {
|
||||
// Don't generate unversioned types for core types, just use the versioned types
|
||||
if strings.HasPrefix(mSubType.Name.Package, "k8s.io/api/") {
|
||||
// Import the package under an alias so it doesn't conflict with other groups
|
||||
// having the same version
|
||||
importAlias := path.Base(path.Dir(mSubType.Name.Package)) + path.Base(mSubType.Name.Package)
|
||||
uImport = fmt.Sprintf("%s \"%s\"", importAlias, mSubType.Name.Package)
|
||||
if hasElem {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package, importAlias, 1)
|
||||
} else {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = fmt.Sprintf("%s.%s", importAlias, parts[1])
|
||||
}
|
||||
} else {
|
||||
switch member.Type.Name.Package {
|
||||
case "k8s.io/apimachinery/pkg/apis/meta/v1":
|
||||
// Use versioned types for meta/v1
|
||||
uImport = fmt.Sprintf("%s \"%s\"", "metav1", "k8s.io/apimachinery/pkg/apis/meta/v1")
|
||||
uType = "metav1." + parts[1]
|
||||
default:
|
||||
// Use unversioned types for everything else
|
||||
t := member.Type
|
||||
|
||||
if t.Elem != nil {
|
||||
// handle Pointers, Maps, Slices
|
||||
|
||||
// We need to parse the package from the Type String
|
||||
t = t.Elem
|
||||
str := member.Type.String()
|
||||
startPkg := strings.LastIndexAny(str, "*]")
|
||||
endPkg := strings.LastIndexAny(str, ".")
|
||||
pkg := str[startPkg+1 : endPkg]
|
||||
name := str[endPkg+1:]
|
||||
prefix := str[:startPkg+1]
|
||||
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
uType = prefix + uImportName + "." + name
|
||||
} else {
|
||||
// handle non- Pointer, Maps, Slices
|
||||
pkg := t.Name.Package
|
||||
name := t.Name.Name
|
||||
|
||||
// Come up with the alias the package is imported under
|
||||
// Concatenate with directory package to reduce naming collisions
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
|
||||
// Create the import statement
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
// Create the field type name - should be <pkgalias>.<TypeName>
|
||||
uType = uImportName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if member.Embedded {
|
||||
memberName = ""
|
||||
}
|
||||
|
||||
s.Fields = append(s.Fields, &codegen.Field{
|
||||
Name: memberName,
|
||||
VersionedPackage: member.Type.Name.Package,
|
||||
UnversionedImport: uImport,
|
||||
UnversionedType: uType,
|
||||
})
|
||||
|
||||
// Add this member Type for processing if it isn't a primitive and
|
||||
// is part of the same API group
|
||||
if !mSubType.IsPrimitive() && GetGroup(mSubType) == GetGroup(t) {
|
||||
remaining = append(remaining, mSubType)
|
||||
}
|
||||
}
|
||||
return s, remaining
|
||||
}
|
||||
|
||||
func (b *APIs) genClient(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("resource", ":") + comments.getTag("kubebuilder:resource", ":")
|
||||
return len(resource) > 0
|
||||
}
|
||||
|
||||
func (b *APIs) genDeepCopy(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
return comments.hasTag("subresource-request")
|
||||
}
|
||||
|
||||
func parseDoc(resource, apiResource *codegen.APIResource) {
|
||||
if HasDocAnnotation(resource.Type) {
|
||||
resource.DocAnnotation = getDocAnnotation(resource.Type, "warning", "note")
|
||||
apiResource.DocAnnotation = resource.DocAnnotation
|
||||
}
|
||||
}
|
||||
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/parser"
|
||||
)
|
||||
|
||||
// NewContext returns a new Context from the builder
|
||||
func NewContext(p *parser.Builder) (*generator.Context, error) {
|
||||
return generator.NewContext(p, NameSystems(), DefaultNameSystem())
|
||||
}
|
||||
|
||||
// DefaultNameSystem returns public by default.
|
||||
func DefaultNameSystem() string {
|
||||
return "public"
|
||||
}
|
||||
|
||||
// NameSystems returns the name system used by the generators in this package.
|
||||
// e.g. black-magic
|
||||
func NameSystems() namer.NameSystems {
|
||||
return namer.NameSystems{
|
||||
"public": namer.NewPublicNamer(1),
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
}
|
||||
}
|
||||
607
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
607
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
@@ -1,607 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
// parseCRDs populates the CRD field of each Group.Version.Resource,
|
||||
// creating validations using the annotations on type fields.
|
||||
func (b *APIs) parseCRDs() {
|
||||
for _, group := range b.APIs.Groups {
|
||||
for _, version := range group.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
if IsAPIResource(resource.Type) {
|
||||
resource.JSONSchemaProps, resource.Validation =
|
||||
b.typeToJSONSchemaProps(resource.Type, sets.NewString(), []string{}, true)
|
||||
|
||||
// Note: Drop the Type field at the root level of validation
|
||||
// schema. Refer to following issue for details.
|
||||
// https://github.com/kubernetes/kubernetes/issues/65293
|
||||
resource.JSONSchemaProps.Type = ""
|
||||
j, err := json.MarshalIndent(resource.JSONSchemaProps, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not Marshall validation %v\n", err)
|
||||
}
|
||||
resource.ValidationComments = string(j)
|
||||
|
||||
resource.CRD = v1beta1.CustomResourceDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apiextensions.k8s.io/v1beta1",
|
||||
Kind: "CustomResourceDefinition",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s.%s.%s", resource.Resource, resource.Group, resource.Domain),
|
||||
Labels: map[string]string{"controller-tools.k8s.io": "1.0"},
|
||||
},
|
||||
Spec: v1beta1.CustomResourceDefinitionSpec{
|
||||
Group: fmt.Sprintf("%s.%s", resource.Group, resource.Domain),
|
||||
Version: resource.Version,
|
||||
Names: v1beta1.CustomResourceDefinitionNames{
|
||||
Kind: resource.Kind,
|
||||
Plural: resource.Resource,
|
||||
},
|
||||
Validation: &v1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &resource.JSONSchemaProps,
|
||||
},
|
||||
},
|
||||
}
|
||||
if resource.NonNamespaced {
|
||||
resource.CRD.Spec.Scope = "Cluster"
|
||||
} else {
|
||||
resource.CRD.Spec.Scope = "Namespaced"
|
||||
}
|
||||
|
||||
if hasCategories(resource.Type) {
|
||||
categoriesTag := getCategoriesTag(resource.Type)
|
||||
categories := strings.Split(categoriesTag, ",")
|
||||
resource.CRD.Spec.Names.Categories = categories
|
||||
resource.Categories = categories
|
||||
}
|
||||
|
||||
if hasStatusSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Status = &v1beta1.CustomResourceSubresourceStatus{}
|
||||
}
|
||||
|
||||
resource.CRD.Status.Conditions = []v1beta1.CustomResourceDefinitionCondition{}
|
||||
resource.CRD.Status.StoredVersions = []string{}
|
||||
|
||||
if hasScaleSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
jsonPath, err := parseScaleParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed in parsing CRD, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Scale = &v1beta1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: jsonPath[specReplicasPath],
|
||||
StatusReplicasPath: jsonPath[statusReplicasPath],
|
||||
}
|
||||
labelSelctor, ok := jsonPath[labelSelectorPath]
|
||||
if ok && labelSelctor != "" {
|
||||
resource.CRD.Spec.Subresources.Scale.LabelSelectorPath = &labelSelctor
|
||||
}
|
||||
}
|
||||
if hasPrintColumn(resource.Type) {
|
||||
result, err := parsePrintColumnParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse printcolumn annotations, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.AdditionalPrinterColumns = result
|
||||
}
|
||||
if len(resource.ShortName) > 0 {
|
||||
resource.CRD.Spec.Names.ShortNames = []string{resource.ShortName}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *APIs) getTime() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Format: "date-time",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) objSchema() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
}`
|
||||
}
|
||||
|
||||
// typeToJSONSchemaProps returns a JSONSchemaProps object and its serialization
|
||||
// in Go that describe the JSONSchema validations for the given type.
|
||||
func (b *APIs) typeToJSONSchemaProps(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
// Special cases
|
||||
time := types.Name{Name: "Time", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
meta := types.Name{Name: "ObjectMeta", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
unstructured := types.Name{Name: "Unstructured", Package: "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"}
|
||||
intOrString := types.Name{Name: "IntOrString", Package: "k8s.io/apimachinery/pkg/util/intstr"}
|
||||
switch t.Name {
|
||||
case time:
|
||||
return v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Format: "date-time",
|
||||
Description: parseDescription(comments),
|
||||
}, b.getTime()
|
||||
case meta:
|
||||
return v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}, b.objSchema()
|
||||
case unstructured:
|
||||
return v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}, b.objSchema()
|
||||
case intOrString:
|
||||
return v1beta1.JSONSchemaProps{
|
||||
OneOf: []v1beta1.JSONSchemaProps{
|
||||
{
|
||||
Type: "string",
|
||||
},
|
||||
{
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
Description: parseDescription(comments),
|
||||
}, b.objSchema()
|
||||
}
|
||||
|
||||
var v v1beta1.JSONSchemaProps
|
||||
var s string
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
v, s = b.parsePrimitiveValidation(t, found, comments)
|
||||
case types.Struct:
|
||||
v, s = b.parseObjectValidation(t, found, comments, isRoot)
|
||||
case types.Map:
|
||||
v, s = b.parseMapValidation(t, found, comments)
|
||||
case types.Slice:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Array:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Pointer:
|
||||
v, s = b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
case types.Alias:
|
||||
v, s = b.typeToJSONSchemaProps(t.Underlying, found, comments, false)
|
||||
default:
|
||||
log.Fatalf("Unknown supported Kind %v\n", t.Kind)
|
||||
}
|
||||
|
||||
return v, s
|
||||
}
|
||||
|
||||
var jsonRegex = regexp.MustCompile("json:\"([a-zA-Z,]+)\"")
|
||||
|
||||
type primitiveTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Value string
|
||||
Format string
|
||||
EnumValue string // TODO check type of enum value to match the type of field
|
||||
Description string
|
||||
}
|
||||
|
||||
var primitiveTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if .Pattern -}}
|
||||
Pattern: "{{ .Pattern }}",
|
||||
{{ end -}}
|
||||
{{ if .Maximum -}}
|
||||
Maximum: getFloat({{ .Maximum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMaximum -}}
|
||||
ExclusiveMaximum: {{ .ExclusiveMaximum }},
|
||||
{{ end -}}
|
||||
{{ if .Minimum -}}
|
||||
Minimum: getFloat({{ .Minimum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMinimum -}}
|
||||
ExclusiveMinimum: {{ .ExclusiveMinimum }},
|
||||
{{ end -}}
|
||||
Type: "{{ .Value }}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{ .Format }}",
|
||||
{{ end -}}
|
||||
{{ if .EnumValue -}}
|
||||
Enum: {{ .EnumValue }},
|
||||
{{ end -}}
|
||||
{{ if .MaxLength -}}
|
||||
MaxLength: getInt({{ .MaxLength }}),
|
||||
{{ end -}}
|
||||
{{ if .MinLength -}}
|
||||
MinLength: getInt({{ .MinLength }}),
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
// parsePrimitiveValidation returns a JSONSchemaProps object and its
|
||||
// serialization in Go that describe the validations for the given primitive
|
||||
// type.
|
||||
func (b *APIs) parsePrimitiveValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
props := v1beta1.JSONSchemaProps{Type: string(t.Name.Name)}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
|
||||
var n, f, s, d string
|
||||
switch t.Name.Name {
|
||||
case "int", "int64", "uint64":
|
||||
n = "integer"
|
||||
f = "int64"
|
||||
case "int32", "uint32":
|
||||
n = "integer"
|
||||
f = "int32"
|
||||
case "float", "float32":
|
||||
n = "number"
|
||||
f = "float"
|
||||
case "float64":
|
||||
n = "number"
|
||||
f = "double"
|
||||
case "bool":
|
||||
n = "boolean"
|
||||
case "string":
|
||||
n = "string"
|
||||
default:
|
||||
n = t.Name.Name
|
||||
}
|
||||
if props.Enum != nil {
|
||||
s = parseEnumToString(props.Enum)
|
||||
}
|
||||
d = parseDescription(comments)
|
||||
if err := primitiveTemplate.Execute(buff, primitiveTemplateArgs{props, n, f, s, d}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
props.Type = n
|
||||
props.Format = f
|
||||
props.Description = d
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type mapTempateArgs struct {
|
||||
Result string
|
||||
SkipMapValidation bool
|
||||
}
|
||||
|
||||
var mapTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
{{if not .SkipMapValidation}}AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &{{.Result}},
|
||||
},{{end}}
|
||||
}`))
|
||||
|
||||
// parseMapValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given map type.
|
||||
func (b *APIs) parseMapValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
additionalProps, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
additionalProps.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
parseOption := b.arguments.CustomArgs.(*Options)
|
||||
if !parseOption.SkipMapValidation {
|
||||
props.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &additionalProps}
|
||||
}
|
||||
buff := &bytes.Buffer{}
|
||||
if err := mapTemplate.Execute(buff, mapTempateArgs{Result: result, SkipMapValidation: parseOption.SkipMapValidation}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
var arrayTemplate = template.Must(template.New("array-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "{{.Type}}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{.Format}}",
|
||||
{{ end -}}
|
||||
{{ if .MaxItems -}}
|
||||
MaxItems: getInt({{ .MaxItems }}),
|
||||
{{ end -}}
|
||||
{{ if .MinItems -}}
|
||||
MinItems: getInt({{ .MinItems }}),
|
||||
{{ end -}}
|
||||
{{ if .UniqueItems -}}
|
||||
UniqueItems: {{ .UniqueItems }},
|
||||
{{ end -}}
|
||||
{{ if .Items -}}
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &{{.ItemsSchema}},
|
||||
},
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
type arrayTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
ItemsSchema string
|
||||
}
|
||||
|
||||
// parseArrayValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given array type.
|
||||
func (b *APIs) parseArrayValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
items, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
items.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{Schema: &items},
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
// To represent byte arrays in the generated code, the property of the OpenAPI definition
|
||||
// should have string as its type and byte as its format.
|
||||
if t.Name.Name == "[]byte" {
|
||||
props.Type = "string"
|
||||
props.Format = "byte"
|
||||
props.Items = nil
|
||||
props.Description = parseDescription(comments)
|
||||
}
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
buff := &bytes.Buffer{}
|
||||
if err := arrayTemplate.Execute(buff, arrayTemplateArgs{props, result}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type objectTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Fields map[string]string
|
||||
Required []string
|
||||
IsRoot bool
|
||||
}
|
||||
|
||||
var objectTemplate = template.Must(template.New("object-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if not .IsRoot -}}
|
||||
Type: "object",
|
||||
{{ end -}}
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
{{ range $k, $v := .Fields -}}
|
||||
"{{ $k }}": {{ $v }},
|
||||
{{ end -}}
|
||||
},
|
||||
{{if .Required}}Required: []string{
|
||||
{{ range $k, $v := .Required -}}
|
||||
"{{ $v }}",
|
||||
{{ end -}}
|
||||
},{{ end -}}
|
||||
}`))
|
||||
|
||||
// parseObjectValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given object type.
|
||||
func (b *APIs) parseObjectValidation(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
buff := &bytes.Buffer{}
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
|
||||
if strings.HasPrefix(t.Name.String(), "k8s.io/api") {
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, nil, nil, false}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
} else {
|
||||
m, result, required := b.getMembers(t, found)
|
||||
props.Properties = m
|
||||
props.Required = required
|
||||
|
||||
// Only add field validation for non-inlined fields
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, result, required, isRoot}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
// getValidation parses the validation tags from the comment and sets the
|
||||
// validation rules on the given JSONSchemaProps.
|
||||
func getValidation(comment string, props *v1beta1.JSONSchemaProps) {
|
||||
comment = strings.TrimLeft(comment, " ")
|
||||
if !strings.HasPrefix(comment, "+kubebuilder:validation:") {
|
||||
return
|
||||
}
|
||||
c := strings.Replace(comment, "+kubebuilder:validation:", "", -1)
|
||||
parts := strings.Split(c, "=")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected +kubebuilder:validation:<key>=<value> actual: %s", comment)
|
||||
return
|
||||
}
|
||||
switch parts[0] {
|
||||
case "Maximum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Maximum = &f
|
||||
case "ExclusiveMaximum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMaximum = b
|
||||
case "Minimum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Minimum = &f
|
||||
case "ExclusiveMinimum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMinimum = b
|
||||
case "MaxLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxLength = &v
|
||||
case "MinLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinLength = &v
|
||||
case "Pattern":
|
||||
props.Pattern = parts[1]
|
||||
case "MaxItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxItems = &v
|
||||
}
|
||||
case "MinItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinItems = &v
|
||||
}
|
||||
case "UniqueItems":
|
||||
if props.Type == "array" {
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.UniqueItems = b
|
||||
}
|
||||
case "MultipleOf":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MultipleOf = &f
|
||||
case "Enum":
|
||||
if props.Type != "array" {
|
||||
value := strings.Split(parts[1], ",")
|
||||
enums := []v1beta1.JSON{}
|
||||
for _, s := range value {
|
||||
checkType(props, s, &enums)
|
||||
}
|
||||
props.Enum = enums
|
||||
}
|
||||
case "Format":
|
||||
props.Format = parts[1]
|
||||
default:
|
||||
log.Fatalf("Unsupport validation: %s", comment)
|
||||
}
|
||||
}
|
||||
|
||||
// getMembers builds maps by field name of the JSONSchemaProps and their Go
|
||||
// serializations.
|
||||
func (b *APIs) getMembers(t *types.Type, found sets.String) (map[string]v1beta1.JSONSchemaProps, map[string]string, []string) {
|
||||
members := map[string]v1beta1.JSONSchemaProps{}
|
||||
result := map[string]string{}
|
||||
required := []string{}
|
||||
|
||||
// Don't allow recursion until we support it through refs
|
||||
// TODO: Support recursion
|
||||
if found.Has(t.Name.String()) {
|
||||
fmt.Printf("Breaking recursion for type %s", t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
found.Insert(t.Name.String())
|
||||
|
||||
for _, member := range t.Members {
|
||||
tags := jsonRegex.FindStringSubmatch(member.Tags)
|
||||
if len(tags) == 0 {
|
||||
// Skip fields without json tags
|
||||
//fmt.Printf("Skipping member %s %s\n", member.Name, member.Type.Name.String())
|
||||
continue
|
||||
}
|
||||
ts := strings.Split(tags[1], ",")
|
||||
name := member.Name
|
||||
strat := ""
|
||||
if len(ts) > 0 && len(ts[0]) > 0 {
|
||||
name = ts[0]
|
||||
}
|
||||
if len(ts) > 1 {
|
||||
strat = ts[1]
|
||||
}
|
||||
|
||||
// Inline "inline" structs
|
||||
if strat == "inline" {
|
||||
m, r, re := b.getMembers(member.Type, found)
|
||||
for n, v := range m {
|
||||
members[n] = v
|
||||
}
|
||||
for n, v := range r {
|
||||
result[n] = v
|
||||
}
|
||||
required = append(required, re...)
|
||||
} else {
|
||||
m, r := b.typeToJSONSchemaProps(member.Type, found, member.CommentLines, false)
|
||||
members[name] = m
|
||||
result[name] = r
|
||||
if !strings.HasSuffix(strat, "omitempty") {
|
||||
required = append(required, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer found.Delete(t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
160
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
160
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/markbates/inflect"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// parseIndex indexes all types with the comment "// +resource=RESOURCE" by GroupVersionKind and
|
||||
// GroupKindVersion
|
||||
func (b *APIs) parseIndex() {
|
||||
// Index resource by group, version, kind
|
||||
b.ByGroupVersionKind = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index resources by group, kind, version
|
||||
b.ByGroupKindVersion = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index subresources by group, version, kind
|
||||
b.SubByGroupVersionKind = map[string]map[string]map[string]*types.Type{}
|
||||
|
||||
for _, c := range b.context.Order {
|
||||
// The type is a subresource, add it to the subresource index
|
||||
if IsAPISubresource(c) {
|
||||
group := GetGroup(c)
|
||||
version := GetVersion(c, group)
|
||||
kind := GetKind(c, group)
|
||||
if _, f := b.SubByGroupVersionKind[group]; !f {
|
||||
b.SubByGroupVersionKind[group] = map[string]map[string]*types.Type{}
|
||||
}
|
||||
if _, f := b.SubByGroupVersionKind[group][version]; !f {
|
||||
b.SubByGroupVersionKind[group][version] = map[string]*types.Type{}
|
||||
}
|
||||
b.SubByGroupVersionKind[group][version][kind] = c
|
||||
}
|
||||
|
||||
// If it isn't a subresource or resource, continue to the next type
|
||||
if !IsAPIResource(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse out the resource information
|
||||
r := &codegen.APIResource{
|
||||
Type: c,
|
||||
NonNamespaced: IsNonNamespaced(c),
|
||||
}
|
||||
r.Group = GetGroup(c)
|
||||
r.Version = GetVersion(c, r.Group)
|
||||
r.Kind = GetKind(c, r.Group)
|
||||
r.Domain = b.Domain
|
||||
|
||||
// TODO: revisit the part...
|
||||
if r.Resource == "" {
|
||||
r.Resource = strings.ToLower(inflect.Pluralize(r.Kind))
|
||||
}
|
||||
rt, err := parseResourceAnnotation(c)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse resource annotations, error: %v", err.Error())
|
||||
}
|
||||
if rt.Resource != "" {
|
||||
r.Resource = rt.Resource
|
||||
}
|
||||
r.ShortName = rt.ShortName
|
||||
|
||||
// Copy the Status strategy to mirror the non-status strategy
|
||||
r.StatusStrategy = strings.TrimSuffix(r.Strategy, "Strategy")
|
||||
r.StatusStrategy = fmt.Sprintf("%sStatusStrategy", r.StatusStrategy)
|
||||
|
||||
// Initialize the map entries so they aren't nill
|
||||
if _, f := b.ByGroupKindVersion[r.Group]; !f {
|
||||
b.ByGroupKindVersion[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupKindVersion[r.Group][r.Kind]; !f {
|
||||
b.ByGroupKindVersion[r.Group][r.Kind] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group]; !f {
|
||||
b.ByGroupVersionKind[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group][r.Version]; !f {
|
||||
b.ByGroupVersionKind[r.Group][r.Version] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
|
||||
// Add the resource to the map
|
||||
b.ByGroupKindVersion[r.Group][r.Kind][r.Version] = r
|
||||
b.ByGroupVersionKind[r.Group][r.Version][r.Kind] = r
|
||||
r.Type = c
|
||||
}
|
||||
}
|
||||
|
||||
// resourceTags contains the tags present in a "+resource=" comment
|
||||
type resourceTags struct {
|
||||
Resource string
|
||||
REST string
|
||||
Strategy string
|
||||
ShortName string
|
||||
}
|
||||
|
||||
// resourceAnnotationValue is a helper function to extract resource annotation.
|
||||
func resourceAnnotationValue(tag string) (resourceTags, error) {
|
||||
res := resourceTags{}
|
||||
for _, elem := range strings.Split(tag, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
return resourceTags{}, fmt.Errorf("// +kubebuilder:resource: tags must be key value pairs. Expected "+
|
||||
"keys [path=<resourcepath>] "+
|
||||
"Got string: [%s]", tag)
|
||||
}
|
||||
switch key {
|
||||
case "path":
|
||||
res.Resource = value
|
||||
case "shortName":
|
||||
res.ShortName = value
|
||||
default:
|
||||
return resourceTags{}, fmt.Errorf("The given input %s is invalid", value)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// parseResourceAnnotation parses the tags in a "+resource=" comment into a resourceTags struct.
|
||||
func parseResourceAnnotation(t *types.Type) (resourceTags, error) {
|
||||
finalResult := resourceTags{}
|
||||
var resourceAnnotationFound bool
|
||||
for _, comment := range t.CommentLines {
|
||||
anno := general.GetAnnotation(comment, "kubebuilder:resource")
|
||||
if len(anno) == 0 {
|
||||
continue
|
||||
}
|
||||
result, err := resourceAnnotationValue(anno)
|
||||
if err != nil {
|
||||
return resourceTags{}, err
|
||||
}
|
||||
if resourceAnnotationFound {
|
||||
return resourceTags{}, fmt.Errorf("resource annotation should only exists once per type")
|
||||
}
|
||||
resourceAnnotationFound = true
|
||||
finalResult = result
|
||||
}
|
||||
return finalResult, nil
|
||||
}
|
||||
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
// APIs is the information of a collection of API
|
||||
type APIs struct {
|
||||
context *generator.Context
|
||||
arguments *args.GeneratorArgs
|
||||
Domain string
|
||||
VersionedPkgs sets.String
|
||||
UnversionedPkgs sets.String
|
||||
APIsPkg string
|
||||
APIsPkgRaw *types.Package
|
||||
GroupNames sets.String
|
||||
|
||||
APIs *codegen.APIs
|
||||
Controllers []codegen.Controller
|
||||
|
||||
ByGroupKindVersion map[string]map[string]map[string]*codegen.APIResource
|
||||
ByGroupVersionKind map[string]map[string]map[string]*codegen.APIResource
|
||||
SubByGroupVersionKind map[string]map[string]map[string]*types.Type
|
||||
Groups map[string]types.Package
|
||||
Rules []rbacv1.PolicyRule
|
||||
Informers map[v1.GroupVersionKind]bool
|
||||
}
|
||||
|
||||
// NewAPIs returns a new APIs instance with given context.
|
||||
func NewAPIs(context *generator.Context, arguments *args.GeneratorArgs, domain, apisPkg string) *APIs {
|
||||
b := &APIs{
|
||||
context: context,
|
||||
arguments: arguments,
|
||||
Domain: domain,
|
||||
APIsPkg: apisPkg,
|
||||
}
|
||||
b.parsePackages()
|
||||
b.parseGroupNames()
|
||||
b.parseIndex()
|
||||
b.parseAPIs()
|
||||
b.parseCRDs()
|
||||
if len(b.Domain) == 0 {
|
||||
b.parseDomain()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// parseGroupNames initializes b.GroupNames with the set of all groups
|
||||
func (b *APIs) parseGroupNames() {
|
||||
b.GroupNames = sets.String{}
|
||||
for p := range b.UnversionedPkgs {
|
||||
pkg := b.context.Universe[p]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
continue
|
||||
}
|
||||
b.GroupNames.Insert(filepath.Base(p))
|
||||
}
|
||||
}
|
||||
|
||||
// parsePackages parses out the sets of Versioned, Unversioned packages and identifies the root Apis package.
|
||||
func (b *APIs) parsePackages() {
|
||||
b.VersionedPkgs = sets.NewString()
|
||||
b.UnversionedPkgs = sets.NewString()
|
||||
for _, o := range b.context.Order {
|
||||
if IsAPIResource(o) {
|
||||
versioned := o.Name.Package
|
||||
b.VersionedPkgs.Insert(versioned)
|
||||
|
||||
unversioned := filepath.Dir(versioned)
|
||||
b.UnversionedPkgs.Insert(unversioned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseDomain parses the domain from the apis/doc.go file comment "// +domain=YOUR_DOMAIN".
|
||||
func (b *APIs) parseDomain() {
|
||||
pkg := b.context.Universe[b.APIsPkg]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
panic(errors.Errorf("Missing apis package."))
|
||||
}
|
||||
comments := Comments(pkg.Comments)
|
||||
b.Domain = comments.getTag("domain", "=")
|
||||
if len(b.Domain) == 0 {
|
||||
b.Domain = parseDomainFromFiles(b.context.Inputs)
|
||||
if len(b.Domain) == 0 {
|
||||
panic("Could not find string matching // +domain=.+ in apis/doc.go")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseDomainFromFiles(paths []string) string {
|
||||
var domain string
|
||||
for _, path := range paths {
|
||||
if strings.HasSuffix(path, "pkg/apis") {
|
||||
filePath := strings.Join([]string{build.Default.GOPATH, "src", path, "doc.go"}, "/")
|
||||
lines := []string{}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "//") {
|
||||
lines = append(lines, strings.Replace(scanner.Text(), "// ", "", 1))
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
comments := Comments(lines)
|
||||
domain = comments.getTag("domain", "=")
|
||||
break
|
||||
}
|
||||
}
|
||||
return domain
|
||||
}
|
||||
515
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
515
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
@@ -1,515 +0,0 @@
|
||||
/*
|
||||
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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
const (
|
||||
specReplicasPath = "specpath"
|
||||
statusReplicasPath = "statuspath"
|
||||
labelSelectorPath = "selectorpath"
|
||||
jsonPathError = "invalid scale path. specpath, statuspath key-value pairs are required, only selectorpath key-value is optinal. For example: // +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=.spec.Label"
|
||||
printColumnName = "name"
|
||||
printColumnType = "type"
|
||||
printColumnDescr = "description"
|
||||
printColumnPath = "JSONPath"
|
||||
printColumnFormat = "format"
|
||||
printColumnPri = "priority"
|
||||
printColumnError = "invalid printcolumn path. name,type, and JSONPath are required kye-value pairs and rest of the fields are optinal. For example: // +kubebuilder:printcolumn:name=abc,type=string,JSONPath=status"
|
||||
)
|
||||
|
||||
// Options contains the parser options
|
||||
type Options struct {
|
||||
SkipMapValidation bool
|
||||
|
||||
// SkipRBACValidation flag determines whether to check RBAC annotations
|
||||
// for the controller or not at parse stage.
|
||||
SkipRBACValidation bool
|
||||
}
|
||||
|
||||
// IsAPIResource returns true if either of the two conditions become true:
|
||||
// 1. t has a +resource/+kubebuilder:resource comment tag
|
||||
// 2. t has TypeMeta and ObjectMeta in its member list.
|
||||
func IsAPIResource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+resource") || strings.Contains(c, "+kubebuilder:resource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
typeMetaFound, objMetaFound := false, false
|
||||
for _, m := range t.Members {
|
||||
if m.Name == "TypeMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" {
|
||||
typeMetaFound = true
|
||||
}
|
||||
if m.Name == "ObjectMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" {
|
||||
objMetaFound = true
|
||||
}
|
||||
if typeMetaFound && objMetaFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNonNamespaced returns true if t has a +nonNamespaced comment tag
|
||||
func IsNonNamespaced(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range t.SecondClosestCommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsController returns true if t has a +controller or +kubebuilder:controller tag
|
||||
func IsController(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+controller") || strings.Contains(c, "+kubebuilder:controller") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRBAC returns true if t has a +rbac or +kubebuilder:rbac tag
|
||||
func IsRBAC(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+rbac") || strings.Contains(c, "+kubebuilder:rbac") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPrintColumn returns true if t has a +printcolumn or +kubebuilder:printcolumn annotation.
|
||||
func hasPrintColumn(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+printcolumn") || strings.Contains(c, "+kubebuilder:printcolumn") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInformer returns true if t has a +informers or +kubebuilder:informers tag
|
||||
func IsInformer(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+informers") || strings.Contains(c, "+kubebuilder:informers") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAPISubresource returns true if t has a +subresource-request comment tag
|
||||
func IsAPISubresource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+subresource-request") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasSubresource returns true if t is an APIResource with one or more Subresources
|
||||
func HasSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "subresource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasStatusSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:status
|
||||
func hasStatusSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:status") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasScaleSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:scale
|
||||
func hasScaleSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasCategories returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:categories
|
||||
func hasCategories(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:categories") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasDocAnnotation returns true if t is an APIResource with doc annotation
|
||||
// +kubebuilder:doc
|
||||
func HasDocAnnotation(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:doc") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnversioned returns true if t is in given group, and not in versioned path.
|
||||
func IsUnversioned(t *types.Type, group string) bool {
|
||||
return IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// IsVersioned returns true if t is in given group, and in versioned path.
|
||||
func IsVersioned(t *types.Type, group string) bool {
|
||||
dir := filepath.Base(filepath.Dir(filepath.Dir(t.Name.Package)))
|
||||
return IsApisDir(dir) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// GetVersion returns version of t.
|
||||
func GetVersion(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get version for unversioned type %v", t.Name))
|
||||
}
|
||||
return filepath.Base(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetGroup returns group of t.
|
||||
func GetGroup(t *types.Type) string {
|
||||
return filepath.Base(GetGroupPackage(t))
|
||||
}
|
||||
|
||||
// GetGroupPackage returns group package of t.
|
||||
func GetGroupPackage(t *types.Type) string {
|
||||
if IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) {
|
||||
return t.Name.Package
|
||||
}
|
||||
return filepath.Dir(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetKind returns kind of t.
|
||||
func GetKind(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) && !IsUnversioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get kind for type not in group %v", t.Name))
|
||||
}
|
||||
return t.Name.Name
|
||||
}
|
||||
|
||||
// IsApisDir returns true if a directory path is a Kubernetes api directory
|
||||
func IsApisDir(dir string) bool {
|
||||
return dir == "apis" || dir == "api"
|
||||
}
|
||||
|
||||
// Comments is a structure for using comment tags on go structs and fields
|
||||
type Comments []string
|
||||
|
||||
// GetTags returns the value for the first comment with a prefix matching "+name="
|
||||
// e.g. "+name=foo\n+name=bar" would return "foo"
|
||||
func (c Comments) getTag(name, sep string) string {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// hasTag returns true if the Comments has a tag with the given name
|
||||
func (c Comments) hasTag(name string) bool {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s", name)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTags returns the value for all comments with a prefix and separator. E.g. for "name" and "="
|
||||
// "+name=foo\n+name=bar" would return []string{"foo", "bar"}
|
||||
func (c Comments) getTags(name, sep string) []string {
|
||||
tags := []string{}
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
tags = append(tags, strings.Replace(c, prefix, "", 1))
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// getCategoriesTag returns the value of the +kubebuilder:categories tags
|
||||
func getCategoriesTag(c *types.Type) string {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("kubebuilder:categories", "=")
|
||||
if len(resource) == 0 {
|
||||
panic(errors.Errorf("Must specify +kubebuilder:categories comment for type %v", c.Name))
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
// getDocAnnotation parse annotations of "+kubebuilder:doc:" with tags of "warning" or "doc" for control generating doc config.
|
||||
// E.g. +kubebuilder:doc:warning=foo +kubebuilder:doc:note=bar
|
||||
func getDocAnnotation(t *types.Type, tags ...string) map[string]string {
|
||||
annotation := make(map[string]string)
|
||||
for _, tag := range tags {
|
||||
for _, c := range t.CommentLines {
|
||||
prefix := fmt.Sprintf("+kubebuilder:doc:%s=", tag)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
annotation[tag] = strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotation
|
||||
}
|
||||
|
||||
// parseByteValue returns the literal digital number values from a byte array
|
||||
func parseByteValue(b []byte) string {
|
||||
elem := strings.Join(strings.Fields(fmt.Sprintln(b)), ",")
|
||||
elem = strings.TrimPrefix(elem, "[")
|
||||
elem = strings.TrimSuffix(elem, "]")
|
||||
return elem
|
||||
}
|
||||
|
||||
// parseDescription parse comments above each field in the type definition.
|
||||
func parseDescription(res []string) string {
|
||||
var temp strings.Builder
|
||||
var desc string
|
||||
for _, comment := range res {
|
||||
if !(strings.Contains(comment, "+kubebuilder") || strings.Contains(comment, "+optional")) {
|
||||
temp.WriteString(comment)
|
||||
temp.WriteString(" ")
|
||||
desc = strings.TrimRight(temp.String(), " ")
|
||||
}
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
// parseEnumToString returns a representive validated go format string from JSONSchemaProps schema
|
||||
func parseEnumToString(value []v1beta1.JSON) string {
|
||||
res := "[]v1beta1.JSON{"
|
||||
prefix := "v1beta1.JSON{[]byte{"
|
||||
for _, v := range value {
|
||||
res = res + prefix + parseByteValue(v.Raw) + "}},"
|
||||
}
|
||||
return strings.TrimSuffix(res, ",") + "}"
|
||||
}
|
||||
|
||||
// check type of enum element value to match type of field
|
||||
func checkType(props *v1beta1.JSONSchemaProps, s string, enums *[]v1beta1.JSON) {
|
||||
|
||||
// TODO support more types check
|
||||
switch props.Type {
|
||||
case "int", "int64", "uint64":
|
||||
if _, err := strconv.ParseInt(s, 0, 64); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "int32", "unit32":
|
||||
if _, err := strconv.ParseInt(s, 0, 32); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float", "float32":
|
||||
if _, err := strconv.ParseFloat(s, 32); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float64":
|
||||
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "string":
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(`"` + s + `"`)})
|
||||
}
|
||||
}
|
||||
|
||||
// Scale subresource requires specpath, statuspath, selectorpath key values, represents for JSONPath of
|
||||
// SpecReplicasPath, StatusReplicasPath, LabelSelectorPath separately. e.g.
|
||||
// +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=
|
||||
func parseScaleParams(t *types.Type) (map[string]string, error) {
|
||||
jsonPath := make(map[string]string)
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
paths := strings.Replace(c, "+kubebuilder:subresource:scale:", "", -1)
|
||||
path := strings.Split(paths, ",")
|
||||
if len(path) < 2 {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
for _, s := range path {
|
||||
kv := strings.Split(s, "=")
|
||||
if kv[0] == specReplicasPath || kv[0] == statusReplicasPath || kv[0] == labelSelectorPath {
|
||||
jsonPath[kv[0]] = kv[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
_, ok = jsonPath[specReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
_, ok = jsonPath[statusReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
return jsonPath, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
|
||||
// printColumnKV parses key-value string formatted as "foo=bar" and returns key and value.
|
||||
func printColumnKV(s string) (key, value string, err error) {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
err = fmt.Errorf("invalid key value pair")
|
||||
return key, value, err
|
||||
}
|
||||
key, value = kv[0], kv[1]
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return key, value, err
|
||||
}
|
||||
|
||||
// helperPrintColumn is a helper function for the parsePrintColumnParams to compute printer columns.
|
||||
func helperPrintColumn(parts string, comment string) (v1beta1.CustomResourceColumnDefinition, error) {
|
||||
config := v1beta1.CustomResourceColumnDefinition{}
|
||||
var count int
|
||||
part := strings.Split(parts, ",")
|
||||
if len(part) < 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
|
||||
for _, elem := range strings.Split(parts, ",") {
|
||||
key, value, err := printColumnKV(elem)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{},
|
||||
fmt.Errorf("//+kubebuilder:printcolumn: tags must be key value pairs.Expected "+
|
||||
"keys [name=<name>,type=<type>,description=<descr>,format=<format>] "+
|
||||
"Got string: [%s]", parts)
|
||||
}
|
||||
if key == printColumnName || key == printColumnType || key == printColumnPath {
|
||||
count++
|
||||
}
|
||||
switch key {
|
||||
case printColumnName:
|
||||
config.Name = value
|
||||
case printColumnType:
|
||||
if value == "integer" || value == "number" || value == "string" || value == "boolean" || value == "date" {
|
||||
config.Type = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnType)
|
||||
}
|
||||
case printColumnFormat:
|
||||
if config.Type == "integer" && (value == "int32" || value == "int64") {
|
||||
config.Format = value
|
||||
} else if config.Type == "number" && (value == "float" || value == "double") {
|
||||
config.Format = value
|
||||
} else if config.Type == "string" && (value == "byte" || value == "date" || value == "date-time" || value == "password") {
|
||||
config.Format = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnFormat)
|
||||
}
|
||||
case printColumnPath:
|
||||
config.JSONPath = value
|
||||
case printColumnPri:
|
||||
i, err := strconv.Atoi(value)
|
||||
v := int32(i)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnPri)
|
||||
}
|
||||
config.Priority = v
|
||||
case printColumnDescr:
|
||||
config.Description = value
|
||||
default:
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
}
|
||||
if count != 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// printcolumn requires name,type,JSONPath fields and rest of the field are optional
|
||||
// +kubebuilder:printcolumn:name=<name>,type=<type>,description=<desc>,JSONPath:<.spec.Name>,priority=<int32>,format=<format>
|
||||
func parsePrintColumnParams(t *types.Type) ([]v1beta1.CustomResourceColumnDefinition, error) {
|
||||
result := []v1beta1.CustomResourceColumnDefinition{}
|
||||
for _, comment := range t.CommentLines {
|
||||
if strings.Contains(comment, "+kubebuilder:printcolumn") {
|
||||
parts := strings.Replace(comment, "+kubebuilder:printcolumn:", "", -1)
|
||||
res, err := helperPrintColumn(parts, comment)
|
||||
if err != nil {
|
||||
return []v1beta1.CustomResourceColumnDefinition{}, err
|
||||
}
|
||||
result = append(result, res)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
213
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/types.go
generated
vendored
213
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/types.go
generated
vendored
@@ -1,213 +0,0 @@
|
||||
/*
|
||||
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 codegen
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
// APIs is the information of a collection of API
|
||||
type APIs struct {
|
||||
// Domain is the domain portion of the group - e.g. k8s.io
|
||||
Domain string
|
||||
|
||||
// Package is the name of the root API package - e.g. github.com/my-org/my-repo/pkg/apis
|
||||
Package string
|
||||
|
||||
// Pkg the Package for the root API package
|
||||
Pkg *types.Package
|
||||
|
||||
// Groups is the list of API groups found under the apis package
|
||||
Groups map[string]*APIGroup
|
||||
|
||||
Rules []rbacv1.PolicyRule
|
||||
|
||||
Informers map[v1.GroupVersionKind]bool
|
||||
}
|
||||
|
||||
// GetRules get rules of the APIs
|
||||
func (apis *APIs) GetRules() []rbacv1.PolicyRule {
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
rulesIndex := map[v1.GroupResource]sets.String{}
|
||||
for _, rule := range apis.Rules {
|
||||
for _, g := range rule.APIGroups {
|
||||
for _, r := range rule.Resources {
|
||||
gr := v1.GroupResource{
|
||||
Group: g,
|
||||
Resource: r,
|
||||
}
|
||||
if _, found := rulesIndex[gr]; !found {
|
||||
rulesIndex[gr] = sets.NewString()
|
||||
}
|
||||
rulesIndex[gr].Insert(rule.Verbs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
for gr, v := range rulesIndex {
|
||||
verbs := v.List()
|
||||
sort.Strings(verbs)
|
||||
rule := rbacv1.PolicyRule{
|
||||
Resources: []string{gr.Resource},
|
||||
APIGroups: []string{gr.Group},
|
||||
Verbs: verbs,
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// APIGroup contains information of an API group.
|
||||
type APIGroup struct {
|
||||
// Package is the name of the go package the api group is under - e.g. github.com/me/apiserver-helloworld/apis
|
||||
Package string
|
||||
// Domain is the domain portion of the group - e.g. k8s.io
|
||||
Domain string
|
||||
// Group is the short name of the group - e.g. mushroomkingdom
|
||||
Group string
|
||||
GroupTitle string
|
||||
// Versions is the list of all versions for this group keyed by name
|
||||
Versions map[string]*APIVersion
|
||||
|
||||
UnversionedResources map[string]*APIResource
|
||||
|
||||
// Structs is a list of unversioned definitions that must be generated
|
||||
Structs []*Struct
|
||||
Pkg *types.Package
|
||||
PkgPath string
|
||||
}
|
||||
|
||||
// Struct contains information of a struct.
|
||||
type Struct struct {
|
||||
// Name is the name of the type
|
||||
Name string
|
||||
// genClient
|
||||
GenClient bool
|
||||
GenDeepCopy bool
|
||||
NonNamespaced bool
|
||||
|
||||
GenUnversioned bool
|
||||
// Fields is the list of fields appearing in the struct
|
||||
Fields []*Field
|
||||
}
|
||||
|
||||
// Field contains information of a field.
|
||||
type Field struct {
|
||||
// Name is the name of the field
|
||||
Name string
|
||||
// For versioned Kubernetes types, this is the versioned package
|
||||
VersionedPackage string
|
||||
// For versioned Kubernetes types, this is the unversioned package
|
||||
UnversionedImport string
|
||||
UnversionedType string
|
||||
}
|
||||
|
||||
// APIVersion contains information of an API version.
|
||||
type APIVersion struct {
|
||||
// Domain is the group domain - e.g. k8s.io
|
||||
Domain string
|
||||
// Group is the group name - e.g. mushroomkingdom
|
||||
Group string
|
||||
// Version is the api version - e.g. v1beta1
|
||||
Version string
|
||||
// Resources is a list of resources appearing in the API version keyed by name
|
||||
Resources map[string]*APIResource
|
||||
// Pkg is the Package object from code-gen
|
||||
Pkg *types.Package
|
||||
}
|
||||
|
||||
// APIResource contains information of an API resource.
|
||||
type APIResource struct {
|
||||
// Domain is the group domain - e.g. k8s.io
|
||||
Domain string
|
||||
// Group is the group name - e.g. mushroomkingdom
|
||||
Group string
|
||||
// Version is the api version - e.g. v1beta1
|
||||
Version string
|
||||
// Kind is the resource name - e.g. PeachesCastle
|
||||
Kind string
|
||||
// Resource is the resource name - e.g. peachescastles
|
||||
Resource string
|
||||
// REST is the rest.Storage implementation used to handle requests
|
||||
// This field is optional. The standard REST implementation will be used
|
||||
// by default.
|
||||
REST string
|
||||
// Subresources is a map of subresources keyed by name
|
||||
Subresources map[string]*APISubresource
|
||||
// Type is the Type object from code-gen
|
||||
Type *types.Type
|
||||
// Strategy is name of the struct to use for the strategy
|
||||
Strategy string
|
||||
// Strategy is name of the struct to use for the strategy
|
||||
StatusStrategy string
|
||||
// NonNamespaced indicates that the resource kind is non namespaced
|
||||
NonNamespaced bool
|
||||
|
||||
ShortName string
|
||||
|
||||
JSONSchemaProps v1beta1.JSONSchemaProps
|
||||
CRD v1beta1.CustomResourceDefinition
|
||||
Validation string
|
||||
ValidationComments string
|
||||
// DocAnnotation is a map of annotations by name for doc. e.g. warning, notes message
|
||||
DocAnnotation map[string]string
|
||||
// Categories is a list of categories the resource is part of.
|
||||
Categories []string
|
||||
}
|
||||
|
||||
// APISubresource contains information of an API subresource.
|
||||
type APISubresource struct {
|
||||
// Domain is the group domain - e.g. k8s.io
|
||||
Domain string
|
||||
// Group is the group name - e.g. mushroomkingdom
|
||||
Group string
|
||||
// Version is the api version - e.g. v1beta1
|
||||
Version string
|
||||
// Kind is the resource name - e.g. PeachesCastle
|
||||
Kind string
|
||||
// Resource is the resource name - e.g. peachescastles
|
||||
Resource string
|
||||
// Request is the subresource request type - e.g. ScaleCastle
|
||||
Request string
|
||||
// REST is the rest.Storage implementation used to handle requests
|
||||
REST string
|
||||
// Path is the subresource path - e.g. scale
|
||||
Path string
|
||||
|
||||
// ImportPackage is the import statement that must appear for the Request
|
||||
ImportPackage string
|
||||
|
||||
// RequestType is the type of the request
|
||||
RequestType *types.Type
|
||||
|
||||
// RESTType is the type of the request handler
|
||||
RESTType *types.Type
|
||||
}
|
||||
|
||||
// Controller contains information of a controller.
|
||||
type Controller struct {
|
||||
Target schema.GroupVersionKind
|
||||
Resource string
|
||||
Pkg *types.Package
|
||||
Repo string
|
||||
}
|
||||
102
vendor/sigs.k8s.io/controller-tools/pkg/internal/general/util.go
generated
vendored
102
vendor/sigs.k8s.io/controller-tools/pkg/internal/general/util.go
generated
vendored
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
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 general
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isGoFile filters files from parsing.
|
||||
func isGoFile(f os.FileInfo) bool {
|
||||
// ignore non-Go or Go test files
|
||||
name := f.Name()
|
||||
return !f.IsDir() &&
|
||||
!strings.HasPrefix(name, ".") &&
|
||||
!strings.HasSuffix(name, "_test.go") &&
|
||||
strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
// GetAnnotation extracts the annotation from comment text.
|
||||
// It will return "foo" for comment "+kubebuilder:webhook:foo" .
|
||||
func GetAnnotation(c, name string) string {
|
||||
prefix := fmt.Sprintf("+%s:", name)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return strings.TrimPrefix(c, prefix)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseKV parses key-value string formatted as "foo=bar" and returns key and value.
|
||||
func ParseKV(s string) (key, value string, err error) {
|
||||
kv := strings.Split(s, "=")
|
||||
if len(kv) != 2 {
|
||||
err = fmt.Errorf("invalid key value pair")
|
||||
return key, value, err
|
||||
}
|
||||
key, value = kv[0], kv[1]
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return key, value, err
|
||||
}
|
||||
|
||||
// ParseDir parses the Go files under given directory and parses the annotation by
|
||||
// invoking the parseFn function on each comment group (multi-lines comments).
|
||||
// TODO(droot): extend it to multiple dirs
|
||||
func ParseDir(dir string, parseFn func(string) error) error {
|
||||
fset := token.NewFileSet()
|
||||
|
||||
err := filepath.Walk(dir,
|
||||
func(path string, info os.FileInfo, _ error) error {
|
||||
if !isGoFile(info) {
|
||||
// TODO(droot): enable this output based on verbose flag
|
||||
// fmt.Println("skipping non-go file", path)
|
||||
return nil
|
||||
}
|
||||
return ParseFile(fset, path, nil, parseFn)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseFile parses given filename or content src and parses annotations by
|
||||
// invoking the parseFn function on each comment group (multi-lines comments).
|
||||
func ParseFile(fset *token.FileSet, filename string, src interface{}, parseFn func(string) error) error {
|
||||
f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
|
||||
if err != nil {
|
||||
fmt.Printf("error from parse.ParseFile: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// using commentMaps here because it sanitizes the comment text by removing
|
||||
// comment markers, compresses newlines etc.
|
||||
cmap := ast.NewCommentMap(fset, f, f.Comments)
|
||||
|
||||
for _, commentGroup := range cmap.Comments() {
|
||||
err = parseFn(commentGroup.Text())
|
||||
if err != nil {
|
||||
fmt.Print("error when parsing annotation")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
152
vendor/sigs.k8s.io/controller-tools/pkg/rbac/manifests.go
generated
vendored
152
vendor/sigs.k8s.io/controller-tools/pkg/rbac/manifests.go
generated
vendored
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
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 rbac
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// ManifestOptions represent options for generating the RBAC manifests.
|
||||
type ManifestOptions struct {
|
||||
InputDir string
|
||||
OutputDir string
|
||||
Name string
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// SetDefaults sets up the default options for RBAC Manifest generator.
|
||||
func (o *ManifestOptions) SetDefaults() {
|
||||
o.Name = "manager"
|
||||
o.InputDir = filepath.Join(".", "pkg")
|
||||
o.OutputDir = filepath.Join(".", "config", "rbac")
|
||||
}
|
||||
|
||||
// RoleName returns the RBAC role name to be used in the manifests.
|
||||
func (o *ManifestOptions) RoleName() string {
|
||||
return o.Name + "-role"
|
||||
}
|
||||
|
||||
// RoleBindingName returns the RBAC role binding name to be used in the manifests.
|
||||
func (o *ManifestOptions) RoleBindingName() string {
|
||||
return o.Name + "-rolebinding"
|
||||
}
|
||||
|
||||
// Namespace returns the namespace to be used in the RBAC manifests.
|
||||
func (o *ManifestOptions) Namespace() string {
|
||||
// TODO(droot): define this as a constant and share it with scaffold pkg.
|
||||
return "system"
|
||||
}
|
||||
|
||||
// Validate validates the input options.
|
||||
func (o *ManifestOptions) Validate() error {
|
||||
if _, err := os.Stat(o.InputDir); err != nil {
|
||||
return fmt.Errorf("invalid input directory '%s' %v", o.InputDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate generates RBAC manifests by parsing the RBAC annotations in Go source
|
||||
// files specified in the input directory.
|
||||
func Generate(o *ManifestOptions) error {
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ops := parserOptions{
|
||||
rules: []rbacv1.PolicyRule{},
|
||||
}
|
||||
err := general.ParseDir(o.InputDir, ops.parseAnnotation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse the input dir %v", err)
|
||||
}
|
||||
if len(ops.rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
roleManifest, err := getClusterRoleManifest(ops.rules, o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate role manifest %v", err)
|
||||
}
|
||||
|
||||
roleBindingManifest, err := getClusterRoleBindingManifest(o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate role binding manifests %v", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(o.OutputDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output dir %v", err)
|
||||
}
|
||||
roleManifestFile := filepath.Join(o.OutputDir, "rbac_role.yaml")
|
||||
if err := ioutil.WriteFile(roleManifestFile, roleManifest, 0666); err != nil {
|
||||
return fmt.Errorf("failed to write role manifest YAML file %v", err)
|
||||
}
|
||||
|
||||
roleBindingManifestFile := filepath.Join(o.OutputDir, "rbac_role_binding.yaml")
|
||||
if err := ioutil.WriteFile(roleBindingManifestFile, roleBindingManifest, 0666); err != nil {
|
||||
return fmt.Errorf("failed to write role manifest YAML file %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getClusterRoleManifest(rules []rbacv1.PolicyRule, o *ManifestOptions) ([]byte, error) {
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ClusterRole",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.RoleName(),
|
||||
Labels: o.Labels,
|
||||
},
|
||||
Rules: rules,
|
||||
}
|
||||
return yaml.Marshal(role)
|
||||
}
|
||||
|
||||
func getClusterRoleBindingManifest(o *ManifestOptions) ([]byte, error) {
|
||||
rolebinding := &rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.RoleBindingName(),
|
||||
Labels: o.Labels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Name: "default",
|
||||
Namespace: o.Namespace(),
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
Name: o.RoleName(),
|
||||
Kind: "ClusterRole",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
},
|
||||
}
|
||||
return yaml.Marshal(rolebinding)
|
||||
}
|
||||
83
vendor/sigs.k8s.io/controller-tools/pkg/rbac/parser.go
generated
vendored
83
vendor/sigs.k8s.io/controller-tools/pkg/rbac/parser.go
generated
vendored
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
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 rbac contain libraries for generating RBAC manifests from RBAC
|
||||
// annotations in Go source files.
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
type parserOptions struct {
|
||||
rules []rbacv1.PolicyRule
|
||||
}
|
||||
|
||||
// parseAnnotation parses RBAC annotations
|
||||
func (o *parserOptions) parseAnnotation(commentText string) error {
|
||||
for _, comment := range strings.Split(commentText, "\n") {
|
||||
comment := strings.TrimSpace(comment)
|
||||
if strings.HasPrefix(comment, "+rbac") {
|
||||
if ann := general.GetAnnotation(comment, "rbac"); ann != "" {
|
||||
o.rules = append(o.rules, parseRBACTag(ann))
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(comment, "+kubebuilder:rbac") {
|
||||
if ann := general.GetAnnotation(comment, "kubebuilder:rbac"); ann != "" {
|
||||
o.rules = append(o.rules, parseRBACTag(ann))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseRBACTag parses the given RBAC annotation in to an RBAC PolicyRule.
|
||||
// This is copied from Kubebuilder code.
|
||||
func parseRBACTag(tag string) rbacv1.PolicyRule {
|
||||
result := rbacv1.PolicyRule{}
|
||||
for _, elem := range strings.Split(tag, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
log.Fatalf("// +kubebuilder:rbac: tags must be key value pairs. Expected "+
|
||||
"keys [groups=<group1;group2>,resources=<resource1;resource2>,verbs=<verb1;verb2>] "+
|
||||
"Got string: [%s]", tag)
|
||||
}
|
||||
values := strings.Split(value, ";")
|
||||
switch key {
|
||||
case "groups":
|
||||
normalized := []string{}
|
||||
for _, v := range values {
|
||||
if v == "core" {
|
||||
normalized = append(normalized, "")
|
||||
} else {
|
||||
normalized = append(normalized, v)
|
||||
}
|
||||
}
|
||||
result.APIGroups = normalized
|
||||
case "resources":
|
||||
result.Resources = values
|
||||
case "verbs":
|
||||
result.Verbs = values
|
||||
case "urls":
|
||||
result.NonResourceURLs = values
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
77
vendor/sigs.k8s.io/controller-tools/pkg/util/util.go
generated
vendored
77
vendor/sigs.k8s.io/controller-tools/pkg/util/util.go
generated
vendored
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// FileWriter is a io wrapper to write files
|
||||
type FileWriter struct {
|
||||
Fs afero.Fs
|
||||
}
|
||||
|
||||
// WriteCloser returns a WriteCloser to write to given path
|
||||
func (fw *FileWriter) WriteCloser(path string) (io.Writer, error) {
|
||||
if fw.Fs == nil {
|
||||
fw.Fs = afero.NewOsFs()
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
err := fw.Fs.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := fw.Fs.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
// WriteFile write given content to the file path
|
||||
func (fw *FileWriter) WriteFile(filePath string, content []byte) error {
|
||||
if fw.Fs == nil {
|
||||
fw.Fs = afero.NewOsFs()
|
||||
}
|
||||
f, err := fw.WriteCloser(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
if c, ok := f.(io.Closer); ok {
|
||||
defer func() {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
_, err = f.Write(content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
189
vendor/sigs.k8s.io/controller-tools/pkg/webhook/internal/client.go
generated
vendored
189
vendor/sigs.k8s.io/controller-tools/pkg/webhook/internal/client.go
generated
vendored
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var decoder = scheme.Codecs.UniversalDeserializer()
|
||||
|
||||
// NewManifestClient constructs a new manifestClient.
|
||||
func NewManifestClient(file string) client.Client {
|
||||
return &manifestClient{
|
||||
ManifestFile: file,
|
||||
fs: afero.NewOsFs(),
|
||||
}
|
||||
}
|
||||
|
||||
// manifestClient reads from and writes to the file specified by ManifestFile.
|
||||
type manifestClient struct {
|
||||
ManifestFile string
|
||||
|
||||
objects map[schema.GroupVersionKind][]byte
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
var _ client.Client = &manifestClient{}
|
||||
|
||||
func (c *manifestClient) index() error {
|
||||
c.objects = map[schema.GroupVersionKind][]byte{}
|
||||
|
||||
_, err := c.fs.Stat(c.ManifestFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := afero.ReadFile(c.fs, c.ManifestFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objs := bytes.Split(b, []byte("---\n"))
|
||||
for _, objectB := range objs {
|
||||
objB := bytes.TrimSpace(objectB)
|
||||
if len(objB) == 0 {
|
||||
continue
|
||||
}
|
||||
_, gvk, err := decoder.Decode(objB, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.objects[*gvk] = objectB
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get read from the target file.
|
||||
func (c *manifestClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
|
||||
if obj == nil {
|
||||
return errors.New("obj should not be nil")
|
||||
}
|
||||
err := c.index()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
objectB, found := c.objects[gvk]
|
||||
if !found {
|
||||
return apierrors.NewNotFound(schema.GroupResource{}, key.Name)
|
||||
}
|
||||
_, _, err = decoder.Decode(objectB, nil, obj)
|
||||
return err
|
||||
}
|
||||
|
||||
// List does nothing, it should not be invoked.
|
||||
func (c *manifestClient) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
|
||||
return errors.New("method List is not implemented")
|
||||
}
|
||||
|
||||
// Create creates an object and write it to the target file with other objects if any.
|
||||
// If the object needs to be written already exists, it will error out.
|
||||
func (c *manifestClient) Create(ctx context.Context, obj runtime.Object) error {
|
||||
if obj == nil {
|
||||
return errors.New("obj should not be nil")
|
||||
}
|
||||
err := c.index()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
_, found := c.objects[gvk]
|
||||
if found {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apierrors.NewAlreadyExists(schema.GroupResource{}, accessor.GetName())
|
||||
}
|
||||
b, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.objects[gvk] = b
|
||||
return c.writeObjects()
|
||||
}
|
||||
|
||||
// Delete does nothing, it should not be invoked.
|
||||
func (c *manifestClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error {
|
||||
return errors.New("method Delete is not implemented")
|
||||
}
|
||||
|
||||
// Update replace the object if it already exists on the target file.
|
||||
// Otherwise, it creates the object.
|
||||
func (c *manifestClient) Update(ctx context.Context, obj runtime.Object) error {
|
||||
if obj == nil {
|
||||
return errors.New("obj should not be nil")
|
||||
}
|
||||
err := c.index()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
m, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.objects[gvk] = m
|
||||
return c.writeObjects()
|
||||
}
|
||||
|
||||
// writeObjects writes objects to the target file in yaml format separated by `---`.
|
||||
func (c *manifestClient) writeObjects() error {
|
||||
needSeparator := false
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
var gvks []schema.GroupVersionKind
|
||||
for gvk := range c.objects {
|
||||
gvks = append(gvks, gvk)
|
||||
}
|
||||
|
||||
sort.Slice(gvks, func(i, j int) bool {
|
||||
return gvks[i].String() < gvks[j].String()
|
||||
})
|
||||
|
||||
for _, gvk := range gvks {
|
||||
if needSeparator {
|
||||
buf.WriteString("---\n")
|
||||
}
|
||||
needSeparator = true
|
||||
_, err := buf.Write(c.objects[gvk])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return afero.WriteFile(c.fs, c.ManifestFile, buf.Bytes(), 0666)
|
||||
}
|
||||
|
||||
// Status returns a nil client.StatusWriter.
|
||||
func (c *manifestClient) Status() client.StatusWriter {
|
||||
return nil
|
||||
}
|
||||
70
vendor/sigs.k8s.io/controller-tools/pkg/webhook/internal/manager.go
generated
vendored
70
vendor/sigs.k8s.io/controller-tools/pkg/webhook/internal/manager.go
generated
vendored
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
|
||||
)
|
||||
|
||||
// Manager is a dummy manager that does nothing.
|
||||
type Manager struct{}
|
||||
|
||||
var _ manager.Manager = &Manager{}
|
||||
|
||||
// Add will set reqeusted dependencies on the component, and cause the component to be
|
||||
// started when Start is called. Add will inject any dependencies for which the argument
|
||||
// implements the inject interface - e.g. inject.Client
|
||||
func (m *Manager) Add(manager.Runnable) error { return nil }
|
||||
|
||||
// SetFields will set any dependencies on an object for which the object has implemented the inject
|
||||
// interface - e.g. inject.Client.
|
||||
func (m *Manager) SetFields(interface{}) error { return nil }
|
||||
|
||||
// Start starts all registered Controllers and blocks until the Stop channel is closed.
|
||||
// Returns an error if there is an error starting any controller.
|
||||
func (m *Manager) Start(<-chan struct{}) error { return nil }
|
||||
|
||||
// GetConfig returns an initialized Config
|
||||
func (m *Manager) GetConfig() *rest.Config { return nil }
|
||||
|
||||
// GetScheme returns and initialized Scheme
|
||||
func (m *Manager) GetScheme() *runtime.Scheme { return nil }
|
||||
|
||||
// GetAdmissionDecoder returns the runtime.Decoder based on the scheme.
|
||||
func (m *Manager) GetAdmissionDecoder() types.Decoder { return nil }
|
||||
|
||||
// GetClient returns a client configured with the Config
|
||||
func (m *Manager) GetClient() client.Client { return nil }
|
||||
|
||||
// GetFieldIndexer returns a client.FieldIndexer configured with the client
|
||||
func (m *Manager) GetFieldIndexer() client.FieldIndexer { return nil }
|
||||
|
||||
// GetCache returns a cache.Cache
|
||||
func (m *Manager) GetCache() cache.Cache { return nil }
|
||||
|
||||
// GetRecorder returns a new EventRecorder for the provided name
|
||||
func (m *Manager) GetRecorder(name string) record.EventRecorder { return nil }
|
||||
|
||||
// GetRESTMapper returns a RESTMapper
|
||||
func (m *Manager) GetRESTMapper() meta.RESTMapper { return nil }
|
||||
163
vendor/sigs.k8s.io/controller-tools/pkg/webhook/manifests.go
generated
vendored
163
vendor/sigs.k8s.io/controller-tools/pkg/webhook/manifests.go
generated
vendored
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
generateinteral "sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
"sigs.k8s.io/controller-tools/pkg/webhook/internal"
|
||||
)
|
||||
|
||||
// ManifestOptions represent options for generating the webhook manifests.
|
||||
type ManifestOptions struct {
|
||||
InputDir string
|
||||
OutputDir string
|
||||
PatchOutputDir string
|
||||
|
||||
webhooks []webhook.Webhook
|
||||
svrOps *webhook.ServerOptions
|
||||
svr *webhook.Server
|
||||
}
|
||||
|
||||
// SetDefaults sets up the default options for RBAC Manifest generator.
|
||||
func (o *ManifestOptions) SetDefaults() {
|
||||
o.InputDir = filepath.Join(".", "pkg", "webhook")
|
||||
o.OutputDir = filepath.Join(".", "config", "webhook")
|
||||
o.PatchOutputDir = filepath.Join(".", "config", "default")
|
||||
}
|
||||
|
||||
// Validate validates the input options.
|
||||
func (o *ManifestOptions) Validate() error {
|
||||
if _, err := os.Stat(o.InputDir); err != nil {
|
||||
return fmt.Errorf("invalid input directory '%s' %v", o.InputDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate generates RBAC manifests by parsing the RBAC annotations in Go source
|
||||
// files specified in the input directory.
|
||||
func Generate(o *ManifestOptions) error {
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := os.Stat(o.OutputDir)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(o.OutputDir, 0766)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.webhooks = []webhook.Webhook{}
|
||||
o.svrOps = &webhook.ServerOptions{
|
||||
Client: internal.NewManifestClient(path.Join(o.OutputDir, "webhook.yaml")),
|
||||
}
|
||||
err = generateinteral.ParseDir(o.InputDir, o.parseAnnotation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse the input dir: %v", err)
|
||||
}
|
||||
|
||||
o.svr, err = webhook.NewServer("generator", &internal.Manager{}, *o.svrOps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.svr.Register(o.webhooks...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process the input before generating: %v", err)
|
||||
}
|
||||
|
||||
err = o.svr.InstallWebhookManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.labelPatch()
|
||||
}
|
||||
|
||||
func (o *ManifestOptions) labelPatch() error {
|
||||
var kustomizeLabelPatch = `apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Labels }}
|
||||
labels:
|
||||
{{ toYaml . | indent 8 }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
type KustomizeLabelPatch struct {
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
p := KustomizeLabelPatch{Labels: o.svrOps.Service.Selectors}
|
||||
funcMap := template.FuncMap{
|
||||
"toYaml": toYAML,
|
||||
"indent": indent,
|
||||
}
|
||||
temp, err := template.New("kustomizeLabelPatch").Funcs(funcMap).Parse(kustomizeLabelPatch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := temp.Execute(buf, p); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(o.PatchOutputDir, "manager_label_patch.yaml"), buf.Bytes(), 0644)
|
||||
}
|
||||
|
||||
func toYAML(m map[string]string) (string, error) {
|
||||
d, err := yaml.Marshal(m)
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
func indent(n int, s string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, elem := range strings.Split(s, "\n") {
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := buf.WriteRune(' ')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
_, err := buf.WriteString(elem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = buf.WriteRune('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return strings.TrimRight(buf.String(), " \n"), nil
|
||||
}
|
||||
255
vendor/sigs.k8s.io/controller-tools/pkg/webhook/parser.go
generated
vendored
255
vendor/sigs.k8s.io/controller-tools/pkg/webhook/parser.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
webhooktypes "sigs.k8s.io/controller-runtime/pkg/webhook/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
const webhookAnnotationPrefix = "kubebuilder:webhook"
|
||||
|
||||
var (
|
||||
webhookTags = sets.NewString([]string{"groups", "versions", "resources", "verbs", "type", "name", "path", "failure-policy"}...)
|
||||
serverTags = sets.NewString([]string{"port", "cert-dir", "service", "selector", "secret", "host", "mutating-webhook-config-name", "validating-webhook-config-name"}...)
|
||||
)
|
||||
|
||||
// parseAnnotation parses webhook annotations
|
||||
func (o *ManifestOptions) parseAnnotation(commentText string) error {
|
||||
webhookKVMap, serverKVMap := map[string]string{}, map[string]string{}
|
||||
for _, comment := range strings.Split(commentText, "\n") {
|
||||
comment := strings.TrimSpace(comment)
|
||||
anno := general.GetAnnotation(comment, webhookAnnotationPrefix)
|
||||
if len(anno) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, elem := range strings.Split(anno, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
log.Fatalf("// +kubebuilder:webhook: tags must be key value pairs. Example "+
|
||||
"keys [groups=<group1;group2>,resources=<resource1;resource2>,verbs=<verb1;verb2>] "+
|
||||
"Got string: [%s]", anno)
|
||||
}
|
||||
switch {
|
||||
case webhookTags.Has(key):
|
||||
webhookKVMap[key] = value
|
||||
case serverTags.Has(key):
|
||||
serverKVMap[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := o.parseWebhookAnnotation(webhookKVMap); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.parseServerAnnotation(serverKVMap)
|
||||
}
|
||||
|
||||
// parseWebhookAnnotation parses webhook annotations in the same comment group
|
||||
// nolint: gocyclo
|
||||
func (o *ManifestOptions) parseWebhookAnnotation(kvMap map[string]string) error {
|
||||
if len(kvMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
rule := admissionregistrationv1beta1.RuleWithOperations{}
|
||||
w := &admission.Webhook{}
|
||||
for key, value := range kvMap {
|
||||
switch key {
|
||||
case "groups":
|
||||
values := strings.Split(value, ";")
|
||||
normalized := []string{}
|
||||
for _, v := range values {
|
||||
if v == "core" {
|
||||
normalized = append(normalized, "")
|
||||
} else {
|
||||
normalized = append(normalized, v)
|
||||
}
|
||||
}
|
||||
rule.APIGroups = values
|
||||
|
||||
case "versions":
|
||||
values := strings.Split(value, ";")
|
||||
rule.APIVersions = values
|
||||
|
||||
case "resources":
|
||||
values := strings.Split(value, ";")
|
||||
rule.Resources = values
|
||||
|
||||
case "verbs":
|
||||
values := strings.Split(value, ";")
|
||||
var ops []admissionregistrationv1beta1.OperationType
|
||||
for _, v := range values {
|
||||
switch strings.ToLower(v) {
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Create)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Create)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Update)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Update)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Delete)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Delete)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Connect)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Connect)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.OperationAll)):
|
||||
ops = append(ops, admissionregistrationv1beta1.OperationAll)
|
||||
default:
|
||||
return fmt.Errorf("unknown operation: %v", v)
|
||||
}
|
||||
}
|
||||
rule.Operations = ops
|
||||
|
||||
case "type":
|
||||
switch strings.ToLower(value) {
|
||||
case "mutating":
|
||||
w.Type = webhooktypes.WebhookTypeMutating
|
||||
case "validating":
|
||||
w.Type = webhooktypes.WebhookTypeValidating
|
||||
default:
|
||||
return fmt.Errorf("unknown webhook type: %v", value)
|
||||
}
|
||||
|
||||
case "name":
|
||||
w.Name = value
|
||||
|
||||
case "path":
|
||||
w.Path = value
|
||||
|
||||
case "failure-policy":
|
||||
switch strings.ToLower(value) {
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Ignore)):
|
||||
fp := admissionregistrationv1beta1.Ignore
|
||||
w.FailurePolicy = &fp
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Fail)):
|
||||
fp := admissionregistrationv1beta1.Fail
|
||||
w.FailurePolicy = &fp
|
||||
default:
|
||||
return fmt.Errorf("unknown webhook failure policy: %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Rules = []admissionregistrationv1beta1.RuleWithOperations{rule}
|
||||
w.Handlers = []admission.Handler{admission.HandlerFunc(nil)}
|
||||
o.webhooks = append(o.webhooks, w)
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseWebhookAnnotation parses webhook server annotations in the same comment group
|
||||
// nolint: gocyclo
|
||||
func (o *ManifestOptions) parseServerAnnotation(kvMap map[string]string) error {
|
||||
if len(kvMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
for key, value := range kvMap {
|
||||
switch key {
|
||||
case "port":
|
||||
port, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.svrOps.Port = int32(port)
|
||||
case "cert-dir":
|
||||
o.svrOps.CertDir = value
|
||||
case "service":
|
||||
// format: <service=namespace:name>
|
||||
split := strings.Split(value, ":")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("invalid service format: expect <namespace:name>, but got %q", value)
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
if o.svrOps.Service == nil {
|
||||
o.svrOps.Service = &webhook.Service{}
|
||||
}
|
||||
o.svrOps.Service.Namespace = split[0]
|
||||
o.svrOps.Service.Name = split[1]
|
||||
case "selector":
|
||||
// selector of the service. Format: <selector=label1:value1;label2:value2>
|
||||
split := strings.Split(value, ";")
|
||||
if len(split) == 0 {
|
||||
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
if o.svrOps.Service == nil {
|
||||
o.svrOps.Service = &webhook.Service{}
|
||||
}
|
||||
for _, v := range split {
|
||||
l := strings.Split(v, ":")
|
||||
if len(l) != 2 || len(l[0]) == 0 || len(l[1]) == 0 {
|
||||
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
|
||||
}
|
||||
if o.svrOps.Service.Selectors == nil {
|
||||
o.svrOps.Service.Selectors = map[string]string{}
|
||||
}
|
||||
o.svrOps.Service.Selectors[l[0]] = l[1]
|
||||
}
|
||||
case "host":
|
||||
if len(value) == 0 {
|
||||
return errors.New("host should not be empty if specified")
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
o.svrOps.Host = &value
|
||||
|
||||
case "mutating-webhook-config-name":
|
||||
if len(value) == 0 {
|
||||
return errors.New("mutating-webhook-config-name should not be empty if specified")
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
o.svrOps.MutatingWebhookConfigName = value
|
||||
|
||||
case "validating-webhook-config-name":
|
||||
if len(value) == 0 {
|
||||
return errors.New("validating-webhook-config-name should not be empty if specified")
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
o.svrOps.ValidatingWebhookConfigName = value
|
||||
|
||||
case "secret":
|
||||
// format: <secret=namespace:name>
|
||||
split := strings.Split(value, ":")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("invalid secret format: expect <namespace:name>, but got %q", value)
|
||||
}
|
||||
if o.svrOps.BootstrapOptions == nil {
|
||||
o.svrOps.BootstrapOptions = &webhook.BootstrapOptions{}
|
||||
}
|
||||
if o.svrOps.Secret == nil {
|
||||
o.svrOps.Secret = &types.NamespacedName{}
|
||||
}
|
||||
o.svrOps.Secret.Namespace = split[0]
|
||||
o.svrOps.Secret.Name = split[1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
vendor/sigs.k8s.io/testing_frameworks/integration/.gitignore
generated
vendored
Normal file
1
vendor/sigs.k8s.io/testing_frameworks/integration/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
assets/bin
|
||||
8
vendor/sigs.k8s.io/testing_frameworks/integration/README.md
generated
vendored
Normal file
8
vendor/sigs.k8s.io/testing_frameworks/integration/README.md
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Integration Testing Framework
|
||||
|
||||
A framework for integration testing components of kubernetes. This framework is
|
||||
intended to work properly both in CI, and on a local dev machine. It therefore
|
||||
explicitly supports both Linux and Darwin.
|
||||
|
||||
For detailed documentation see the
|
||||
[](https://godoc.org/github.com/kubernetes-sigs/testing_frameworks/integration).
|
||||
20
vendor/sigs.k8s.io/yaml/.gitignore
generated
vendored
Normal file
20
vendor/sigs.k8s.io/yaml/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# OSX leaves these everywhere on SMB shares
|
||||
._*
|
||||
|
||||
# Eclipse files
|
||||
.classpath
|
||||
.project
|
||||
.settings/**
|
||||
|
||||
# Emacs save files
|
||||
*~
|
||||
|
||||
# Vim-related files
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
*.un~
|
||||
Session.vim
|
||||
.netrwhist
|
||||
|
||||
# Go test binaries
|
||||
*.test
|
||||
14
vendor/sigs.k8s.io/yaml/.travis.yml
generated
vendored
Normal file
14
vendor/sigs.k8s.io/yaml/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
language: go
|
||||
dist: xenial
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- diff -u <(echo -n) <(golint $(go list -e ./...) | grep -v YAMLToJSON)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
||||
install:
|
||||
- go get golang.org/x/lint/golint
|
||||
31
vendor/sigs.k8s.io/yaml/CONTRIBUTING.md
generated
vendored
Normal file
31
vendor/sigs.k8s.io/yaml/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
|
||||
|
||||
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||
|
||||
## Getting Started
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
|
||||
<!---
|
||||
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
|
||||
-->
|
||||
|
||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||
|
||||
## Mentorship
|
||||
|
||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||
|
||||
<!---
|
||||
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
|
||||
|
||||
## Contact Information
|
||||
|
||||
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
|
||||
- [Mailing list](URL)
|
||||
|
||||
-->
|
||||
25
vendor/sigs.k8s.io/yaml/OWNERS
generated
vendored
Normal file
25
vendor/sigs.k8s.io/yaml/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
approvers:
|
||||
- dims
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- deads2k
|
||||
- sttts
|
||||
- liggitt
|
||||
- caesarxuchao
|
||||
reviewers:
|
||||
- dims
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- gmarek
|
||||
- sttts
|
||||
- ncdc
|
||||
- tallclair
|
||||
labels:
|
||||
- sig/api-machinery
|
||||
121
vendor/sigs.k8s.io/yaml/README.md
generated
vendored
Normal file
121
vendor/sigs.k8s.io/yaml/README.md
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
# YAML marshaling and unmarshaling support for Go
|
||||
|
||||
[](https://travis-ci.org/ghodss/yaml)
|
||||
|
||||
## Introduction
|
||||
|
||||
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
|
||||
|
||||
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
|
||||
|
||||
## Compatibility
|
||||
|
||||
This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
|
||||
|
||||
## Caveats
|
||||
|
||||
**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example:
|
||||
|
||||
```
|
||||
BAD:
|
||||
exampleKey: !!binary gIGC
|
||||
|
||||
GOOD:
|
||||
exampleKey: gIGC
|
||||
... and decode the base64 data in your code.
|
||||
```
|
||||
|
||||
**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys.
|
||||
|
||||
## Installation and usage
|
||||
|
||||
To install, run:
|
||||
|
||||
```
|
||||
$ go get github.com/ghodss/yaml
|
||||
```
|
||||
|
||||
And import using:
|
||||
|
||||
```
|
||||
import "github.com/ghodss/yaml"
|
||||
```
|
||||
|
||||
Usage is very similar to the JSON library:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"` // Affects YAML field names too.
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Marshal a Person struct to YAML.
|
||||
p := Person{"John", 30}
|
||||
y, err := yaml.Marshal(p)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(y))
|
||||
/* Output:
|
||||
age: 30
|
||||
name: John
|
||||
*/
|
||||
|
||||
// Unmarshal the YAML back into a Person struct.
|
||||
var p2 Person
|
||||
err = yaml.Unmarshal(y, &p2)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(p2)
|
||||
/* Output:
|
||||
{John 30}
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
j := []byte(`{"name": "John", "age": 30}`)
|
||||
y, err := yaml.JSONToYAML(j)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(y))
|
||||
/* Output:
|
||||
name: John
|
||||
age: 30
|
||||
*/
|
||||
j2, err := yaml.YAMLToJSON(y)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(j2))
|
||||
/* Output:
|
||||
{"age":30,"name":"John"}
|
||||
*/
|
||||
}
|
||||
```
|
||||
9
vendor/sigs.k8s.io/yaml/RELEASE.md
generated
vendored
Normal file
9
vendor/sigs.k8s.io/yaml/RELEASE.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Release Process
|
||||
|
||||
The `yaml` Project is released on an as-needed basis. The process is as follows:
|
||||
|
||||
1. An issue is proposing a new release with a changelog since the last release
|
||||
1. All [OWNERS](OWNERS) must LGTM this release
|
||||
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
|
||||
1. The release issue is closed
|
||||
1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
|
||||
17
vendor/sigs.k8s.io/yaml/SECURITY_CONTACTS
generated
vendored
Normal file
17
vendor/sigs.k8s.io/yaml/SECURITY_CONTACTS
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Defined below are the security contacts for this repo.
|
||||
#
|
||||
# They are the contact point for the Product Security Team to reach out
|
||||
# to for triaging and handling of incoming issues.
|
||||
#
|
||||
# The below names agree to abide by the
|
||||
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
|
||||
# and will be removed and replaced if they violate that agreement.
|
||||
#
|
||||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||
|
||||
cjcullen
|
||||
jessfraz
|
||||
liggitt
|
||||
philips
|
||||
tallclair
|
||||
3
vendor/sigs.k8s.io/yaml/code-of-conduct.md
generated
vendored
Normal file
3
vendor/sigs.k8s.io/yaml/code-of-conduct.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Kubernetes Community Code of Conduct
|
||||
|
||||
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
|
||||
Reference in New Issue
Block a user