From f3dac60421b70dff77c849467627abd769c920bd Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Sat, 31 Mar 2018 13:40:18 -0700 Subject: [PATCH] Add CRD Validation (#962) ref: https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/#validation --- apis/voyager/v1beta1/certificate.go | 6 +- apis/voyager/v1beta1/crd.go | 67 +-- apis/voyager/v1beta1/crds.yaml | 534 +++++++++++++++++- apis/voyager/v1beta1/diff.go | 3 +- apis/voyager/v1beta1/ingress.go | 6 +- glide.lock | 18 +- glide.yaml | 2 +- hack/gencrd/main.go | 29 + pkg/operator/ingress_crds.go | 9 +- pkg/operator/ingresses.go | 9 +- .../kutil/apiextensions/v1beta1/cli-utils.go | 151 +++++ .../apiextensions/v1beta1/convert_types.go | 127 +++++ .../apiextensions/v1beta1/crdvalidation.go | 46 ++ .../kutil/apiextensions/v1beta1/kubernetes.go | 5 + vendor/github.com/pkg/errors/stack.go | 51 +- vendor/github.com/spf13/cobra/command.go | 10 + vendor/github.com/spf13/pflag/bytes.go | 105 ++++ vendor/github.com/spf13/pflag/count.go | 12 +- .../github.com/spf13/pflag/duration_slice.go | 128 +++++ vendor/github.com/spf13/pflag/flag.go | 135 ++++- vendor/github.com/spf13/pflag/int16.go | 88 +++ vendor/github.com/spf13/pflag/string_array.go | 8 +- vendor/github.com/spf13/pflag/string_slice.go | 20 + vendor/gopkg.in/ini.v1/LICENSE | 2 +- vendor/gopkg.in/ini.v1/file.go | 403 +++++++++++++ vendor/gopkg.in/ini.v1/ini.go | 384 +------------ vendor/gopkg.in/ini.v1/key.go | 64 ++- vendor/gopkg.in/ini.v1/parser.go | 50 +- vendor/gopkg.in/ini.v1/section.go | 9 + vendor/gopkg.in/ini.v1/struct.go | 16 +- 30 files changed, 1960 insertions(+), 537 deletions(-) create mode 100644 hack/gencrd/main.go create mode 100644 vendor/github.com/appscode/kutil/apiextensions/v1beta1/cli-utils.go create mode 100644 vendor/github.com/appscode/kutil/apiextensions/v1beta1/convert_types.go create mode 100644 vendor/github.com/appscode/kutil/apiextensions/v1beta1/crdvalidation.go create mode 100644 vendor/github.com/spf13/pflag/bytes.go create mode 100644 vendor/github.com/spf13/pflag/duration_slice.go create mode 100644 vendor/github.com/spf13/pflag/int16.go create mode 100644 vendor/gopkg.in/ini.v1/file.go diff --git a/apis/voyager/v1beta1/certificate.go b/apis/voyager/v1beta1/certificate.go index c6d5a95ef..0acdac13c 100644 --- a/apis/voyager/v1beta1/certificate.go +++ b/apis/voyager/v1beta1/certificate.go @@ -6,9 +6,9 @@ import ( ) const ( - ResourceKindCertificate = "Certificate" - ResourceNameCertificate = "certificate" - ResourceTypeCertificate = "certificates" + ResourceKindCertificate = "Certificate" + ResourceSingularCertificate = "certificate" + ResourcePluralCertificate = "certificates" ) // +genclient diff --git a/apis/voyager/v1beta1/crd.go b/apis/voyager/v1beta1/crd.go index 2dc30bbe0..59c264aa0 100644 --- a/apis/voyager/v1beta1/crd.go +++ b/apis/voyager/v1beta1/crd.go @@ -1,49 +1,44 @@ package v1beta1 import ( - "github.com/appscode/voyager/apis/voyager" + crdutils "github.com/appscode/kutil/apiextensions/v1beta1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const VoyagerFinalizer = "voyager.appscode.com" - func (r Ingress) CustomResourceDefinition() *apiextensions.CustomResourceDefinition { - return &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: ResourceTypeIngress + "." + SchemeGroupVersion.Group, - Labels: map[string]string{"app": "voyager"}, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: voyager.GroupName, - Version: SchemeGroupVersion.Version, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Singular: ResourceNameIngress, - Plural: ResourceTypeIngress, - Kind: ResourceKindIngress, - ShortNames: []string{"ing"}, - }, + return crdutils.NewCustomResourceDefinition(crdutils.Config{ + Group: SchemeGroupVersion.Group, + Version: SchemeGroupVersion.Version, + Plural: ResourcePluralIngress, + Singular: ResourceSingularIngress, + Kind: ResourceKindIngress, + ListKind: ResourceKindIngress + "List", + ShortNames: []string{"ing"}, + ResourceScope: string(apiextensions.NamespaceScoped), + Labels: crdutils.Labels{ + LabelsMap: map[string]string{"app": "voyager"}, }, - } + SpecDefinitionName: "github.com/appscode/voyager/apis/voyager/v1beta1.Ingress", + EnableValidation: true, + GetOpenAPIDefinitions: GetOpenAPIDefinitions, + }) } func (c Certificate) CustomResourceDefinition() *apiextensions.CustomResourceDefinition { - return &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: ResourceTypeCertificate + "." + SchemeGroupVersion.Group, - Labels: map[string]string{"app": "voyager"}, - }, - Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: voyager.GroupName, - Version: SchemeGroupVersion.Version, - Scope: apiextensions.NamespaceScoped, - Names: apiextensions.CustomResourceDefinitionNames{ - Singular: ResourceNameCertificate, - Plural: ResourceTypeCertificate, - Kind: ResourceKindCertificate, - ShortNames: []string{"cert"}, - }, + return crdutils.NewCustomResourceDefinition(crdutils.Config{ + Group: SchemeGroupVersion.Group, + Version: SchemeGroupVersion.Version, + Plural: ResourcePluralCertificate, + Singular: ResourceSingularCertificate, + Kind: ResourceKindCertificate, + ListKind: ResourceKindCertificate + "List", + ShortNames: []string{"cert"}, + ResourceScope: string(apiextensions.NamespaceScoped), + Labels: crdutils.Labels{ + LabelsMap: map[string]string{"app": "voyager"}, }, - } + SpecDefinitionName: "github.com/appscode/voyager/apis/voyager/v1beta1.Certificate", + EnableValidation: true, + GetOpenAPIDefinitions: GetOpenAPIDefinitions, + }) } diff --git a/apis/voyager/v1beta1/crds.yaml b/apis/voyager/v1beta1/crds.yaml index 293b0f7b6..0b618052f 100644 --- a/apis/voyager/v1beta1/crds.yaml +++ b/apis/voyager/v1beta1/crds.yaml @@ -1,35 +1,545 @@ +--- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: certificates.voyager.appscode.com + creationTimestamp: null labels: app: voyager + name: ingresses.voyager.appscode.com spec: group: voyager.appscode.com names: - kind: Certificate - listKind: CertificateList - plural: certificates + kind: Ingress + listKind: IngressList + plural: ingresses shortNames: - - cert - singular: certificate + - ing + singular: ingress scope: Namespaced + validation: + openAPIV3Schema: + description: Custom Ingress type for Voyager. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: {} + spec: + description: IngressSpec describes the Ingress the user wishes to exist. + properties: + affinity: {} + backend: + properties: + backendRules: + description: Serialized HAProxy rules to apply on server backend + including request, response or header rewrite. acls also can be + used. https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#1 + items: + type: string + type: array + headerRules: + description: |- + Header rules to modifies the header. + + Deprecated: Use backendRule, will be removed. + items: + type: string + type: array + hostNames: + description: Host names to forward traffic to. If empty traffic + will be forwarded to all subsets instance. If set only matched + hosts will get the traffic. This is an handy way to send traffic + to Specific StatefulSet pod. IE. Setting [web-0] will send traffic + to only web-0 host for this StatefulSet, https://kubernetes.io/docs/tasks/stateful-application/basic-stateful-set/#creating-a-statefulset + items: + type: string + type: array + name: + description: User can specify backend name for using it with custom + acl Otherwise it will be generated + type: string + rewriteRules: + description: |- + Path rewrite rules with haproxy formatted regex. + + Deprecated: Use backendRule, will be removed. + items: + type: string + type: array + serviceName: + description: Specifies the name of the referenced service. + type: string + servicePort: {} + externalIPs: + description: externalIPs is a list of IP addresses for which nodes in + the cluster will also accept traffic for this service. These IPs + are not managed by Kubernetes. The user is responsible for ensuring + that traffic arrives at a node with this IP. A common example is + external load-balancers that are not part of the Kubernetes system. + items: + type: string + type: array + frontendRules: + description: Frontend rules specifies a set of rules that should be + applied in HAProxy frontend configuration. The set of keywords are + from here https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.1 + Only frontend sections can be applied here. It is up to user to provide + valid set of rules. This allows acls or other options in frontend + sections in HAProxy config. Frontend rules will be mapped with Ingress + Rules according to port. + items: + properties: + auth: + properties: + basic: + properties: + realm: + type: string + secretName: + type: string + tls: + properties: + errorPage: + type: string + headers: + type: object + secretName: + type: string + verifyClient: + type: string + port: {} + rules: + description: Serialized rules + items: + type: string + type: array + type: array + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references to + secrets in the same namespace to use for pulling any of the images + used by this PodSpec. If specified, these secrets will be passed to + individual puller implementations for them to use. For example, in + the case of docker, only DockerConfig type secrets are honored. More + info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + items: {} + type: array + loadBalancerSourceRanges: + description: 'Optional: If specified and supported by the platform, + this will restrict traffic through the cloud-provider load-balancer + will be restricted to the specified client IPs. This field will be + ignored if the cloud-provider does not support the feature. https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + items: + type: string + type: array + nodeSelector: + description: 'NodeSelector is a selector which must be true for the + pod to fit on a node. Selector which must match a node''s labels for + the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + resources: {} + rules: + description: A list of host rules used to configure the Ingress. If + unspecified, or no rule matches, all traffic is sent to the default + backend. + items: + description: IngressRule represents the rules mapping the paths under + a specified host to the related backend services. Incoming requests + are first evaluated for a host match, then routed to the backend + associated with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain name of a network + host, as defined by RFC 3986. Note the following deviations + from the \"host\" part of the URI as defined in the RFC: 1. + IPs are not allowed. Currently an IngressRuleValue can only + apply to the\n\t IP in the Spec of the parent Ingress.\n2. + The `:` delimiter is not respected because ports are not allowed.\n\t + \ Currently the port of an Ingress is implicitly :80 for http + and\n\t :443 for https.\nBoth these may change in the future. + Incoming requests are matched against the host before the IngressRuleValue. + If the host is unspecified, the Ingress routes all traffic based + on the specified IngressRuleValue." + type: string + http: + description: 'HTTPIngressRuleValue is a list of http selectors + pointing to backends. In the example: http:///? + -> backend where where parts of the url correspond to RFC 3986, + this resource will be used to match against everything after + the last ''/'' and before the first ''?'' or ''#''.' + properties: + address: + description: The network address to listen HTTP(s) connections + on. + type: string + noTLS: + description: Set noTLS = true to force plain text. Else, auto + detect like present + type: boolean + nodePort: {} + paths: + description: A collection of paths that map requests to backends. + items: + description: HTTPIngressPath associates a path regex with + a backend. Incoming urls matching the path are forwarded + to the backend. + properties: + backend: + properties: + backendRules: + description: Serialized HAProxy rules to apply on + server backend including request, response or + header rewrite. acls also can be used. https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#1 + items: + type: string + type: array + headerRules: + description: |- + Header rules to modifies the header. + + Deprecated: Use backendRule, will be removed. + items: + type: string + type: array + hostNames: + description: Host names to forward traffic to. If + empty traffic will be forwarded to all subsets + instance. If set only matched hosts will get the + traffic. This is an handy way to send traffic + to Specific StatefulSet pod. IE. Setting [web-0] + will send traffic to only web-0 host for this + StatefulSet, https://kubernetes.io/docs/tasks/stateful-application/basic-stateful-set/#creating-a-statefulset + items: + type: string + type: array + name: + description: User can specify backend name for using + it with custom acl Otherwise it will be generated + type: string + rewriteRules: + description: |- + Path rewrite rules with haproxy formatted regex. + + Deprecated: Use backendRule, will be removed. + items: + type: string + type: array + serviceName: + description: Specifies the name of the referenced + service. + type: string + servicePort: {} + path: + description: Path is a extended POSIX regex as defined + by IEEE Std 1003.1, (i.e this follows the egrep/unix + syntax, not the perl syntax) matched against the path + of an incoming request. Currently it can contain characters + disallowed from the conventional "path" part of a + URL as defined by RFC 3986. Paths must begin with + a '/'. If unspecified, the path defaults to a catch + all sending traffic to the backend. + type: string + type: array + port: {} + required: + - paths + tcp: + properties: + address: + description: The network address to listen TCP connections + on. + type: string + alpn: + description: Application-Layer Protocol Negotiation (ALPN) + is a Transport Layer Security (TLS) extension for application + layer protocol negotiation. ALPN allows the application + layer to negotiate which protocol should be performed over + a secure connection in a manner which avoids additional + round trips and which is independent of the application + layer protocols. It is used by HTTP/2. If provided a list + of alpn will be added to port as alpn option1,option2,... + If SecretName is Provided this secret will be used to terminate + SSL with alpn options. If Secret name is not provided backend + server is responsible for handling SSL. + items: + type: string + type: array + backend: + description: IngressBackend describes all endpoints for a + given service and port. + properties: + backendRules: + description: Serialized HAProxy rules to apply on server + backend including request, response or header rewrite. + acls also can be used. https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#1 + items: + type: string + type: array + hostNames: + description: Host names to forward traffic to. If empty + traffic will be forwarded to all subsets instance. If + set only matched hosts will get the traffic. This is + an handy way to send traffic to Specific StatefulSet + pod. IE. Setting [web-0] will send traffic to only web-0 + host for this StatefulSet, https://kubernetes.io/docs/tasks/stateful-application/basic-stateful-set/#creating-a-statefulset + items: + type: string + type: array + name: + description: User can specify backend name for using it + with custom acl Otherwise it will be generated + type: string + serviceName: + description: Specifies the name of the referenced service. + type: string + servicePort: {} + noTLS: + description: Set noTLS = true to force plain text. Else, auto + detect like present + type: boolean + nodePort: {} + port: {} + type: array + schedulerName: + description: If specified, the pod will be dispatched by specified scheduler. + If not specified, the pod will be dispatched by default scheduler. + type: string + tls: + description: TLS is the TLS configuration. Currently the Ingress only + supports a single TLS port, 443, and assumes TLS termination. If multiple + members of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through the SNI + TLS extension. + items: + description: IngressTLS describes the transport layer security associated + with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the TLS certificate. + The values in this list must match the name/s used in the tlsSecret. + Defaults to the wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + ref: + description: LocalTypedReference contains enough information to + let you inspect or modify the referred object. + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + secretName: + description: SecretName is the name of the secret used to terminate + SSL traffic on 443. Field is left optional to allow SSL routing + based on SNI hostname alone. If the SNI host in a listener conflicts + with the "Host" header field used by an IngressRule, the SNI + host is used for termination and value of the Host header is + used for routing. Deprecated + type: string + type: array + tolerations: + description: If specified, the pod's tolerations. + items: {} + type: array + status: + description: IngressStatus describe the current state of the Ingress. + properties: + loadBalancer: {} version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: null --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: ingresses.voyager.appscode.com + creationTimestamp: null labels: app: voyager + name: certificates.voyager.appscode.com spec: group: voyager.appscode.com names: - kind: Ingress - listKind: IngressList - plural: ingresses + kind: Certificate + listKind: CertificateList + plural: certificates shortNames: - - ing - singular: ingress + - cert + singular: certificate scope: Namespaced + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: {} + spec: + properties: + acmeStagingURL: + description: ACME server that will be used to obtain this certificate. + Deprecated + type: string + acmeUserSecretName: + description: |- + Secret contains ACMEUser information. Secret must contain a key `email` If empty tries to find an Secret via domains if not found create an ACMEUser and stores as a secret. Secrets key to be expected: + ACME_EMAIL -> required, if not provided it will through error. + ACME_SERVER_URL -> custom server url to generate certificates, default is lets encrypt. + ACME_USER_DATA -> user data, if not found one will be created for the provided email, + and stored in the key. + type: string + challengeProvider: + properties: + dns: + properties: + credentialSecretName: + type: string + provider: + description: DNS Provider from the list https://github.com/appscode/voyager/blob/master/docs/tasks/certificate/providers.md + type: string + http: + properties: + ingress: + description: LocalTypedReference contains enough information + to let you inspect or modify the referred object. + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + domains: + description: Tries to obtain a single certificate using all domains + passed into Domains. The first domain in domains is used for the CommonName + field of the certificate, all other domains are added using the Subject + Alternate Names extension. + items: + type: string + type: array + email: + description: Deprecated + type: string + httpProviderIngressReference: + description: LocalTypedReference contains enough information to let + you inspect or modify the referred object. + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + provider: + description: Following fields are deprecated and will removed in future + version. https://github.com/appscode/voyager/pull/506 Deprecated. + DNS Provider. + type: string + providerCredentialSecretName: + description: ProviderCredentialSecretName is used to create the acme + client, that will do needed processing in DNS. Deprecated + type: string + storage: + properties: + secret: {} + vault: + properties: + name: + type: string + prefix: + type: string + required: + - challengeProvider + - acmeUserSecretName + status: + properties: + acmeUserSecretName: + description: Deprecated + type: string + certificateObtained: + description: Deprecated + type: boolean + conditions: + items: + properties: + lastUpdateTime: {} + message: + description: human readable message with details about the request + state + type: string + reason: + description: brief reason for the request state + type: string + type: + description: request approval state, currently Approved or Denied. + type: string + required: + - type + type: array + creationTime: {} + details: + properties: + accountRef: + type: string + certStableUrl: + type: string + certUrl: + type: string + domain: + type: string + required: + - domain + - certUrl + - certStableUrl + lastIssuedCertificate: + properties: + accountRef: + type: string + certStableURL: + type: string + certURL: + type: string + notAfter: {} + notBefore: {} + serialNumber: + type: string + required: + - certURL + - certStableURL + message: + description: Deprecated + type: string version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: null diff --git a/apis/voyager/v1beta1/diff.go b/apis/voyager/v1beta1/diff.go index bd1836313..dbf066988 100644 --- a/apis/voyager/v1beta1/diff.go +++ b/apis/voyager/v1beta1/diff.go @@ -8,6 +8,7 @@ import ( "time" core_util "github.com/appscode/kutil/core/v1" + "github.com/appscode/voyager/apis/voyager" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -39,7 +40,7 @@ func (r Ingress) HasChanged(o Ingress) (bool, error) { return false, errors.New("not the same Ingress") } - if o.DeletionTimestamp != nil && core_util.HasFinalizer(o.ObjectMeta, VoyagerFinalizer) { + if o.DeletionTimestamp != nil && core_util.HasFinalizer(o.ObjectMeta, voyager.GroupName) { return true, nil } diff --git a/apis/voyager/v1beta1/ingress.go b/apis/voyager/v1beta1/ingress.go index 22b60ebc9..9bbc78173 100644 --- a/apis/voyager/v1beta1/ingress.go +++ b/apis/voyager/v1beta1/ingress.go @@ -7,9 +7,9 @@ import ( ) const ( - ResourceKindIngress = "Ingress" - ResourceNameIngress = "ingress" - ResourceTypeIngress = "ingresses" + ResourceKindIngress = "Ingress" + ResourceSingularIngress = "ingress" + ResourcePluralIngress = "ingresses" ) // +genclient diff --git a/glide.lock b/glide.lock index 95c8b63ca..bf9cce498 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: c2dffc27b4f9fb7a28af11d0bad9bdf946b348b31237a5c328b77a953de4760f -updated: 2018-03-29T09:58:50.76079968-07:00 +hash: 1596963029d94145703f740f040faad597b7cc93be4e9933cb70a743c717b579 +updated: 2018-03-31T13:35:04.621496894-07:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -25,7 +25,7 @@ imports: - types - version - name: github.com/appscode/jsonpatch - version: d1ada3d25b084ae42746377df856bbc74a901503 + version: 58a38a46ddb591a543c6b1003213353e29331c5a - name: github.com/appscode/kube-mon version: eeb8de0ba83795dc2fd86c3667d6566a62276160 subpackages: @@ -40,7 +40,7 @@ imports: - admission/v1beta1 - registry/admissionreview/v1beta1 - name: github.com/appscode/kutil - version: 9618833b480d6b78f02121dc4d231a933e30a8cc + version: d05ce0e9bbbec24d6f6f1fdce4a0a7c65ff221e5 subpackages: - apiextensions/v1beta1 - apps/v1beta1 @@ -152,7 +152,7 @@ imports: - name: github.com/dgrijalva/jwt-go version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 - name: github.com/dnsimple/dnsimple-go - version: 67fc221205115459ce8a04043c7ca567072c92ec + version: 3a4b2e4e17f93fa9c2780f1c98b82da06fa8a826 subpackages: - dnsimple - name: github.com/elazarl/go-bindata-assetfs @@ -375,7 +375,7 @@ imports: - name: github.com/pires/go-proxyproto version: 2f38359974ffed92842a86ffcc17001b3f525e34 - name: github.com/pkg/errors - version: 645ef00459ed84a119197bfb8d8205042c6df63d + version: 816c9085562cd7ee03e7f8188a1cfd942858cded - name: github.com/prometheus/client_golang version: c5b7fccd204277076155f10851dad72b76a49317 subpackages: @@ -423,11 +423,11 @@ imports: subpackages: - mem - name: github.com/spf13/cobra - version: a1f051bc3eba734da4772d60e2d677f47cf93ef4 + version: 4dab30cb33e6633c33c787106bafbfbfdde7842d subpackages: - doc - name: github.com/spf13/pflag - version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 + version: 1cd4a0c365d95803411bec89fb7b76bade17053b - name: github.com/timewasted/linode version: 37e84520dcf74488f67654f9c775b9752c232dc1 subpackages: @@ -581,7 +581,7 @@ imports: - name: gopkg.in/inf.v0 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 - name: gopkg.in/ini.v1 - version: 20b96f641a5ea98f2f8619ff4f3e061cff4833bd + version: 6333e38ac20b8949a8dd68baa3650f4dee8f39f0 - name: gopkg.in/natefinch/lumberjack.v2 version: 20b71e5b60d756d3d2f80def009790325acc2b23 - name: gopkg.in/square/go-jose.v1 diff --git a/glide.yaml b/glide.yaml index 5120a282b..2cf6bf11a 100644 --- a/glide.yaml +++ b/glide.yaml @@ -47,7 +47,7 @@ import: - package: github.com/spf13/cobra version: master - package: github.com/spf13/pflag - version: v1.0.0 + version: master - package: github.com/tredoe/osutil - package: github.com/xenolf/lego repo: https://github.com/appscode/lego.git diff --git a/hack/gencrd/main.go b/hack/gencrd/main.go new file mode 100644 index 000000000..50d077f3e --- /dev/null +++ b/hack/gencrd/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" + + "github.com/appscode/go/log" + "github.com/appscode/go/runtime" + crdutils "github.com/appscode/kutil/apiextensions/v1beta1" + api "github.com/appscode/voyager/apis/voyager/v1beta1" + crd_api "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" +) + +func main() { + filename := runtime.GOPath() + "/src/github.com/appscode/voyager/apis/voyager/v1beta1/crds.yaml" + + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + crds := []*crd_api.CustomResourceDefinition{ + api.Ingress{}.CustomResourceDefinition(), + api.Certificate{}.CustomResourceDefinition(), + } + for _, crd := range crds { + crdutils.MarshallCrd(f, crd, "yaml") + } +} diff --git a/pkg/operator/ingress_crds.go b/pkg/operator/ingress_crds.go index c5edc9732..aa9c0bbff 100644 --- a/pkg/operator/ingress_crds.go +++ b/pkg/operator/ingress_crds.go @@ -8,6 +8,7 @@ import ( core_util "github.com/appscode/kutil/core/v1" "github.com/appscode/kutil/meta" "github.com/appscode/kutil/tools/queue" + "github.com/appscode/voyager/apis/voyager" api "github.com/appscode/voyager/apis/voyager/v1beta1" "github.com/appscode/voyager/client/clientset/versioned/typed/voyager/v1beta1/util" "github.com/appscode/voyager/pkg/eventer" @@ -82,19 +83,19 @@ func (op *Operator) reconcileEngress(key string) error { ctrl := ingress.NewController(NewID(context.Background()), op.KubeClient, op.CRDClient, op.VoyagerClient, op.PromClient, op.svcLister, op.epLister, op.Config, engress) if engress.DeletionTimestamp != nil { - if core_util.HasFinalizer(engress.ObjectMeta, api.VoyagerFinalizer) { + if core_util.HasFinalizer(engress.ObjectMeta, voyager.GroupName) { glog.Infof("Delete for engress %s\n", key) ctrl.Delete() util.PatchIngress(op.VoyagerClient.VoyagerV1beta1(), engress, func(obj *api.Ingress) *api.Ingress { - obj.ObjectMeta = core_util.RemoveFinalizer(obj.ObjectMeta, api.VoyagerFinalizer) + obj.ObjectMeta = core_util.RemoveFinalizer(obj.ObjectMeta, voyager.GroupName) return obj }) } } else { glog.Infof("Sync/Add/Update for engress %s\n", key) - if !core_util.HasFinalizer(engress.ObjectMeta, api.VoyagerFinalizer) { + if !core_util.HasFinalizer(engress.ObjectMeta, voyager.GroupName) { util.PatchIngress(op.VoyagerClient.VoyagerV1beta1(), engress, func(obj *api.Ingress) *api.Ingress { - obj.ObjectMeta = core_util.AddFinalizer(obj.ObjectMeta, api.VoyagerFinalizer) + obj.ObjectMeta = core_util.AddFinalizer(obj.ObjectMeta, voyager.GroupName) return obj }) } diff --git a/pkg/operator/ingresses.go b/pkg/operator/ingresses.go index bfd7532a3..b6fdfc503 100644 --- a/pkg/operator/ingresses.go +++ b/pkg/operator/ingresses.go @@ -9,6 +9,7 @@ import ( ext_util "github.com/appscode/kutil/extensions/v1beta1" "github.com/appscode/kutil/meta" "github.com/appscode/kutil/tools/queue" + "github.com/appscode/voyager/apis/voyager" api "github.com/appscode/voyager/apis/voyager/v1beta1" "github.com/appscode/voyager/pkg/eventer" "github.com/appscode/voyager/pkg/ingress" @@ -95,19 +96,19 @@ func (op *Operator) reconcileIngress(key string) error { ctrl := ingress.NewController(NewID(context.Background()), op.KubeClient, op.CRDClient, op.VoyagerClient, op.PromClient, op.svcLister, op.epLister, op.Config, engress) if ing.DeletionTimestamp != nil { - if core_util.HasFinalizer(ing.ObjectMeta, api.VoyagerFinalizer) { + if core_util.HasFinalizer(ing.ObjectMeta, voyager.GroupName) { glog.Infof("Delete for engress %s\n", key) ctrl.Delete() ext_util.PatchIngress(op.KubeClient, ing, func(obj *extensions.Ingress) *extensions.Ingress { - obj.ObjectMeta = core_util.RemoveFinalizer(obj.ObjectMeta, api.VoyagerFinalizer) + obj.ObjectMeta = core_util.RemoveFinalizer(obj.ObjectMeta, voyager.GroupName) return obj }) } } else { glog.Infof("Sync/Add/Update for ingress %s\n", key) - if !core_util.HasFinalizer(ing.ObjectMeta, api.VoyagerFinalizer) { + if !core_util.HasFinalizer(ing.ObjectMeta, voyager.GroupName) { ext_util.PatchIngress(op.KubeClient, ing, func(obj *extensions.Ingress) *extensions.Ingress { - obj.ObjectMeta = core_util.AddFinalizer(obj.ObjectMeta, api.VoyagerFinalizer) + obj.ObjectMeta = core_util.AddFinalizer(obj.ObjectMeta, voyager.GroupName) return obj }) } diff --git a/vendor/github.com/appscode/kutil/apiextensions/v1beta1/cli-utils.go b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/cli-utils.go new file mode 100644 index 000000000..c7b109843 --- /dev/null +++ b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/cli-utils.go @@ -0,0 +1,151 @@ +// Copyright 2018 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1beta1 + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/ghodss/yaml" + "github.com/spf13/pflag" + extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Config stores the user configuration input +type Config struct { + SpecDefinitionName string + EnableValidation bool + OutputFormat string + Labels Labels + Annotations Labels + ResourceScope string + Group string + Kind string + Version string + Plural string + Singular string + ShortNames []string + ListKind string + GetOpenAPIDefinitions GetAPIDefinitions +} + +type Labels struct { + LabelsString string + LabelsMap map[string]string +} + +func (labels *Labels) Type() string { return "Labels" } + +// Implement the flag.Value interface +func (labels *Labels) String() string { + return labels.LabelsString +} + +// Merge labels create a new map with labels merged. +func (labels *Labels) Merge(otherLabels map[string]string) map[string]string { + mergedLabels := map[string]string{} + + for key, value := range otherLabels { + mergedLabels[key] = value + } + + for key, value := range labels.LabelsMap { + mergedLabels[key] = value + } + return mergedLabels +} + +// Implement the flag.Set interface +func (labels *Labels) Set(value string) error { + m := map[string]string{} + if value != "" { + splited := strings.Split(value, ",") + for _, pair := range splited { + sp := strings.Split(pair, "=") + m[sp[0]] = sp[1] + } + } + (*labels).LabelsMap = m + (*labels).LabelsString = value + return nil +} + +func NewCustomResourceDefinition(config Config) *extensionsobj.CustomResourceDefinition { + crd := &extensionsobj.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.Plural + "." + config.Group, + Labels: config.Labels.LabelsMap, + Annotations: config.Annotations.LabelsMap, + }, + TypeMeta: CustomResourceDefinitionTypeMeta, + Spec: extensionsobj.CustomResourceDefinitionSpec{ + Group: config.Group, + Version: config.Version, + Scope: extensionsobj.ResourceScope(config.ResourceScope), + Names: extensionsobj.CustomResourceDefinitionNames{ + Plural: config.Plural, + Singular: config.Singular, + Kind: config.Kind, + ShortNames: config.ShortNames, + ListKind: config.ListKind, + }, + }, + } + + if config.SpecDefinitionName != "" && config.EnableValidation == true { + crd.Spec.Validation = GetCustomResourceValidation(config.SpecDefinitionName, config.GetOpenAPIDefinitions) + } + + return crd +} + +func MarshallCrd(w io.Writer, crd *extensionsobj.CustomResourceDefinition, outputFormat string) { + jsonBytes, err := json.MarshalIndent(crd, "", " ") + if err != nil { + fmt.Println("error:", err) + } + + if outputFormat == "json" { + w.Write(jsonBytes) + } else { + yamlBytes, err := yaml.JSONToYAML(jsonBytes) + if err != nil { + fmt.Println("error:", err) + } + w.Write([]byte("---\n")) + w.Write(yamlBytes) + } +} + +// InitFlags prepares command line flags parser +func InitFlags(cfg *Config, fs *pflag.FlagSet) *pflag.FlagSet { + fs.Var(&cfg.Labels, "labels", "Labels") + fs.Var(&cfg.Annotations, "annotations", "Annotations") + fs.BoolVar(&cfg.EnableValidation, "with-validation", true, "Add CRD validation field, default: true") + fs.StringVar(&cfg.Group, "apigroup", "custom.example.com", "CRD api group") + fs.StringVar(&cfg.SpecDefinitionName, "spec-name", "", "CRD spec definition name") + fs.StringVar(&cfg.OutputFormat, "output", "yaml", "output format: json|yaml") + fs.StringVar(&cfg.Kind, "kind", "", "CRD Kind") + fs.StringVar(&cfg.ResourceScope, "scope", string(extensionsobj.NamespaceScoped), "CRD scope: 'Namespaced' | 'Cluster'. Default: Namespaced") + fs.StringVar(&cfg.Version, "version", "v1", "CRD version, default: 'v1'") + fs.StringVar(&cfg.Plural, "plural", "", "CRD plural name") + fs.StringVar(&cfg.Singular, "singular", "", "CRD singular name") + fs.StringSliceVar(&cfg.ShortNames, "short-names", nil, "CRD short names") + fs.StringVar(&cfg.ListKind, "list-kind", "", "CRD list kind") + return fs +} diff --git a/vendor/github.com/appscode/kutil/apiextensions/v1beta1/convert_types.go b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/convert_types.go new file mode 100644 index 000000000..4f5106c79 --- /dev/null +++ b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/convert_types.go @@ -0,0 +1,127 @@ +package v1beta1 + +import ( + "fmt" + + "github.com/go-openapi/spec" + extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/kube-openapi/pkg/common" +) + +// SchemaPropsToJSONPropsArray converts []Schema to []JSONSchemaProps +func SchemaPropsToJSONPropsArray(schemas []spec.Schema, openapiSpec map[string]common.OpenAPIDefinition, nested bool) []extensionsobj.JSONSchemaProps { + var s []extensionsobj.JSONSchemaProps + for _, schema := range schemas { + s = append(s, *SchemaPropsToJSONProps(&schema, openapiSpec, nested)) + } + return s +} + +// StringOrArrayToString converts StringOrArray to string +func StringOrArrayToString(strOrArray spec.StringOrArray) string { + if len(strOrArray) > 0 { + return strOrArray[0] + } + return "" +} + +// EnumJSON converts []interface{} to []JSON +func EnumJSON(enum []interface{}) []extensionsobj.JSON { + var s []extensionsobj.JSON + for _, elt := range enum { + s = append(s, extensionsobj.JSON{ + Raw: []byte(fmt.Sprintf("%v", elt)), + }) + } + return s +} + +// SchemaOrArrayToJSONItems converts *SchemaOrArray to *JSONSchemaPropsOrArray +func SchemaOrArrayToJSONItems(schemaOrArray *spec.SchemaOrArray, openapiSpec map[string]common.OpenAPIDefinition, nested bool) *extensionsobj.JSONSchemaPropsOrArray { + var array *extensionsobj.JSONSchemaPropsOrArray + if schemaOrArray == nil { + return array + } + return &extensionsobj.JSONSchemaPropsOrArray{ + Schema: SchemaPropsToJSONProps(schemaOrArray.Schema, openapiSpec, nested), + JSONSchemas: SchemaPropsToJSONPropsArray(schemaOrArray.Schemas, openapiSpec, nested), + } +} + +// SchemaOrBoolToJSONProps converts *SchemaOrBool to *JSONSchemaPropsOrBool +func SchemaOrBoolToJSONProps(schemaOrBool *spec.SchemaOrBool, openapiSpec map[string]common.OpenAPIDefinition, nested bool) *extensionsobj.JSONSchemaPropsOrBool { + var s *extensionsobj.JSONSchemaPropsOrBool + if schemaOrBool == nil { + return s + } + return &extensionsobj.JSONSchemaPropsOrBool{ + Schema: SchemaPropsToJSONProps(schemaOrBool.Schema, openapiSpec, nested), + Allows: schemaOrBool.Allows, + } +} + +// SchemPropsMapToJSONMap converts map[string]Schema to map[string]JSONSchemaProps +func SchemPropsMapToJSONMap(schemaMap map[string]spec.Schema, openapiSpec map[string]common.OpenAPIDefinition, nested bool) map[string]extensionsobj.JSONSchemaProps { + var m map[string]extensionsobj.JSONSchemaProps + m = make(map[string]extensionsobj.JSONSchemaProps) + for key, schema := range schemaMap { + m[key] = *SchemaPropsToJSONProps(&schema, openapiSpec, nested) + } + return m +} + +// SchemaPropsToJSONProps converts a SchemaProps to a JSONProps +func SchemaPropsToJSONProps(schema *spec.Schema, openapiSpec map[string]common.OpenAPIDefinition, nested bool) *extensionsobj.JSONSchemaProps { + var props *extensionsobj.JSONSchemaProps + if schema == nil { + return props + } + schemaProps := &schema.SchemaProps + + var ref *string + if schemaProps.Ref.String() != "" { + if nested { + propref := openapiSpec[schemaProps.Ref.String()].Schema + // If nested just return a pointer to the reference + return SchemaPropsToJSONProps(&propref, openapiSpec, nested) + } + ref = new(string) + *ref = schemaProps.Ref.String() + } + + props = &extensionsobj.JSONSchemaProps{ + Ref: ref, + ID: schemaProps.ID, + Schema: extensionsobj.JSONSchemaURL(string(schema.Schema)), + Description: schemaProps.Description, + Type: StringOrArrayToString(schemaProps.Type), + Format: schemaProps.Format, + Title: schemaProps.Title, + Maximum: schemaProps.Maximum, + ExclusiveMaximum: schemaProps.ExclusiveMaximum, + Minimum: schemaProps.Minimum, + ExclusiveMinimum: schemaProps.ExclusiveMinimum, + MaxLength: schemaProps.MaxLength, + MinLength: schemaProps.MinLength, + Pattern: schemaProps.Pattern, + MaxItems: schemaProps.MaxItems, + MinItems: schemaProps.MinItems, + UniqueItems: schemaProps.UniqueItems, + MultipleOf: schemaProps.MultipleOf, + Enum: EnumJSON(schemaProps.Enum), + MaxProperties: schemaProps.MaxProperties, + MinProperties: schemaProps.MinProperties, + Required: schemaProps.Required, + Items: SchemaOrArrayToJSONItems(schemaProps.Items, openapiSpec, nested), + AllOf: SchemaPropsToJSONPropsArray(schemaProps.AllOf, openapiSpec, nested), + OneOf: SchemaPropsToJSONPropsArray(schemaProps.OneOf, openapiSpec, nested), + AnyOf: SchemaPropsToJSONPropsArray(schemaProps.AnyOf, openapiSpec, nested), + Not: SchemaPropsToJSONProps(schemaProps.Not, openapiSpec, nested), + Properties: SchemPropsMapToJSONMap(schemaProps.Properties, openapiSpec, nested), + // @TODO(01-25-2018) Field not accepted by the current CRD Validation Spec + // AdditionalProperties: SchemaOrBoolToJSONProps(schemaProps.AdditionalProperties, openapiSpec, nested), + PatternProperties: SchemPropsMapToJSONMap(schemaProps.PatternProperties, openapiSpec, nested), + AdditionalItems: SchemaOrBoolToJSONProps(schemaProps.AdditionalItems, openapiSpec, nested), + } + return props +} diff --git a/vendor/github.com/appscode/kutil/apiextensions/v1beta1/crdvalidation.go b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/crdvalidation.go new file mode 100644 index 000000000..174538b42 --- /dev/null +++ b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/crdvalidation.go @@ -0,0 +1,46 @@ +package v1beta1 + +import ( + "github.com/go-openapi/spec" + extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kube-openapi/pkg/common" +) + +// CustomResourceDefinitionTypeMeta set the default kind/apiversion of CRD +var CustomResourceDefinitionTypeMeta = metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1beta1", +} + +// OpenAPIRefCallBack returns a jsonref using the input string without modification +func OpenAPIRefCallBack(name string) spec.Ref { + return spec.MustCreateRef(name) +} + +// GetAPIDefinition is a function returning a map with all Definition +type GetAPIDefinitions func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition + +// GetCustomResourceValidations returns a CRD validation spec map. It took the openapi generated definition from kube-openapi as argument +func GetCustomResourceValidations(fn GetAPIDefinitions) map[string]*extensionsobj.CustomResourceValidation { + openapiSpec := fn(OpenAPIRefCallBack) + var definitions map[string]*extensionsobj.CustomResourceValidation + definitions = make(map[string]*extensionsobj.CustomResourceValidation) + for key, definition := range openapiSpec { + schema := definition.Schema + definitions[key] = &extensionsobj.CustomResourceValidation{ + OpenAPIV3Schema: SchemaPropsToJSONProps(&schema, openapiSpec, true), + } + } + return definitions +} + +// GetCustomResourceValidation returns the validation definition for a CRD name +func GetCustomResourceValidation(name string, fn func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition) *extensionsobj.CustomResourceValidation { + openapiSpec := fn(OpenAPIRefCallBack) + schema := openapiSpec[name].Schema + return &extensionsobj.CustomResourceValidation{ + OpenAPIV3Schema: SchemaPropsToJSONProps(&schema, openapiSpec, true), + } + +} diff --git a/vendor/github.com/appscode/kutil/apiextensions/v1beta1/kubernetes.go b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/kubernetes.go index a5f6acfb1..296293cea 100644 --- a/vendor/github.com/appscode/kutil/apiextensions/v1beta1/kubernetes.go +++ b/vendor/github.com/appscode/kutil/apiextensions/v1beta1/kubernetes.go @@ -24,6 +24,11 @@ func RegisterCRDs(client crd_cs.ApiextensionsV1beta1Interface, crds []*crd_api.C } } else if err != nil { return err + } else { + _, err = client.CustomResourceDefinitions().Update(crd) + if err != nil { + return err + } } } return WaitForCRDReady(client.RESTClient(), crds) diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 6b1f2891a..2874a048c 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -46,7 +46,8 @@ func (f Frame) line() int { // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s path of source file relative to the compile time GOPATH +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { @@ -79,6 +80,14 @@ func (f Frame) Format(s fmt.State, verb rune) { // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -136,43 +145,3 @@ func funcname(name string) string { i = strings.Index(name, ".") return name[i+1:] } - -func trimGOPATH(name, file string) string { - // Here we want to get the source file path relative to the compile time - // GOPATH. As of Go 1.6.x there is no direct way to know the compiled - // GOPATH at runtime, but we can infer the number of path segments in the - // GOPATH. We note that fn.Name() returns the function name qualified by - // the import path, which does not include the GOPATH. Thus we can trim - // segments from the beginning of the file path until the number of path - // separators remaining is one more than the number of path separators in - // the function name. For example, given: - // - // GOPATH /home/user - // file /home/user/src/pkg/sub/file.go - // fn.Name() pkg/sub.Type.Method - // - // We want to produce: - // - // pkg/sub/file.go - // - // From this we can easily see that fn.Name() has one less path separator - // than our desired output. We count separators from the end of the file - // path until it finds two more than in the function name and then move - // one character forward to preserve the initial path segment without a - // leading separator. - const sep = "/" - goal := strings.Count(name, sep) + 2 - i := len(file) - for n := 0; n < goal; n++ { - i = strings.LastIndex(file[:i], sep) - if i == -1 { - // not enough separators found, set i so that the slice expression - // below leaves file unmodified - i = -len(sep) - break - } - } - // get back to 0 or trim the leading separator - file = file[i+len(sep):] - return file -} diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index 15b811279..34d1bf367 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -27,6 +27,9 @@ import ( flag "github.com/spf13/pflag" ) +// FParseErrWhitelist configures Flag parse errors to be ignored +type FParseErrWhitelist flag.ParseErrorsWhitelist + // Command is just that, a command for your application. // E.g. 'go run ...' - 'run' is the command. Cobra requires // you to define the usage and description as part of your command @@ -137,6 +140,9 @@ type Command struct { // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool + //FParseErrWhitelist flag parse errors to be ignored + FParseErrWhitelist FParseErrWhitelist + // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -1463,6 +1469,10 @@ func (c *Command) ParseFlags(args []string) error { } beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() + + //do it here after merging all flags and just before parse + c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) + err := c.Flags().Parse(args) // Print warnings if they occurred (e.g. deprecated flag messages). if c.flagErrorBuf.Len()-beforeErrorBufLen > 0 && err == nil { diff --git a/vendor/github.com/spf13/pflag/bytes.go b/vendor/github.com/spf13/pflag/bytes.go new file mode 100644 index 000000000..12c58db9f --- /dev/null +++ b/vendor/github.com/spf13/pflag/bytes.go @@ -0,0 +1,105 @@ +package pflag + +import ( + "encoding/hex" + "fmt" + "strings" +) + +// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded +type bytesHexValue []byte + +func (bytesHex bytesHexValue) String() string { + return fmt.Sprintf("%X", []byte(bytesHex)) +} + +func (bytesHex *bytesHexValue) Set(value string) error { + bin, err := hex.DecodeString(strings.TrimSpace(value)) + + if err != nil { + return err + } + + *bytesHex = bin + + return nil +} + +func (*bytesHexValue) Type() string { + return "bytesHex" +} + +func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue { + *p = val + return (*bytesHexValue)(p) +} + +func bytesHexConv(sval string) (interface{}, error) { + + bin, err := hex.DecodeString(sval) + + if err == nil { + return bin, nil + } + + return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err) +} + +// GetBytesHex return the []byte value of a flag with the given name +func (f *FlagSet) GetBytesHex(name string) ([]byte, error) { + val, err := f.getFlagType(name, "bytesHex", bytesHexConv) + + if err != nil { + return []byte{}, err + } + + return val.([]byte), nil +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func BytesHexVar(p *[]byte, name string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, "", value, usage) + return p +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, shorthand, value, usage) + return p +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func BytesHex(name string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, "", value, usage) +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/count.go b/vendor/github.com/spf13/pflag/count.go index 250a43814..aa126e44d 100644 --- a/vendor/github.com/spf13/pflag/count.go +++ b/vendor/github.com/spf13/pflag/count.go @@ -11,13 +11,13 @@ func newCountValue(val int, p *int) *countValue { } func (i *countValue) Set(s string) error { - v, err := strconv.ParseInt(s, 0, 64) - // -1 means that no specific value was passed, so increment - if v == -1 { + // "+1" means that no specific value was passed, so increment + if s == "+1" { *i = countValue(*i + 1) - } else { - *i = countValue(v) + return nil } + v, err := strconv.ParseInt(s, 0, 0) + *i = countValue(v) return err } @@ -54,7 +54,7 @@ func (f *FlagSet) CountVar(p *int, name string, usage string) { // CountVarP is like CountVar only take a shorthand for the flag name. func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) { flag := f.VarPF(newCountValue(0, p), name, shorthand, usage) - flag.NoOptDefVal = "-1" + flag.NoOptDefVal = "+1" } // CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set diff --git a/vendor/github.com/spf13/pflag/duration_slice.go b/vendor/github.com/spf13/pflag/duration_slice.go new file mode 100644 index 000000000..52c6b6dc1 --- /dev/null +++ b/vendor/github.com/spf13/pflag/duration_slice.go @@ -0,0 +1,128 @@ +package pflag + +import ( + "fmt" + "strings" + "time" +) + +// -- durationSlice Value +type durationSliceValue struct { + value *[]time.Duration + changed bool +} + +func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSliceValue { + dsv := new(durationSliceValue) + dsv.value = p + *dsv.value = val + return dsv +} + +func (s *durationSliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]time.Duration, len(ss)) + for i, d := range ss { + var err error + out[i], err = time.ParseDuration(d) + if err != nil { + return err + } + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *durationSliceValue) Type() string { + return "durationSlice" +} + +func (s *durationSliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%s", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func durationSliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []time.Duration{}, nil + } + ss := strings.Split(val, ",") + out := make([]time.Duration, len(ss)) + for i, d := range ss { + var err error + out[i], err = time.ParseDuration(d) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetDurationSlice returns the []time.Duration value of a flag with the given name +func (f *FlagSet) GetDurationSlice(name string) ([]time.Duration, error) { + val, err := f.getFlagType(name, "durationSlice", durationSliceConv) + if err != nil { + return []time.Duration{}, err + } + return val.([]time.Duration), nil +} + +// DurationSliceVar defines a durationSlice flag with specified name, default value, and usage string. +// The argument p points to a []time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) { + f.VarP(newDurationSliceValue(value, p), name, "", usage) +} + +// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) { + f.VarP(newDurationSliceValue(value, p), name, shorthand, usage) +} + +// DurationSliceVar defines a duration[] flag with specified name, default value, and usage string. +// The argument p points to a duration[] variable in which to store the value of the flag. +func DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) { + CommandLine.VarP(newDurationSliceValue(value, p), name, "", usage) +} + +// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash. +func DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) { + CommandLine.VarP(newDurationSliceValue(value, p), name, shorthand, usage) +} + +// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a []time.Duration variable that stores the value of the flag. +func (f *FlagSet) DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration { + p := []time.Duration{} + f.DurationSliceVarP(&p, name, "", value, usage) + return &p +} + +// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration { + p := []time.Duration{} + f.DurationSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a []time.Duration variable that stores the value of the flag. +func DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration { + return CommandLine.DurationSliceP(name, "", value, usage) +} + +// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash. +func DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration { + return CommandLine.DurationSliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go index 6f1fc3007..f0acbb84e 100644 --- a/vendor/github.com/spf13/pflag/flag.go +++ b/vendor/github.com/spf13/pflag/flag.go @@ -123,6 +123,12 @@ const ( PanicOnError ) +// ParseErrorsWhitelist defines the parsing errors that can be ignored +type ParseErrorsWhitelist struct { + // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags + UnknownFlags bool +} + // NormalizedName is a flag name that has been normalized according to rules // for the FlagSet (e.g. making '-' and '_' equivalent). type NormalizedName string @@ -138,6 +144,9 @@ type FlagSet struct { // help/usage messages. SortFlags bool + // ParseErrorsWhitelist is used to configure a whitelist of errors + ParseErrorsWhitelist ParseErrorsWhitelist + name string parsed bool actual map[NormalizedName]*Flag @@ -202,12 +211,18 @@ func sortFlags(flags map[NormalizedName]*Flag) []*Flag { func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) { f.normalizeNameFunc = n f.sortedFormal = f.sortedFormal[:0] - for k, v := range f.orderedFormal { - delete(f.formal, NormalizedName(v.Name)) - nname := f.normalizeFlagName(v.Name) - v.Name = string(nname) - f.formal[nname] = v - f.orderedFormal[k] = v + for fname, flag := range f.formal { + nname := f.normalizeFlagName(flag.Name) + if fname == nname { + continue + } + flag.Name = string(nname) + delete(f.formal, fname) + f.formal[nname] = flag + if _, set := f.actual[fname]; set { + delete(f.actual, fname) + f.actual[nname] = flag + } } } @@ -440,13 +455,15 @@ func (f *FlagSet) Set(name, value string) error { return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err) } - if f.actual == nil { - f.actual = make(map[NormalizedName]*Flag) - } - f.actual[normalName] = flag - f.orderedActual = append(f.orderedActual, flag) + if !flag.Changed { + if f.actual == nil { + f.actual = make(map[NormalizedName]*Flag) + } + f.actual[normalName] = flag + f.orderedActual = append(f.orderedActual, flag) - flag.Changed = true + flag.Changed = true + } if flag.Deprecated != "" { fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) @@ -556,6 +573,14 @@ func UnquoteUsage(flag *Flag) (name string, usage string) { name = "int" case "uint64": name = "uint" + case "stringSlice": + name = "strings" + case "intSlice": + name = "ints" + case "uintSlice": + name = "uints" + case "boolSlice": + name = "bools" } return @@ -570,11 +595,14 @@ func wrapN(i, slop int, s string) (string, string) { return s, "" } - w := strings.LastIndexAny(s[:i], " \t") + w := strings.LastIndexAny(s[:i], " \t\n") if w <= 0 { return s, "" } - + nlPos := strings.LastIndex(s[:i], "\n") + if nlPos > 0 && nlPos < w { + return s[:nlPos], s[nlPos+1:] + } return s[:w], s[w+1:] } @@ -583,7 +611,7 @@ func wrapN(i, slop int, s string) (string, string) { // caller). Pass `w` == 0 to do no wrapping func wrap(i, w int, s string) string { if w == 0 { - return s + return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1) } // space between indent i and end of line width w into which @@ -601,7 +629,7 @@ func wrap(i, w int, s string) string { } // If still not enough space then don't even try to wrap. if wrap < 24 { - return s + return strings.Replace(s, "\n", r, -1) } // Try to avoid short orphan words on the final line, by @@ -613,14 +641,14 @@ func wrap(i, w int, s string) string { // Handle first line, which is indented by the caller (or the // special case above) l, s = wrapN(wrap, slop, s) - r = r + l + r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1) // Now wrap the rest for s != "" { var t string t, s = wrapN(wrap, slop, s) - r = r + "\n" + strings.Repeat(" ", i) + t + r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1) } return r @@ -660,6 +688,10 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string { if flag.NoOptDefVal != "true" { line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) } + case "count": + if flag.NoOptDefVal != "+1" { + line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) + } default: line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) } @@ -857,8 +889,10 @@ func VarP(value Value, name, shorthand, usage string) { // returns the error. func (f *FlagSet) failf(format string, a ...interface{}) error { err := fmt.Errorf(format, a...) - fmt.Fprintln(f.out(), err) - f.usage() + if f.errorHandling != ContinueOnError { + fmt.Fprintln(f.out(), err) + f.usage() + } return err } @@ -874,6 +908,25 @@ func (f *FlagSet) usage() { } } +//--unknown (args will be empty) +//--unknown --next-flag ... (args will be --next-flag ...) +//--unknown arg ... (args will be arg ...) +func stripUnknownFlagValue(args []string) []string { + if len(args) == 0 { + //--unknown + return args + } + + first := args[0] + if first[0] == '-' { + //--unknown --next-flag ... + return args + } + + //--unknown arg ... (args will be arg ...) + return args[1:] +} + func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) { a = args name := s[2:] @@ -885,13 +938,24 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin split := strings.SplitN(name, "=", 2) name = split[0] flag, exists := f.formal[f.normalizeFlagName(name)] + if !exists { - if name == "help" { // special case for nice help message. + switch { + case name == "help": f.usage() return a, ErrHelp + case f.ParseErrorsWhitelist.UnknownFlags: + // --unknown=unknownval arg ... + // we do not want to lose arg in this case + if len(split) >= 2 { + return a, nil + } + + return stripUnknownFlagValue(a), nil + default: + err = f.failf("unknown flag: --%s", name) + return } - err = f.failf("unknown flag: --%s", name) - return } var value string @@ -912,6 +976,9 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin } err = fn(flag, value) + if err != nil { + f.failf(err.Error()) + } return } @@ -926,13 +993,25 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse flag, exists := f.shorthands[c] if !exists { - if c == 'h' { // special case for nice help message. + switch { + case c == 'h': f.usage() err = ErrHelp return + case f.ParseErrorsWhitelist.UnknownFlags: + // '-f=arg arg ...' + // we do not want to lose arg in this case + if len(shorthands) > 2 && shorthands[1] == '=' { + outShorts = "" + return + } + + outArgs = stripUnknownFlagValue(outArgs) + return + default: + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return } - err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) - return } var value string @@ -962,6 +1041,9 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse } err = fn(flag, value) + if err != nil { + f.failf(err.Error()) + } return } @@ -1034,6 +1116,7 @@ func (f *FlagSet) Parse(arguments []string) error { case ContinueOnError: return err case ExitOnError: + fmt.Println(err) os.Exit(2) case PanicOnError: panic(err) diff --git a/vendor/github.com/spf13/pflag/int16.go b/vendor/github.com/spf13/pflag/int16.go new file mode 100644 index 000000000..f1a01d05e --- /dev/null +++ b/vendor/github.com/spf13/pflag/int16.go @@ -0,0 +1,88 @@ +package pflag + +import "strconv" + +// -- int16 Value +type int16Value int16 + +func newInt16Value(val int16, p *int16) *int16Value { + *p = val + return (*int16Value)(p) +} + +func (i *int16Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 16) + *i = int16Value(v) + return err +} + +func (i *int16Value) Type() string { + return "int16" +} + +func (i *int16Value) String() string { return strconv.FormatInt(int64(*i), 10) } + +func int16Conv(sval string) (interface{}, error) { + v, err := strconv.ParseInt(sval, 0, 16) + if err != nil { + return 0, err + } + return int16(v), nil +} + +// GetInt16 returns the int16 value of a flag with the given name +func (f *FlagSet) GetInt16(name string) (int16, error) { + val, err := f.getFlagType(name, "int16", int16Conv) + if err != nil { + return 0, err + } + return val.(int16), nil +} + +// Int16Var defines an int16 flag with specified name, default value, and usage string. +// The argument p points to an int16 variable in which to store the value of the flag. +func (f *FlagSet) Int16Var(p *int16, name string, value int16, usage string) { + f.VarP(newInt16Value(value, p), name, "", usage) +} + +// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int16VarP(p *int16, name, shorthand string, value int16, usage string) { + f.VarP(newInt16Value(value, p), name, shorthand, usage) +} + +// Int16Var defines an int16 flag with specified name, default value, and usage string. +// The argument p points to an int16 variable in which to store the value of the flag. +func Int16Var(p *int16, name string, value int16, usage string) { + CommandLine.VarP(newInt16Value(value, p), name, "", usage) +} + +// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash. +func Int16VarP(p *int16, name, shorthand string, value int16, usage string) { + CommandLine.VarP(newInt16Value(value, p), name, shorthand, usage) +} + +// Int16 defines an int16 flag with specified name, default value, and usage string. +// The return value is the address of an int16 variable that stores the value of the flag. +func (f *FlagSet) Int16(name string, value int16, usage string) *int16 { + p := new(int16) + f.Int16VarP(p, name, "", value, usage) + return p +} + +// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int16P(name, shorthand string, value int16, usage string) *int16 { + p := new(int16) + f.Int16VarP(p, name, shorthand, value, usage) + return p +} + +// Int16 defines an int16 flag with specified name, default value, and usage string. +// The return value is the address of an int16 variable that stores the value of the flag. +func Int16(name string, value int16, usage string) *int16 { + return CommandLine.Int16P(name, "", value, usage) +} + +// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash. +func Int16P(name, shorthand string, value int16, usage string) *int16 { + return CommandLine.Int16P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/string_array.go b/vendor/github.com/spf13/pflag/string_array.go index 276b7ed49..fa7bc6018 100644 --- a/vendor/github.com/spf13/pflag/string_array.go +++ b/vendor/github.com/spf13/pflag/string_array.go @@ -52,7 +52,7 @@ func (f *FlagSet) GetStringArray(name string) ([]string, error) { // StringArrayVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the values of the multiple flags. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) { f.VarP(newStringArrayValue(value, p), name, "", usage) } @@ -64,7 +64,7 @@ func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []s // StringArrayVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func StringArrayVar(p *[]string, name string, value []string, usage string) { CommandLine.VarP(newStringArrayValue(value, p), name, "", usage) } @@ -76,7 +76,7 @@ func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage // StringArray defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string { p := []string{} f.StringArrayVarP(&p, name, "", value, usage) @@ -92,7 +92,7 @@ func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage str // StringArray defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func StringArray(name string, value []string, usage string) *[]string { return CommandLine.StringArrayP(name, "", value, usage) } diff --git a/vendor/github.com/spf13/pflag/string_slice.go b/vendor/github.com/spf13/pflag/string_slice.go index 05eee7543..0cd3ccc08 100644 --- a/vendor/github.com/spf13/pflag/string_slice.go +++ b/vendor/github.com/spf13/pflag/string_slice.go @@ -82,6 +82,11 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) { // StringSliceVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { f.VarP(newStringSliceValue(value, p), name, "", usage) } @@ -93,6 +98,11 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s // StringSliceVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func StringSliceVar(p *[]string, name string, value []string, usage string) { CommandLine.VarP(newStringSliceValue(value, p), name, "", usage) } @@ -104,6 +114,11 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage // StringSlice defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { p := []string{} f.StringSliceVarP(&p, name, "", value, usage) @@ -119,6 +134,11 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str // StringSlice defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func StringSlice(name string, value []string, usage string) *[]string { return CommandLine.StringSliceP(name, "", value, usage) } diff --git a/vendor/gopkg.in/ini.v1/LICENSE b/vendor/gopkg.in/ini.v1/LICENSE index 37ec93a14..d361bbcdf 100644 --- a/vendor/gopkg.in/ini.v1/LICENSE +++ b/vendor/gopkg.in/ini.v1/LICENSE @@ -176,7 +176,7 @@ 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] + Copyright 2014 Unknwon Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/gopkg.in/ini.v1/file.go b/vendor/gopkg.in/ini.v1/file.go new file mode 100644 index 000000000..ae6264acf --- /dev/null +++ b/vendor/gopkg.in/ini.v1/file.go @@ -0,0 +1,403 @@ +// Copyright 2017 Unknwon +// +// 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 ini + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "sync" +) + +// File represents a combination of a or more INI file(s) in memory. +type File struct { + options LoadOptions + dataSources []dataSource + + // Should make things safe, but sometimes doesn't matter. + BlockMode bool + lock sync.RWMutex + + // To keep data in order. + sectionList []string + // Actual data is stored here. + sections map[string]*Section + + NameMapper + ValueMapper +} + +// newFile initializes File object with given data sources. +func newFile(dataSources []dataSource, opts LoadOptions) *File { + return &File{ + BlockMode: true, + dataSources: dataSources, + sections: make(map[string]*Section), + sectionList: make([]string, 0, 10), + options: opts, + } +} + +// Empty returns an empty file object. +func Empty() *File { + // Ignore error here, we sure our data is good. + f, _ := Load([]byte("")) + return f +} + +// NewSection creates a new section. +func (f *File) NewSection(name string) (*Section, error) { + if len(name) == 0 { + return nil, errors.New("error creating new section: empty section name") + } else if f.options.Insensitive && name != DEFAULT_SECTION { + name = strings.ToLower(name) + } + + if f.BlockMode { + f.lock.Lock() + defer f.lock.Unlock() + } + + if inSlice(name, f.sectionList) { + return f.sections[name], nil + } + + f.sectionList = append(f.sectionList, name) + f.sections[name] = newSection(f, name) + return f.sections[name], nil +} + +// NewRawSection creates a new section with an unparseable body. +func (f *File) NewRawSection(name, body string) (*Section, error) { + section, err := f.NewSection(name) + if err != nil { + return nil, err + } + + section.isRawSection = true + section.rawBody = body + return section, nil +} + +// NewSections creates a list of sections. +func (f *File) NewSections(names ...string) (err error) { + for _, name := range names { + if _, err = f.NewSection(name); err != nil { + return err + } + } + return nil +} + +// GetSection returns section by given name. +func (f *File) GetSection(name string) (*Section, error) { + if len(name) == 0 { + name = DEFAULT_SECTION + } + if f.options.Insensitive { + name = strings.ToLower(name) + } + + if f.BlockMode { + f.lock.RLock() + defer f.lock.RUnlock() + } + + sec := f.sections[name] + if sec == nil { + return nil, fmt.Errorf("section '%s' does not exist", name) + } + return sec, nil +} + +// Section assumes named section exists and returns a zero-value when not. +func (f *File) Section(name string) *Section { + sec, err := f.GetSection(name) + if err != nil { + // Note: It's OK here because the only possible error is empty section name, + // but if it's empty, this piece of code won't be executed. + sec, _ = f.NewSection(name) + return sec + } + return sec +} + +// Section returns list of Section. +func (f *File) Sections() []*Section { + if f.BlockMode { + f.lock.RLock() + defer f.lock.RUnlock() + } + + sections := make([]*Section, len(f.sectionList)) + for i, name := range f.sectionList { + sections[i] = f.sections[name] + } + return sections +} + +// ChildSections returns a list of child sections of given section name. +func (f *File) ChildSections(name string) []*Section { + return f.Section(name).ChildSections() +} + +// SectionStrings returns list of section names. +func (f *File) SectionStrings() []string { + list := make([]string, len(f.sectionList)) + copy(list, f.sectionList) + return list +} + +// DeleteSection deletes a section. +func (f *File) DeleteSection(name string) { + if f.BlockMode { + f.lock.Lock() + defer f.lock.Unlock() + } + + if len(name) == 0 { + name = DEFAULT_SECTION + } + + for i, s := range f.sectionList { + if s == name { + f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) + delete(f.sections, name) + return + } + } +} + +func (f *File) reload(s dataSource) error { + r, err := s.ReadCloser() + if err != nil { + return err + } + defer r.Close() + + return f.parse(r) +} + +// Reload reloads and parses all data sources. +func (f *File) Reload() (err error) { + for _, s := range f.dataSources { + if err = f.reload(s); err != nil { + // In loose mode, we create an empty default section for nonexistent files. + if os.IsNotExist(err) && f.options.Loose { + f.parse(bytes.NewBuffer(nil)) + continue + } + return err + } + } + return nil +} + +// Append appends one or more data sources and reloads automatically. +func (f *File) Append(source interface{}, others ...interface{}) error { + ds, err := parseDataSource(source) + if err != nil { + return err + } + f.dataSources = append(f.dataSources, ds) + for _, s := range others { + ds, err = parseDataSource(s) + if err != nil { + return err + } + f.dataSources = append(f.dataSources, ds) + } + return f.Reload() +} + +func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { + equalSign := "=" + if PrettyFormat { + equalSign = " = " + } + + // Use buffer to make sure target is safe until finish encoding. + buf := bytes.NewBuffer(nil) + for i, sname := range f.sectionList { + sec := f.Section(sname) + if len(sec.Comment) > 0 { + if sec.Comment[0] != '#' && sec.Comment[0] != ';' { + sec.Comment = "; " + sec.Comment + } else { + sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:]) + } + if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil { + return nil, err + } + } + + if i > 0 || DefaultHeader { + if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { + return nil, err + } + } else { + // Write nothing if default section is empty + if len(sec.keyList) == 0 { + continue + } + } + + if sec.isRawSection { + if _, err := buf.WriteString(sec.rawBody); err != nil { + return nil, err + } + + if PrettySection { + // Put a line between sections + if _, err := buf.WriteString(LineBreak); err != nil { + return nil, err + } + } + continue + } + + // Count and generate alignment length and buffer spaces using the + // longest key. Keys may be modifed if they contain certain characters so + // we need to take that into account in our calculation. + alignLength := 0 + if PrettyFormat { + for _, kname := range sec.keyList { + keyLength := len(kname) + // First case will surround key by ` and second by """ + if strings.ContainsAny(kname, "\"=:") { + keyLength += 2 + } else if strings.Contains(kname, "`") { + keyLength += 6 + } + + if keyLength > alignLength { + alignLength = keyLength + } + } + } + alignSpaces := bytes.Repeat([]byte(" "), alignLength) + + KEY_LIST: + for _, kname := range sec.keyList { + key := sec.Key(kname) + if len(key.Comment) > 0 { + if len(indent) > 0 && sname != DEFAULT_SECTION { + buf.WriteString(indent) + } + if key.Comment[0] != '#' && key.Comment[0] != ';' { + key.Comment = "; " + key.Comment + } else { + key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:]) + } + if _, err := buf.WriteString(key.Comment + LineBreak); err != nil { + return nil, err + } + } + + if len(indent) > 0 && sname != DEFAULT_SECTION { + buf.WriteString(indent) + } + + switch { + case key.isAutoIncrement: + kname = "-" + case strings.ContainsAny(kname, "\"=:"): + kname = "`" + kname + "`" + case strings.Contains(kname, "`"): + kname = `"""` + kname + `"""` + } + + for _, val := range key.ValueWithShadows() { + if _, err := buf.WriteString(kname); err != nil { + return nil, err + } + + if key.isBooleanType { + if kname != sec.keyList[len(sec.keyList)-1] { + buf.WriteString(LineBreak) + } + continue KEY_LIST + } + + // Write out alignment spaces before "=" sign + if PrettyFormat { + buf.Write(alignSpaces[:alignLength-len(kname)]) + } + + // In case key value contains "\n", "`", "\"", "#" or ";" + if strings.ContainsAny(val, "\n`") { + val = `"""` + val + `"""` + } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { + val = "`" + val + "`" + } + if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { + return nil, err + } + } + + for _, val := range key.nestedValues { + if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { + return nil, err + } + } + } + + if PrettySection { + // Put a line between sections + if _, err := buf.WriteString(LineBreak); err != nil { + return nil, err + } + } + } + + return buf, nil +} + +// WriteToIndent writes content into io.Writer with given indention. +// If PrettyFormat has been set to be true, +// it will align "=" sign with spaces under each section. +func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { + buf, err := f.writeToBuffer(indent) + if err != nil { + return 0, err + } + return buf.WriteTo(w) +} + +// WriteTo writes file content into io.Writer. +func (f *File) WriteTo(w io.Writer) (int64, error) { + return f.WriteToIndent(w, "") +} + +// SaveToIndent writes content to file system with given value indention. +func (f *File) SaveToIndent(filename, indent string) error { + // Note: Because we are truncating with os.Create, + // so it's safer to save to a temporary file location and rename afte done. + buf, err := f.writeToBuffer(indent) + if err != nil { + return err + } + + return ioutil.WriteFile(filename, buf.Bytes(), 0666) +} + +// SaveTo writes content to file system. +func (f *File) SaveTo(filename string) error { + return f.SaveToIndent(filename, "") +} diff --git a/vendor/gopkg.in/ini.v1/ini.go b/vendor/gopkg.in/ini.v1/ini.go index 7f3c4d1ed..9f6ea3b41 100644 --- a/vendor/gopkg.in/ini.v1/ini.go +++ b/vendor/gopkg.in/ini.v1/ini.go @@ -17,15 +17,12 @@ package ini import ( "bytes" - "errors" "fmt" "io" "io/ioutil" "os" "regexp" "runtime" - "strings" - "sync" ) const ( @@ -35,7 +32,7 @@ const ( // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 99 - _VERSION = "1.28.2" + _VERSION = "1.33.0" ) // Version returns current package version literal. @@ -92,18 +89,6 @@ func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { return os.Open(s.name) } -type bytesReadCloser struct { - reader io.Reader -} - -func (rc *bytesReadCloser) Read(p []byte) (n int, err error) { - return rc.reader.Read(p) -} - -func (rc *bytesReadCloser) Close() error { - return nil -} - // sourceData represents an object that contains content in memory. type sourceData struct { data []byte @@ -122,38 +107,6 @@ func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) { return s.reader, nil } -// File represents a combination of a or more INI file(s) in memory. -type File struct { - // Should make things safe, but sometimes doesn't matter. - BlockMode bool - // Make sure data is safe in multiple goroutines. - lock sync.RWMutex - - // Allow combination of multiple data sources. - dataSources []dataSource - // Actual data is stored here. - sections map[string]*Section - - // To keep data in order. - sectionList []string - - options LoadOptions - - NameMapper - ValueMapper -} - -// newFile initializes File object with given data sources. -func newFile(dataSources []dataSource, opts LoadOptions) *File { - return &File{ - BlockMode: true, - dataSources: dataSources, - sections: make(map[string]*Section), - sectionList: make([]string, 0, 10), - options: opts, - } -} - func parseDataSource(source interface{}) (dataSource, error) { switch s := source.(type) { case string: @@ -181,6 +134,16 @@ type LoadOptions struct { AllowBooleanKeys bool // AllowShadows indicates whether to keep track of keys with same name under same section. AllowShadows bool + // AllowNestedValues indicates whether to allow AWS-like nested values. + // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values + AllowNestedValues bool + // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format + // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value" + UnescapeValueDoubleQuotes bool + // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format + // when value is NOT surrounded by any quotes. + // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all. + UnescapeValueCommentSymbols bool // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise // conform to key/value pairs. Specify the names of those blocks here. UnparseableSections []string @@ -229,328 +192,3 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) { func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{AllowShadows: true}, source, others...) } - -// Empty returns an empty file object. -func Empty() *File { - // Ignore error here, we sure our data is good. - f, _ := Load([]byte("")) - return f -} - -// NewSection creates a new section. -func (f *File) NewSection(name string) (*Section, error) { - if len(name) == 0 { - return nil, errors.New("error creating new section: empty section name") - } else if f.options.Insensitive && name != DEFAULT_SECTION { - name = strings.ToLower(name) - } - - if f.BlockMode { - f.lock.Lock() - defer f.lock.Unlock() - } - - if inSlice(name, f.sectionList) { - return f.sections[name], nil - } - - f.sectionList = append(f.sectionList, name) - f.sections[name] = newSection(f, name) - return f.sections[name], nil -} - -// NewRawSection creates a new section with an unparseable body. -func (f *File) NewRawSection(name, body string) (*Section, error) { - section, err := f.NewSection(name) - if err != nil { - return nil, err - } - - section.isRawSection = true - section.rawBody = body - return section, nil -} - -// NewSections creates a list of sections. -func (f *File) NewSections(names ...string) (err error) { - for _, name := range names { - if _, err = f.NewSection(name); err != nil { - return err - } - } - return nil -} - -// GetSection returns section by given name. -func (f *File) GetSection(name string) (*Section, error) { - if len(name) == 0 { - name = DEFAULT_SECTION - } else if f.options.Insensitive { - name = strings.ToLower(name) - } - - if f.BlockMode { - f.lock.RLock() - defer f.lock.RUnlock() - } - - sec := f.sections[name] - if sec == nil { - return nil, fmt.Errorf("section '%s' does not exist", name) - } - return sec, nil -} - -// Section assumes named section exists and returns a zero-value when not. -func (f *File) Section(name string) *Section { - sec, err := f.GetSection(name) - if err != nil { - // Note: It's OK here because the only possible error is empty section name, - // but if it's empty, this piece of code won't be executed. - sec, _ = f.NewSection(name) - return sec - } - return sec -} - -// Section returns list of Section. -func (f *File) Sections() []*Section { - sections := make([]*Section, len(f.sectionList)) - for i := range f.sectionList { - sections[i] = f.Section(f.sectionList[i]) - } - return sections -} - -// ChildSections returns a list of child sections of given section name. -func (f *File) ChildSections(name string) []*Section { - return f.Section(name).ChildSections() -} - -// SectionStrings returns list of section names. -func (f *File) SectionStrings() []string { - list := make([]string, len(f.sectionList)) - copy(list, f.sectionList) - return list -} - -// DeleteSection deletes a section. -func (f *File) DeleteSection(name string) { - if f.BlockMode { - f.lock.Lock() - defer f.lock.Unlock() - } - - if len(name) == 0 { - name = DEFAULT_SECTION - } - - for i, s := range f.sectionList { - if s == name { - f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) - delete(f.sections, name) - return - } - } -} - -func (f *File) reload(s dataSource) error { - r, err := s.ReadCloser() - if err != nil { - return err - } - defer r.Close() - - return f.parse(r) -} - -// Reload reloads and parses all data sources. -func (f *File) Reload() (err error) { - for _, s := range f.dataSources { - if err = f.reload(s); err != nil { - // In loose mode, we create an empty default section for nonexistent files. - if os.IsNotExist(err) && f.options.Loose { - f.parse(bytes.NewBuffer(nil)) - continue - } - return err - } - } - return nil -} - -// Append appends one or more data sources and reloads automatically. -func (f *File) Append(source interface{}, others ...interface{}) error { - ds, err := parseDataSource(source) - if err != nil { - return err - } - f.dataSources = append(f.dataSources, ds) - for _, s := range others { - ds, err = parseDataSource(s) - if err != nil { - return err - } - f.dataSources = append(f.dataSources, ds) - } - return f.Reload() -} - -func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { - equalSign := "=" - if PrettyFormat { - equalSign = " = " - } - - // Use buffer to make sure target is safe until finish encoding. - buf := bytes.NewBuffer(nil) - for i, sname := range f.sectionList { - sec := f.Section(sname) - if len(sec.Comment) > 0 { - if sec.Comment[0] != '#' && sec.Comment[0] != ';' { - sec.Comment = "; " + sec.Comment - } - if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil { - return nil, err - } - } - - if i > 0 || DefaultHeader { - if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { - return nil, err - } - } else { - // Write nothing if default section is empty - if len(sec.keyList) == 0 { - continue - } - } - - if sec.isRawSection { - if _, err := buf.WriteString(sec.rawBody); err != nil { - return nil, err - } - continue - } - - // Count and generate alignment length and buffer spaces using the - // longest key. Keys may be modifed if they contain certain characters so - // we need to take that into account in our calculation. - alignLength := 0 - if PrettyFormat { - for _, kname := range sec.keyList { - keyLength := len(kname) - // First case will surround key by ` and second by """ - if strings.ContainsAny(kname, "\"=:") { - keyLength += 2 - } else if strings.Contains(kname, "`") { - keyLength += 6 - } - - if keyLength > alignLength { - alignLength = keyLength - } - } - } - alignSpaces := bytes.Repeat([]byte(" "), alignLength) - - KEY_LIST: - for _, kname := range sec.keyList { - key := sec.Key(kname) - if len(key.Comment) > 0 { - if len(indent) > 0 && sname != DEFAULT_SECTION { - buf.WriteString(indent) - } - if key.Comment[0] != '#' && key.Comment[0] != ';' { - key.Comment = "; " + key.Comment - } - if _, err := buf.WriteString(key.Comment + LineBreak); err != nil { - return nil, err - } - } - - if len(indent) > 0 && sname != DEFAULT_SECTION { - buf.WriteString(indent) - } - - switch { - case key.isAutoIncrement: - kname = "-" - case strings.ContainsAny(kname, "\"=:"): - kname = "`" + kname + "`" - case strings.Contains(kname, "`"): - kname = `"""` + kname + `"""` - } - - for _, val := range key.ValueWithShadows() { - if _, err := buf.WriteString(kname); err != nil { - return nil, err - } - - if key.isBooleanType { - if kname != sec.keyList[len(sec.keyList)-1] { - buf.WriteString(LineBreak) - } - continue KEY_LIST - } - - // Write out alignment spaces before "=" sign - if PrettyFormat { - buf.Write(alignSpaces[:alignLength-len(kname)]) - } - - // In case key value contains "\n", "`", "\"", "#" or ";" - if strings.ContainsAny(val, "\n`") { - val = `"""` + val + `"""` - } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { - val = "`" + val + "`" - } - if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { - return nil, err - } - } - } - - if PrettySection { - // Put a line between sections - if _, err := buf.WriteString(LineBreak); err != nil { - return nil, err - } - } - } - - return buf, nil -} - -// WriteToIndent writes content into io.Writer with given indention. -// If PrettyFormat has been set to be true, -// it will align "=" sign with spaces under each section. -func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { - buf, err := f.writeToBuffer(indent) - if err != nil { - return 0, err - } - return buf.WriteTo(w) -} - -// WriteTo writes file content into io.Writer. -func (f *File) WriteTo(w io.Writer) (int64, error) { - return f.WriteToIndent(w, "") -} - -// SaveToIndent writes content to file system with given value indention. -func (f *File) SaveToIndent(filename, indent string) error { - // Note: Because we are truncating with os.Create, - // so it's safer to save to a temporary file location and rename afte done. - buf, err := f.writeToBuffer(indent) - if err != nil { - return err - } - - return ioutil.WriteFile(filename, buf.Bytes(), 0666) -} - -// SaveTo writes content to file system. -func (f *File) SaveTo(filename string) error { - return f.SaveToIndent(filename, "") -} diff --git a/vendor/gopkg.in/ini.v1/key.go b/vendor/gopkg.in/ini.v1/key.go index 838356af0..7c8566a1b 100644 --- a/vendor/gopkg.in/ini.v1/key.go +++ b/vendor/gopkg.in/ini.v1/key.go @@ -15,6 +15,7 @@ package ini import ( + "bytes" "errors" "fmt" "strconv" @@ -25,6 +26,7 @@ import ( // Key represents a key under a section. type Key struct { s *Section + Comment string name string value string isAutoIncrement bool @@ -33,7 +35,7 @@ type Key struct { isShadow bool shadows []*Key - Comment string + nestedValues []string } // newKey simply return a key object with given values. @@ -66,6 +68,22 @@ func (k *Key) AddShadow(val string) error { return k.addShadow(val) } +func (k *Key) addNestedValue(val string) error { + if k.isAutoIncrement || k.isBooleanType { + return errors.New("cannot add nested value to auto-increment or boolean key") + } + + k.nestedValues = append(k.nestedValues, val) + return nil +} + +func (k *Key) AddNestedValue(val string) error { + if !k.s.f.options.AllowNestedValues { + return errors.New("nested value is not allowed") + } + return k.addNestedValue(val) +} + // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv type ValueMapper func(string) string @@ -92,6 +110,12 @@ func (k *Key) ValueWithShadows() []string { return vals } +// NestedValues returns nested values stored in the key. +// It is possible returned value is nil if no nested values stored in the key. +func (k *Key) NestedValues() []string { + return k.nestedValues +} + // transformValue takes a raw value and transforms to its final string. func (k *Key) transformValue(val string) string { if k.s.f.ValueMapper != nil { @@ -114,7 +138,7 @@ func (k *Key) transformValue(val string) string { // Search in the same section. nk, err := k.s.GetKey(noption) - if err != nil { + if err != nil || k == nk { // Search again in default section. nk, _ = k.s.f.Section("").GetKey(noption) } @@ -444,11 +468,39 @@ func (k *Key) Strings(delim string) []string { return []string{} } - vals := strings.Split(str, delim) - for i := range vals { - // vals[i] = k.transformValue(strings.TrimSpace(vals[i])) - vals[i] = strings.TrimSpace(vals[i]) + runes := []rune(str) + vals := make([]string, 0, 2) + var buf bytes.Buffer + escape := false + idx := 0 + for { + if escape { + escape = false + if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) { + buf.WriteRune('\\') + } + buf.WriteRune(runes[idx]) + } else { + if runes[idx] == '\\' { + escape = true + } else if strings.HasPrefix(string(runes[idx:]), delim) { + idx += len(delim) - 1 + vals = append(vals, strings.TrimSpace(buf.String())) + buf.Reset() + } else { + buf.WriteRune(runes[idx]) + } + } + idx += 1 + if idx == len(runes) { + break + } + } + + if buf.Len() > 0 { + vals = append(vals, strings.TrimSpace(buf.String())) } + return vals } diff --git a/vendor/gopkg.in/ini.v1/parser.go b/vendor/gopkg.in/ini.v1/parser.go index 69d547627..db3af8f00 100644 --- a/vendor/gopkg.in/ini.v1/parser.go +++ b/vendor/gopkg.in/ini.v1/parser.go @@ -193,7 +193,9 @@ func hasSurroundedQuote(in string, quote byte) bool { strings.IndexByte(in[1:], quote) == len(in)-2 } -func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) { +func (p *parser) readValue(in []byte, + ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols bool) (string, error) { + line := strings.TrimLeftFunc(string(in), unicode.IsSpace) if len(line) == 0 { return "", nil @@ -204,6 +206,8 @@ func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bo valQuote = `"""` } else if line[0] == '`' { valQuote = "`" + } else if unescapeValueDoubleQuotes && line[0] == '"' { + valQuote = `"` } if len(valQuote) > 0 { @@ -214,6 +218,9 @@ func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bo return p.readMultilines(line, line[startIdx:], valQuote) } + if unescapeValueDoubleQuotes && valQuote == `"` { + return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil + } return line[startIdx : pos+startIdx], nil } @@ -234,10 +241,17 @@ func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bo } } - // Trim single quotes + // Trim single and double quotes if hasSurroundedQuote(line, '\'') || hasSurroundedQuote(line, '"') { line = line[1 : len(line)-1] + } else if len(valQuote) == 0 && unescapeValueCommentSymbols { + if strings.Contains(line, `\;`) { + line = strings.Replace(line, `\;`, ";", -1) + } + if strings.Contains(line, `\#`) { + line = strings.Replace(line, `\#`, "#", -1) + } } return line, nil } @@ -250,7 +264,15 @@ func (f *File) parse(reader io.Reader) (err error) { } // Ignore error because default section name is never empty string. - section, _ := f.NewSection(DEFAULT_SECTION) + name := DEFAULT_SECTION + if f.options.Insensitive { + name = strings.ToLower(DEFAULT_SECTION) + } + section, _ := f.NewSection(name) + + // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key + var isLastValueEmpty bool + var lastRegularKey *Key var line []byte var inUnparseableSection bool @@ -260,6 +282,14 @@ func (f *File) parse(reader io.Reader) (err error) { return err } + if f.options.AllowNestedValues && + isLastValueEmpty && len(line) > 0 { + if line[0] == ' ' || line[0] == '\t' { + lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) + continue + } + } + line = bytes.TrimLeftFunc(line, unicode.IsSpace) if len(line) == 0 { continue @@ -321,7 +351,11 @@ func (f *File) parse(reader io.Reader) (err error) { if err != nil { // Treat as boolean key when desired, and whole line is key name. if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys { - kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment) + kname, err := p.readValue(line, + f.options.IgnoreContinuation, + f.options.IgnoreInlineComment, + f.options.UnescapeValueDoubleQuotes, + f.options.UnescapeValueCommentSymbols) if err != nil { return err } @@ -344,10 +378,15 @@ func (f *File) parse(reader io.Reader) (err error) { p.count++ } - value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment) + value, err := p.readValue(line[offset:], + f.options.IgnoreContinuation, + f.options.IgnoreInlineComment, + f.options.UnescapeValueDoubleQuotes, + f.options.UnescapeValueCommentSymbols) if err != nil { return err } + isLastValueEmpty = len(value) == 0 key, err := section.NewKey(kname, value) if err != nil { @@ -356,6 +395,7 @@ func (f *File) parse(reader io.Reader) (err error) { key.isAutoIncrement = isAutoIncr key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() + lastRegularKey = key } return nil } diff --git a/vendor/gopkg.in/ini.v1/section.go b/vendor/gopkg.in/ini.v1/section.go index 94f7375ed..d8a402619 100644 --- a/vendor/gopkg.in/ini.v1/section.go +++ b/vendor/gopkg.in/ini.v1/section.go @@ -54,6 +54,14 @@ func (s *Section) Body() string { return strings.TrimSpace(s.rawBody) } +// SetBody updates body content only if section is raw. +func (s *Section) SetBody(body string) { + if !s.isRawSection { + return + } + s.rawBody = body +} + // NewKey creates a new key to given section. func (s *Section) NewKey(name, val string) (*Key, error) { if len(name) == 0 { @@ -136,6 +144,7 @@ func (s *Section) HasKey(name string) bool { } // Haskey is a backwards-compatible name for HasKey. +// TODO: delete me in v2 func (s *Section) Haskey(name string) bool { return s.HasKey(name) } diff --git a/vendor/gopkg.in/ini.v1/struct.go b/vendor/gopkg.in/ini.v1/struct.go index eeb8dabaa..9719dc698 100644 --- a/vendor/gopkg.in/ini.v1/struct.go +++ b/vendor/gopkg.in/ini.v1/struct.go @@ -113,7 +113,7 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } - if isStrict { + if err != nil && isStrict { return err } @@ -166,7 +166,7 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: durationVal, err := key.Duration() // Skip zero value - if err == nil && int(durationVal) > 0 { + if err == nil && int64(durationVal) > 0 { field.Set(reflect.ValueOf(durationVal)) return nil } @@ -450,6 +450,12 @@ func (s *Section) reflectFrom(val reflect.Value) error { // Note: fieldName can never be empty here, ignore error. sec, _ = s.f.NewSection(fieldName) } + + // Add comment from comment tag + if len(sec.Comment) == 0 { + sec.Comment = tpField.Tag.Get("comment") + } + if err = sec.reflectFrom(field); err != nil { return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) } @@ -461,6 +467,12 @@ func (s *Section) reflectFrom(val reflect.Value) error { if err != nil { key, _ = s.NewKey(fieldName, "") } + + // Add comment from comment tag + if len(key.Comment) == 0 { + key.Comment = tpField.Tag.Get("comment") + } + if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil { return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) }