diff --git a/Dockerfile b/Dockerfile index 8f9cca1..0a091a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,7 @@ # Copy the go source COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ - +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO diff --git a/Makefile b/Makefile index 3221a51..0949d4d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Image URL to use all building/pushing image targets -IMG ?= controller:latest +IMG ?= docker.jerxie.com/anthos-cert-manager:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.25.0 diff --git a/config/crd/bases/onprem.cluster.gke.io_certificates.yaml b/config/crd/bases/onprem.cluster.gke.io_certificates.yaml new file mode 100644 index 0000000..90df614 --- /dev/null +++ b/config/crd/bases/onprem.cluster.gke.io_certificates.yaml @@ -0,0 +1,223 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: certificates.onprem.cluster.gke.io +spec: + group: onprem.cluster.gke.io + names: + kind: Certificate + listKind: CertificateList + plural: certificates + singular: certificate + scope: Namespaced + versions: + - name: anthoscertmanager + schema: + openAPIV3Schema: + description: Certificate is the Schema for the certificates API + 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CertificateSpec defines the desired state of Certificate + properties: + commonName: + description: 'CommonName is a common name to be used on the Certificate. + The CommonName should have a length of 64 characters or fewer to + avoid generating invalid CSRs. This value is ignored by TLS clients + when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4' + type: string + dnsNames: + description: DNSNames is a list of DNS subjectAltNames to be set on + the Certificate. + items: + type: string + type: array + duration: + description: The requested 'duration' (i.e. lifetime) of the Certificate. + This option may be ignored/overridden by some issuer types. If unset + this defaults to 90 days. Certificate will be renewed either 2/3 + through its duration or `renewBefore` period before its expiry, + whichever is later. Minimum accepted duration is 1 hour. Value must + be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + ipAddresses: + description: IPAddresses is a list of IP address subjectAltNames to + be set on the Certificate. + items: + type: string + type: array + isCA: + description: IsCA will mark this Certificate as valid for certificate + signing. This will automatically add the `cert sign` usage to the + list of `usages`. + type: boolean + issuerRef: + description: IssuerRef is a reference to the issuer for this certificate. + If the `kind` field is not set, or set to `Issuer`, an Issuer resource + with the given name in the same namespace as the Certificate will + be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer + with the provided name will be used. The `name` field in this stanza + is required at all times. + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + privateKey: + description: Options to control private keys used for the Certificate. + properties: + algorithm: + description: Algorithm is the private key algorithm of the corresponding + private key for this certificate. If provided, allowed values + are either `RSA`,`Ed25519` or `ECDSA` If `algorithm` is specified + and `size` is not provided, key size of 256 will be used for + `ECDSA` key algorithm and key size of 2048 will be used for + `RSA` key algorithm. key size is ignored when using the `Ed25519` + key algorithm. + type: string + encoding: + description: The private key cryptography standards (PKCS) encoding + for this certificate's private key to be encoded in. If provided, + allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and + PKCS#8, respectively. Defaults to `PKCS1` if not specified. + type: string + rotationPolicy: + description: RotationPolicy controls how private keys should be + regenerated when a re-issuance is being processed. If set to + Never, a private key will only be generated if one does not + already exist in the target `spec.secretName`. If one does exists + but it does not have the correct algorithm or size, a warning + will be raised to await user intervention. If set to Always, + a private key matching the specified requirements will be generated + whenever a re-issuance occurs. Default is 'Never' for backward + compatibility. + enum: + - Never + - Always + type: string + size: + description: Size is the key bit size of the corresponding private + key for this certificate. If `algorithm` is set to `RSA`, valid + values are `2048`, `4096` or `8192`, and will default to `2048` + if not specified. If `algorithm` is set to `ECDSA`, valid values + are `256`, `384` or `521`, and will default to `256` if not + specified. If `algorithm` is set to `Ed25519`, Size is ignored. + No other values are allowed. + type: integer + type: object + renewBefore: + description: How long before the currently issued certificate's expiry + cert-manager should renew the certificate. The default is 2/3 of + the issued certificate's duration. Minimum accepted value is 5 minutes. + Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + secretName: + description: SecretName is the name of the secret resource that will + be automatically created and managed by this Certificate resource. + It will be populated with a private key and certificate, signed + by the denoted issuer. + type: string + secretTemplate: + description: SecretTemplate defines annotations and labels to be copied + to the Certificate's Secret. Labels and annotations on the Secret + will be changed as they appear on the SecretTemplate when added + or removed. SecretTemplate annotations are added in conjunction + with, and cannot overwrite, the base set of annotations cert-manager + sets on the Certificate's Secret. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a key value map to be copied to the + target Kubernetes Secret. + type: object + labels: + additionalProperties: + type: string + description: Labels is a key value map to be copied to the target + Kubernetes Secret. + type: object + type: object + subject: + description: Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). + properties: + countries: + description: Countries to be used on the Certificate. + items: + type: string + type: array + localities: + description: Cities to be used on the Certificate. + items: + type: string + type: array + organizationalUnits: + description: Organizational Units to be used on the Certificate. + items: + type: string + type: array + organizations: + description: Organizations to be used on the Certificate. + items: + type: string + type: array + postalCodes: + description: Postal codes to be used on the Certificate. + items: + type: string + type: array + provinces: + description: State/Provinces to be used on the Certificate. + items: + type: string + type: array + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + items: + type: string + type: array + type: object + required: + - issuerRef + - secretName + type: object + status: + description: CertificateStatus defines the observed state of Certificate + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/onprem.cluster.gke.io_clusterissuers.yaml b/config/crd/bases/onprem.cluster.gke.io_clusterissuers.yaml new file mode 100644 index 0000000..f9ef3b5 --- /dev/null +++ b/config/crd/bases/onprem.cluster.gke.io_clusterissuers.yaml @@ -0,0 +1,145 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: clusterissuers.onprem.cluster.gke.io +spec: + group: onprem.cluster.gke.io + names: + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + singular: clusterissuer + scope: Namespaced + versions: + - name: anthoscertmanager + schema: + openAPIV3Schema: + description: A ClusterIssuer represents a certificate issuing authority which + can be referenced as part of `issuerRef` fields. It is similar to an Issuer, + however it is cluster-scoped and therefore can be referenced by resources + that exist in *any* namespace, not just the same namespace as the referent. + 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the ClusterIssuer resource. + properties: + ca: + description: CA configures this issuer to sign certificates using + a signing CA keypair stored in a Secret resource. This is used to + build internal PKIs that are managed by cert-manager. + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate + extension which identifies the location of the CRL from which + the revocation of this certificate can be checked. If not set, + certificates will be issued without distribution points set. + items: + type: string + type: array + ocspServers: + description: The OCSP server list is an X.509 v3 extension that + defines a list of URLs of OCSP responders. The OCSP responders + can be queried for the revocation status of an issued certificate. + If not set, the certificate will be issued with no OCSP servers + set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + items: + type: string + type: array + secretName: + description: SecretName is the name of the secret used to sign + Certificates issued by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates + using the private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate + extension which identifies the location of the CRL from which + the revocation of this certificate can be checked. If not set + certificate will be issued without CDP. Values are strings. + items: + type: string + type: array + type: object + type: object + status: + description: Status of the ClusterIssuer. This is set and managed automatically. + properties: + conditions: + description: List of status conditions to indicate the status of a + CertificateRequest. Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for + an Issuer. + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.condition[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the Issuer. + format: int64 + type: integer + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/onprem.cluster.gke.io_issuers.yaml b/config/crd/bases/onprem.cluster.gke.io_issuers.yaml new file mode 100644 index 0000000..92b05ba --- /dev/null +++ b/config/crd/bases/onprem.cluster.gke.io_issuers.yaml @@ -0,0 +1,145 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: issuers.onprem.cluster.gke.io +spec: + group: onprem.cluster.gke.io + names: + kind: Issuer + listKind: IssuerList + plural: issuers + singular: issuer + scope: Namespaced + versions: + - name: anthoscertmanager + schema: + openAPIV3Schema: + description: An Issuer represents a certificate issuing authority which can + be referenced as part of `issuerRef` fields. It is scoped to a single namespace + and can therefore only be referenced by resources within the same namespace. + 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Desired state of the Issuer resource. + properties: + ca: + description: CA configures this issuer to sign certificates using + a signing CA keypair stored in a Secret resource. This is used to + build internal PKIs that are managed by cert-manager. + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate + extension which identifies the location of the CRL from which + the revocation of this certificate can be checked. If not set, + certificates will be issued without distribution points set. + items: + type: string + type: array + ocspServers: + description: The OCSP server list is an X.509 v3 extension that + defines a list of URLs of OCSP responders. The OCSP responders + can be queried for the revocation status of an issued certificate. + If not set, the certificate will be issued with no OCSP servers + set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + items: + type: string + type: array + secretName: + description: SecretName is the name of the secret used to sign + Certificates issued by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates + using the private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate + extension which identifies the location of the CRL from which + the revocation of this certificate can be checked. If not set + certificate will be issued without CDP. Values are strings. + items: + type: string + type: array + type: object + type: object + status: + description: Status of the Issuer. This is set and managed automatically. + properties: + conditions: + description: List of status conditions to indicate the status of a + CertificateRequest. Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for + an Issuer. + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding + to the last status change of this condition. + format: date-time + type: string + message: + description: Message is a human readable description of the + details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.condition[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the Issuer. + format: int64 + type: integer + reason: + description: Reason is a brief machine readable explanation + for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + - status + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 2409dc9..f36ba7f 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,12 +1,12 @@ # Adds namespace to all resources. -namespace: anthoscertmanager-system +namespace: anthoscertmanager # Value of this field is prepended to the # names of all resources, e.g. a deployment named # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. -namePrefix: anthoscertmanager- +namePrefix: acm- # Labels to add to all resources and selectors. #labels: diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 687c4a7..46c38c2 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -3,7 +3,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: controller-manager + name: anthos-certificate-manager namespace: system spec: template: diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml index f6f5891..d9b77b6 100644 --- a/config/default/manager_config_patch.yaml +++ b/config/default/manager_config_patch.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: controller-manager + name: anthos-certificate-manager namespace: system spec: template: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b8..4fc5712 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,9 @@ resources: - manager.yaml +- secret.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: docker.jerxie.com/anthos-cert-manager + newTag: latest diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index bd06fe1..95b0e5e 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -2,7 +2,7 @@ kind: Namespace metadata: labels: - control-plane: controller-manager + k8s-app: anthos-certificate-manager app.kubernetes.io/name: namespace app.kubernetes.io/instance: system app.kubernetes.io/component: manager @@ -14,12 +14,12 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: controller-manager + name: anthos-certificate-manager namespace: system labels: - control-plane: controller-manager + k8s-app: anthos-certificate-manager app.kubernetes.io/name: deployment - app.kubernetes.io/instance: controller-manager + app.kubernetes.io/instance: anthos-certificate-manager app.kubernetes.io/component: manager app.kubernetes.io/created-by: anthoscertmanager app.kubernetes.io/part-of: anthoscertmanager @@ -27,14 +27,14 @@ spec: selector: matchLabels: - control-plane: controller-manager + k8s-app: anthos-certificate-manager replicas: 1 template: metadata: annotations: kubectl.kubernetes.io/default-container: manager labels: - control-plane: controller-manager + k8s-app: anthos-certificate-manager spec: # TODO(user): Uncomment the following code to configure the nodeAffinity expression # according to the platforms which are supported by your solution. @@ -56,6 +56,8 @@ # operator: In # values: # - linux + imagePullSecrets: + - name: regcred securityContext: runAsNonRoot: true # TODO(user): For common cases that do not require escalating privileges @@ -98,5 +100,5 @@ requests: cpu: 10m memory: 64Mi - serviceAccountName: controller-manager + serviceAccountName: anthos-certificate-manager terminationGracePeriodSeconds: 10 diff --git a/config/manager/secret.yaml b/config/manager/secret.yaml new file mode 100644 index 0000000..1f08c40 --- /dev/null +++ b/config/manager/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: anthos-certificate-manager + name: regcred +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6eyJkb2NrZXIuamVyeGllLmNvbSI6eyJ1c2VybmFtZSI6InJlZ2lzdHJ5IiwicGFzc3dvcmQiOiJyZWdpc3RyeSIsImVtYWlsIjoiYXhpZXlhbmdiQGdtYWlsLmNvbSIsImF1dGgiOiJjbVZuYVhOMGNuazZjbVZuYVhOMGNuaz0ifX19 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..01fe0ef --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - onprem.cluster.gke.io + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - onprem.cluster.gke.io + resources: + - certificates/finalizers + verbs: + - update +- apiGroups: + - onprem.cluster.gke.io + resources: + - certificates/status + verbs: + - get + - patch + - update diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index 14619ab..8594471 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -3,10 +3,10 @@ metadata: labels: app.kubernetes.io/name: serviceaccount - app.kubernetes.io/instance: controller-manager-sa + app.kubernetes.io/instance: anthos-certificate-manager-sa app.kubernetes.io/component: rbac app.kubernetes.io/created-by: anthoscertmanager app.kubernetes.io/part-of: anthoscertmanager app.kubernetes.io/managed-by: kustomize - name: controller-manager + name: anthos-certificate-manager namespace: system diff --git a/controllers/certificate_controller.go b/controllers/certificate_controller.go deleted file mode 100644 index e10d323..0000000 --- a/controllers/certificate_controller.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2022. - -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 controllers - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -// CertificateReconciler reconciles a Certificate object -type CertificateReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Certificate object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile -func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) - - // TODO(user): your logic here - - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *CertificateReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - Complete(r) -} diff --git a/controllers/suite_test.go b/controllers/suite_test.go deleted file mode 100644 index d90f9d2..0000000 --- a/controllers/suite_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2022. - -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 controllers - -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - onpremclustergkeiov1 "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = onpremclustergkeiov1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/main.go b/main.go index 3c1bb09..1877ef6 100644 --- a/main.go +++ b/main.go @@ -22,17 +22,17 @@ // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/controllers" anthoscertmanager "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" + controllers "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/controller/certificates" + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" //+kubebuilder:scaffold:imports ) @@ -63,7 +63,10 @@ opts.BindFlags(flag.CommandLine) flag.Parse() - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + logf.InitLogs(flag.CommandLine) + defer logf.FlushLogs() + + ctrl.SetLogger(logf.Log) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, diff --git a/pkg/api/scheme.go b/pkg/api/scheme.go new file mode 100644 index 0000000..7efde4d --- /dev/null +++ b/pkg/api/scheme.go @@ -0,0 +1,27 @@ +package api + +import ( + acmapiv1 "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" + acmmetav1 "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) + +var localSchemaBuilder = runtime.SchemeBuilder{ + acmapiv1.AddToScheme, + acmmetav1.AddToScheme, +} + +var AddToScheme = localSchemaBuilder.AddToScheme + +func init() { + metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/apis/anthoscertmanager/doc.go b/pkg/apis/anthoscertmanager/doc.go new file mode 100644 index 0000000..546bc86 --- /dev/null +++ b/pkg/apis/anthoscertmanager/doc.go @@ -0,0 +1,7 @@ +// +groupName=anthos-cert-manager.io +// +groupGoName=AnthosCertmanager + +// Package anthoscertmanager is the internal version of the API. +package anthoscertmanager + +const GroupName = "anthos-cert-manager.io" diff --git a/pkg/apis/anthoscertmanager/v1/certificate_types.go b/pkg/apis/anthoscertmanager/v1/certificate_types.go index 3b6fd51..7af6437 100644 --- a/pkg/apis/anthoscertmanager/v1/certificate_types.go +++ b/pkg/apis/anthoscertmanager/v1/certificate_types.go @@ -14,7 +14,7 @@ limitations under the License. */ -package anthoscertmanager +package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,7 +22,6 @@ acmmeta "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/meta/v1" ) - type PrivateKeyAlgorithm string const ( @@ -51,7 +50,6 @@ PKCS8 PrivateKeyEncoding = "PKCS8" ) - // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. @@ -91,7 +89,7 @@ // +optional Duration *metav1.Duration `json:"duration,omitempty"` - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. + // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. // +optional IPAddresses []string `json:"ipAddresses,omitempty"` @@ -176,7 +174,6 @@ // is being issued. type PrivateKeyRotationPolicy string - // CertificateStatus defines the observed state of Certificate type CertificateStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -244,6 +241,6 @@ Items []Certificate `json:"items"` } -func init() { - SchemeBuilder.Register(&Certificate{}, &CertificateList{}) -} +// func init() { +// SchemeBuilder.Register(&Certificate{}, &CertificateList{}) +// } diff --git a/pkg/apis/anthoscertmanager/v1/doc.go b/pkg/apis/anthoscertmanager/v1/doc.go new file mode 100644 index 0000000..c8c2f9f --- /dev/null +++ b/pkg/apis/anthoscertmanager/v1/doc.go @@ -0,0 +1,7 @@ +// Package v1 is the v1 version of the API. + +// +k8s:deepcopy-gen=package,register +// +groupName=anthos-cert-manager.io +// +groupGoName=AnthosCertmanager + +package v1 diff --git a/pkg/apis/anthoscertmanager/v1/groupversion_info.go b/pkg/apis/anthoscertmanager/v1/groupversion_info.go deleted file mode 100644 index bdbe5e0..0000000 --- a/pkg/apis/anthoscertmanager/v1/groupversion_info.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2022. - -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 anthoscertmanager contains API Schema definitions for the v1 API group -// +kubebuilder:object:generate=true -// +groupName=onprem.cluster.gke.io -package anthoscertmanager - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "onprem.cluster.gke.io", Version: "v1"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} - - // AddToScheme adds the types in this group-version to the given scheme. - AddToScheme = SchemeBuilder.AddToScheme -) diff --git a/pkg/apis/anthoscertmanager/v1/issuer_types.go b/pkg/apis/anthoscertmanager/v1/issuer_types.go index 106906a..47749b5 100644 --- a/pkg/apis/anthoscertmanager/v1/issuer_types.go +++ b/pkg/apis/anthoscertmanager/v1/issuer_types.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package anthoscertmanager +package v1 import ( acmmeta "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/meta/v1" @@ -28,17 +28,21 @@ // be referenced by resources that exist in *any* namespace, not just the same // namespace as the referent. type ClusterIssuer struct { - metav1.TypeMeta - metav1.ObjectMeta + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` // Desired state of the ClusterIssuer resource. - Spec IssuerSpec + Spec IssuerSpec `json:"spec"` // Status of the ClusterIssuer. This is set and managed automatically. - Status IssuerStatus + // +optional + Status IssuerStatus `json:"status"` } +// +genclient +// +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // ClusterIssuerList is a list of Issuers type ClusterIssuerList struct { @@ -48,37 +52,40 @@ Items []ClusterIssuer } +// +genclient +// +k8s:openapi-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:storageversion // An Issuer represents a certificate issuing authority which can be // referenced as part of `issuerRef` fields. // It is scoped to a single namespace and can therefore only be referenced by // resources within the same namespace. type Issuer struct { - metav1.TypeMeta - metav1.ObjectMeta + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` // Desired state of the Issuer resource. - Spec IssuerSpec + Spec IssuerSpec `json:"spec"` // Status of the Issuer. This is set and managed automatically. - Status IssuerStatus + Status IssuerStatus `json:"status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // IssuerList is a list of Issuers type IssuerList struct { - metav1.TypeMeta - metav1.ListMeta + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` - Items []Issuer + Items []Issuer `json:"items"` } // IssuerSpec is the specification of an Issuer. This includes any // configuration required for the issuer. type IssuerSpec struct { - IssuerConfig + IssuerConfig `json:",inline"` } // IssuerConfig is a generic wrapper around custom issuer types @@ -87,18 +94,23 @@ // CA configures this issuer to sign certificates using a signing CA keypair // stored in a Secret resource. // This is used to build internal PKIs that are managed by cert-manager. - CA *CAIssuer + // +optional + CA *CAIssuer `json:"ca,omitempty"` // SelfSigned configures this issuer to 'self sign' certificates using the // private key used to create the CertificateRequest object. - SelfSigned *SelfSignedIssuer + // +optional + SelfSigned *SelfSignedIssuer `json:"selfSigned,omitempty"` } // IssuerStatus contains status information about an Issuer type IssuerStatus struct { // List of status conditions to indicate the status of a CertificateRequest. // Known condition types are `Ready`. - Conditions []IssuerCondition + // +listType=map + // +listMapKey=type + // +optional + Conditions []IssuerCondition `json:"conditions,omitempty"` } // CAIssuer configures an issuer that can issue certificates from its provided @@ -109,19 +121,21 @@ type CAIssuer struct { // SecretName is the name of the secret used to sign Certificates issued // by this Issuer. - SecretName string + SecretName string `json:"secretName"` // The CRL distribution points is an X.509 v3 certificate extension which identifies // the location of the CRL from which the revocation of this certificate can be checked. // If not set, certificates will be issued without distribution points set. - CRLDistributionPoints []string + // +optional + CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` // The OCSP server list is an X.509 v3 extension that defines a list of // URLs of OCSP responders. The OCSP responders can be queried for the // revocation status of an issued certificate. If not set, the // certificate will be issued with no OCSP servers set. For example, an // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". - OCSPServers []string + // +optional + OCSPServers []string `json:"ocspServers,omitempty"` } // SelfSignedIssuer configures an issuer to 'self sign' certificates using the @@ -130,37 +144,40 @@ // The CRL distribution points is an X.509 v3 certificate extension which identifies // the location of the CRL from which the revocation of this certificate can be checked. // If not set certificate will be issued without CDP. Values are strings. - CRLDistributionPoints []string + CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` } // IssuerCondition contains condition information for an Issuer. type IssuerCondition struct { // Type of the condition, known values are (`Ready`). - Type IssuerConditionType + Type IssuerConditionType `json:"type"` // Status of the condition, one of (`True`, `False`, `Unknown`). - Status acmmeta.ConditionStatus + Status acmmeta.ConditionStatus `json:"status"` // LastTransitionTime is the timestamp corresponding to the last status // change of this condition. - LastTransitionTime *metav1.Time + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` // Reason is a brief machine readable explanation for the condition's last // transition. - Reason string + // +optional + Reason string `json:"reason,omitempty"` // Message is a human readable description of the details of the last // transition, complementing reason. - Message string + // +optional + Message string `json:"message,omitempty"` // If set, this represents the .metadata.generation that the condition was // set based upon. // For instance, if .metadata.generation is currently 12, but the // .status.condition[x].observedGeneration is 9, the condition is out of date // with respect to the current state of the Issuer. - ObservedGeneration int64 + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` } - // IssuerConditionType represents an Issuer condition value. -type IssuerConditionType string \ No newline at end of file +type IssuerConditionType string diff --git a/pkg/apis/anthoscertmanager/v1/register.go b/pkg/apis/anthoscertmanager/v1/register.go new file mode 100644 index 0000000..e6f447a --- /dev/null +++ b/pkg/apis/anthoscertmanager/v1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2022. + +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 anthoscertmanager contains API Schema definitions for the v1 API group +// +kubebuilder:object:generate=true +// +groupName=onprem.cluster.gke.io +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: "onprem.cluster.gke.io", Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemaBuilder = &SchemeBuilder + AddToScheme = localSchemaBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Certificate{}, + &CertificateList{}, + &Issuer{}, + &IssuerList{}, + &ClusterIssuer{}, + &ClusterIssuerList{}, + // &CertificateRequest{}, + // &CertificateRequestList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/anthoscertmanager/v1/zz_generated.deepcopy.go b/pkg/apis/anthoscertmanager/v1/zz_generated.deepcopy.go index f24690a..8e00a8d 100644 --- a/pkg/apis/anthoscertmanager/v1/zz_generated.deepcopy.go +++ b/pkg/apis/anthoscertmanager/v1/zz_generated.deepcopy.go @@ -19,11 +19,11 @@ // Code generated by controller-gen. DO NOT EDIT. -package anthoscertmanager +package v1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -164,7 +164,7 @@ } if in.RenewBefore != nil { in, out := &in.RenewBefore, &out.RenewBefore - *out = new(v1.Duration) + *out = new(metav1.Duration) **out = **in } if in.DNSNames != nil { @@ -174,7 +174,7 @@ } if in.Duration != nil { in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) + *out = new(metav1.Duration) **out = **in } if in.IPAddresses != nil { diff --git a/pkg/apis/doc.go b/pkg/apis/doc.go new file mode 100644 index 0000000..d9fdb4d --- /dev/null +++ b/pkg/apis/doc.go @@ -0,0 +1,4 @@ +// +// +domain=anthos-cert-manager.io + +package apis diff --git a/pkg/apis/meta/doc.go b/pkg/apis/meta/doc.go new file mode 100644 index 0000000..aba1b0e --- /dev/null +++ b/pkg/apis/meta/doc.go @@ -0,0 +1,6 @@ +// +groupName=meta.anthos-cert-manager.io + +// Package meta contains meta types for cert-manager APIs +package meta + +const GroupName = "meta.anthos-cert-manager.io" diff --git a/pkg/apis/meta/v1/doc.go b/pkg/apis/meta/v1/doc.go new file mode 100644 index 0000000..0d1f643 --- /dev/null +++ b/pkg/apis/meta/v1/doc.go @@ -0,0 +1,5 @@ +// Package v1 contains meta types for cert-manager APIs +// +k8s:deepcopy-gen=package +// +gencrdrefdocs:force +// +groupName=meta.anthos-cert-manager.io +package v1 diff --git a/pkg/apis/meta/v1/register.go b/pkg/apis/meta/v1/register.go new file mode 100644 index 0000000..aef0559 --- /dev/null +++ b/pkg/apis/meta/v1/register.go @@ -0,0 +1,35 @@ +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + acmmeta "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/meta" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: acmmeta.GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + // No types to register in the meta group + return nil +} diff --git a/pkg/apis/meta/v1/types.go b/pkg/apis/meta/v1/types.go index 630bbf7..923d87f 100644 --- a/pkg/apis/meta/v1/types.go +++ b/pkg/apis/meta/v1/types.go @@ -60,4 +60,4 @@ const ( // Used as a data key in Secret resources to store a CA certificate. TLSCAKey = "ca.crt" -) \ No newline at end of file +) diff --git a/pkg/apis/meta/v1/zz_generated.deepcopy.go b/pkg/apis/meta/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..9e402a6 --- /dev/null +++ b/pkg/apis/meta/v1/zz_generated.deepcopy.go @@ -0,0 +1,70 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import () + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { + if in == nil { + return nil + } + out := new(LocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { + if in == nil { + return nil + } + out := new(SecretKeySelector) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/controller/builder.go b/pkg/controller/builder.go new file mode 100644 index 0000000..6d6c69f --- /dev/null +++ b/pkg/controller/builder.go @@ -0,0 +1,83 @@ +package controller + +import ( + "context" + "fmt" + "time" + + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" +) + +// Builder is used to build controllers that implement the queuingController +// interface +type Builder struct { + // the root controller context factory. Used to build a component context + // which is passed when calling Register() on the queueing Controller. + contextFactory *ContextFactory + + // name is the name for this controller + name string + + // the actual controller implementation + impl queueingController + + // runFirstFuncs are a list of functions that will be called immediately + // after the controller has been initialised, once. They are run in queue, sequentially, + // and block runDurationFuncs until complete. + runFirstFuncs []runFunc + + // runPeriodicFuncs are a list of functions that will be called every + // 'interval' + runPeriodicFuncs []runPeriodicFunc +} + +// New creates a basic Builder, setting the sync call to the one given +func NewBuilder(controllerctx *ContextFactory, name string) *Builder { + return &Builder{ + contextFactory: controllerctx, + name: name, + } +} + +func (b *Builder) For(ctrl queueingController) *Builder { + b.impl = ctrl + return b +} + +// With will register an additional function that should be called every +// 'interval' alongside the controller. +// This is useful if a controller needs to periodically run a scheduled task. +func (b *Builder) With(function func(context.Context), interval time.Duration) *Builder { + b.runPeriodicFuncs = append(b.runPeriodicFuncs, runPeriodicFunc{ + fn: function, + interval: interval, + }) + return b +} + +// First will register a function that will be called once, after the +// controller has been initialised. They are queued, run sequentially, and +// block "With" runDurationFuncs from running until all are complete. +func (b *Builder) First(function func(context.Context)) *Builder { + b.runFirstFuncs = append(b.runFirstFuncs, function) + return b +} + +func (b *Builder) Complete() (Interface, error) { + controllerctx, err := b.contextFactory.Build(b.name) + if err != nil { + return nil, err + } + + ctx := logf.NewContext(controllerctx.RootContext, logf.Log, b.name) + + if b.impl == nil { + return nil, fmt.Errorf("controller implementation must be non-nil") + } + queue, mustSync, err := b.impl.Register(controllerctx) + if err != nil { + return nil, fmt.Errorf("error registering controller: %v", err) + } + + return NewController(ctx, b.name, b.impl.ProcessItem, mustSync, b.runPeriodicFuncs, queue), nil +} diff --git a/pkg/controller/certificates/certificate_controller.go b/pkg/controller/certificates/certificate_controller.go new file mode 100644 index 0000000..91d3e9a --- /dev/null +++ b/pkg/controller/certificates/certificate_controller.go @@ -0,0 +1,64 @@ +/* +Copyright 2022. + +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 controllers + +import ( + "context" + + anthoscertmanagerv1 "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// CertificateReconciler reconciles a Certificate object +type CertificateReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=onprem.cluster.gke.io,resources=certificates/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Certificate object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := logf.FromContext(ctx) + var certificate anthoscertmanagerv1.Certificate + if err := r.Get(ctx, req.NamespacedName, &certificate); err != nil { + log.Error(err, "Fail to get certificate") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CertificateReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + Complete(r) +} diff --git a/pkg/controller/certificates/issuing/internal/secret.go b/pkg/controller/certificates/issuing/internal/secret.go new file mode 100644 index 0000000..2019a68 --- /dev/null +++ b/pkg/controller/certificates/issuing/internal/secret.go @@ -0,0 +1,267 @@ +package internal + +import ( + coreclient "k8s.io/client-go/kubernetes/typed/core/v1" + corelisters "k8s.io/client-go/listers/core/v1" + + acmapi "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" +) + +var ( + certificateGvk = acmapi.SchemeGroupVersion.WithKind("Certificate") +) + +// SecretsManager creates and updates secrets with certificate and key data. +type SecretsManager struct { + secretClient coreclient.SecretsGetter + secretLister corelisters.SecretLister + + // fieldManager is the manager name used for the Apply operations on Secrets. + fieldManager string + + // if true, Secret resources created by the controller will have an + // 'owner reference' set, meaning when the Certificate is deleted, the + // Secret resource will be automatically deleted. + // This option is disabled by default. + enableSecretOwnerReferences bool +} + +// SecretData is a structure wrapping private key, Certificate and CA data +type SecretData struct { + PrivateKey, Certificate, CA []byte +} + +// NewSecretsManager returns a new SecretsManager. Setting +// enableSecretOwnerReferences to true will mean that secrets will be deleted +// when the corresponding Certificate is deleted. +func NewSecretsManager( + secretClient coreclient.SecretsGetter, + secretLister corelisters.SecretLister, + fieldManager string, + enableSecretOwnerReferences bool, +) *SecretsManager { + return &SecretsManager{ + secretClient: secretClient, + secretLister: secretLister, + fieldManager: fieldManager, + enableSecretOwnerReferences: enableSecretOwnerReferences, + } +} + +// // UpdateData will ensure the Secret resource contains the given secret data as +// // well as appropriate metadata using an Apply call. +// // If the Secret resource does not exist, it will be created on Apply. +// // UpdateData will also update deprecated annotations if they exist. +// func (s *SecretsManager) UpdateData(ctx context.Context, crt *acmapi.Certificate, data SecretData) error { +// secret, err := s.getCertificateSecret(ctx, crt) +// if err != nil { +// return err +// } + +// log := logf.FromContext(ctx).WithName("secrets_manager") +// log = logf.WithResource(log, secret) + +// if err := s.setValues(crt, secret, data); err != nil { +// return err +// } + +// // Build Secret apply configuration and options. +// applyOpts := metav1.ApplyOptions{FieldManager: s.fieldManager, Force: true} +// applyCnf := applycorev1.Secret(secret.Name, secret.Namespace). +// WithAnnotations(secret.Annotations).WithLabels(secret.Labels). +// WithData(secret.Data).WithType(secret.Type) + +// // If Secret owner reference is enabled, set it on the Secret. This results +// // in a no-op if the Secret already exists and has the owner reference set, +// // and visa-versa. +// if s.enableSecretOwnerReferences { +// ref := *metav1.NewControllerRef(crt, certificateGvk) +// applyCnf = applyCnf.WithOwnerReferences(&applymetav1.OwnerReferenceApplyConfiguration{ +// APIVersion: &ref.APIVersion, Kind: &ref.Kind, +// Name: &ref.Name, UID: &ref.UID, +// Controller: ref.Controller, BlockOwnerDeletion: ref.BlockOwnerDeletion, +// }) +// } + +// log.V(logf.DebugLevel).Info("applying secret") + +// _, err = s.secretClient.Secrets(secret.Namespace).Apply(ctx, applyCnf, applyOpts) +// if err != nil { +// return fmt.Errorf("failed to apply secret %s/%s: %w", secret.Namespace, secret.Name, err) +// } + +// return nil +// } + +// // setValues will update the Secret resource 'secret' with the data contained +// // in the given secretData. +// // It will update labels and annotations on the Secret resource appropriately. +// // The Secret resource 's' must be non-nil, although may be a resource that does +// // not exist in the Kubernetes apiserver yet. +// // setValues will NOT actually update the resource in the apiserver. +// // It will also update depreciated issuer name and kind annotations if they +// // exist. +// func (s *SecretsManager) setValues(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { +// if err := s.setKeystores(crt, secret, data); err != nil { +// return fmt.Errorf("failed to add keystores to Secret: %w", err) +// } + +// // Add additional output formats if feature enabled. +// if utilfeature.DefaultFeatureGate.Enabled(feature.AdditionalCertificateOutputFormats) { +// if err := setAdditionalOutputFormats(crt, secret, data); err != nil { +// return fmt.Errorf("failed to add additional output formats to Secret: %w", err) +// } +// } + +// secret.Data[corev1.TLSPrivateKeyKey] = data.PrivateKey +// secret.Data[corev1.TLSCertKey] = data.Certificate +// if len(data.CA) > 0 { +// secret.Data[acmmeta.TLSCAKey] = data.CA +// } + +// var certificate *x509.Certificate +// if len(data.Certificate) > 0 { +// var err error +// certificate, err = utilpki.DecodeX509CertificateBytes(data.Certificate) +// // TODO: handle InvalidData here? +// if err != nil { +// return err +// } +// } + +// secret.Annotations = certificates.AnnotationsForCertificateSecret(crt, certificate) +// if secret.Labels == nil { +// secret.Labels = make(map[string]string) +// } + +// if crt.Spec.SecretTemplate != nil { +// for k, v := range crt.Spec.SecretTemplate.Labels { +// secret.Labels[k] = v +// } +// for k, v := range crt.Spec.SecretTemplate.Annotations { +// secret.Annotations[k] = v +// } +// } + +// return nil +// } + +// // getCertificateSecret will return a secret which is ready for fields to be +// // applied. Only the Secret Type will be persisted from the original Secret. +// func (s *SecretsManager) getCertificateSecret(ctx context.Context, crt *cmapi.Certificate) (*corev1.Secret, error) { +// // Get existing secret if it exists. +// existingSecret, err := s.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) + +// // If secret doesn't exist yet, return an empty secret that should be +// // created. +// if apierrors.IsNotFound(err) { +// return &corev1.Secret{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: crt.Spec.SecretName, +// Namespace: crt.Namespace, +// }, +// Data: make(map[string][]byte), +// Type: corev1.SecretTypeTLS, +// }, nil +// } + +// // Transient error. +// if err != nil { +// return nil, err +// } + +// // Only copy Secret Type to not take ownership of annotations or labels on +// // Apply. +// return &corev1.Secret{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: crt.Spec.SecretName, +// Namespace: crt.Namespace, +// }, +// Data: make(map[string][]byte), +// // Use the existing Secret's type since this may not be of type +// // `kubernetes.io/tls`, if for example it was created beforehand. Type is +// // immutable, so we must keep it to its original value. +// Type: existingSecret.Type, +// }, nil +// } + +// // setKeystores will set extra Secret Data keys according to any Keystores +// // which have been configured. +// func (s *SecretsManager) setKeystores(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { +// // Handle the experimental PKCS12 support +// if crt.Spec.Keystores != nil && crt.Spec.Keystores.PKCS12 != nil && crt.Spec.Keystores.PKCS12.Create { +// ref := crt.Spec.Keystores.PKCS12.PasswordSecretRef +// pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) +// if err != nil { +// return fmt.Errorf("fetching PKCS12 keystore password from Secret: %v", err) +// } +// if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { +// return fmt.Errorf("PKCS12 keystore password Secret contains no data for key %q", ref.Key) +// } +// pw := pwSecret.Data[ref.Key] +// keystoreData, err := encodePKCS12Keystore(string(pw), data.PrivateKey, data.Certificate, data.CA) +// if err != nil { +// return fmt.Errorf("error encoding PKCS12 bundle: %w", err) +// } +// // always overwrite the keystore entry for now +// secret.Data[pkcs12SecretKey] = keystoreData + +// if len(data.CA) > 0 { +// truststoreData, err := encodePKCS12Truststore(string(pw), data.CA) +// if err != nil { +// return fmt.Errorf("error encoding PKCS12 trust store bundle: %w", err) +// } +// // always overwrite the truststore entry +// secret.Data[pkcs12TruststoreKey] = truststoreData +// } +// } + +// // Handle the experimental JKS support +// if crt.Spec.Keystores != nil && crt.Spec.Keystores.JKS != nil && crt.Spec.Keystores.JKS.Create { +// ref := crt.Spec.Keystores.JKS.PasswordSecretRef +// pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) +// if err != nil { +// return fmt.Errorf("fetching JKS keystore password from Secret: %v", err) +// } +// if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { +// return fmt.Errorf("JKS keystore password Secret contains no data for key %q", ref.Key) +// } +// pw := pwSecret.Data[ref.Key] +// keystoreData, err := encodeJKSKeystore(pw, data.PrivateKey, data.Certificate, data.CA) +// if err != nil { +// return fmt.Errorf("error encoding JKS bundle: %w", err) +// } +// // always overwrite the keystore entry +// secret.Data[jksSecretKey] = keystoreData + +// if len(data.CA) > 0 { +// truststoreData, err := encodeJKSTruststore(pw, data.CA) +// if err != nil { +// return fmt.Errorf("error encoding JKS trust store bundle: %w", err) +// } +// // always overwrite the keystore entry +// secret.Data[jksTruststoreKey] = truststoreData +// } +// } + +// return nil +// } + +// // setAdditionalOutputFormat will set extra Secret Data keys with additional +// // output formats according to any OutputFormats which have been configured. +// func setAdditionalOutputFormats(crt *acmapi.Certificate, secret *corev1.Secret, data SecretData) error { +// for _, format := range crt.Spec.AdditionalOutputFormats { +// switch format.Type { +// case cmapi.CertificateOutputFormatDER: +// // Store binary format of the private key +// secret.Data[cmapi.CertificateOutputFormatDERKey] = certificates.OutputFormatDER(data.PrivateKey) +// case cmapi.CertificateOutputFormatCombinedPEM: +// // Combine tls.key and tls.crt +// secret.Data[cmapi.CertificateOutputFormatCombinedPEMKey] = certificates.OutputFormatCombinedPEM(data.PrivateKey, data.Certificate) +// default: +// return fmt.Errorf("unknown additional output format %s", format.Type) +// } +// } + +// return nil +// } diff --git a/pkg/controller/certificates/issuing/issuing_controller.go b/pkg/controller/certificates/issuing/issuing_controller.go new file mode 100644 index 0000000..90d31b5 --- /dev/null +++ b/pkg/controller/certificates/issuing/issuing_controller.go @@ -0,0 +1,52 @@ +package issuing + +import ( + "context" + + acmapi "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" + controllerpkg "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/controller" + "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/controller/certificates/issuing/internal" + "github.com/go-logr/logr" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/utils/clock" +) + +const ( + ControllerName = "certificates-issuing" +) + +// This controller observes the state of the certificate's 'Issuing' condition, +// which will then copy the signed certificates and private key to the target +// Secret resource. +type controller struct { + secretLister corelisters.SecretLister + clock clock.Clock + + client kubernetes.Interface + + // secretsUpdateData is used by the SecretTemplate controller for + // re-reconciling Secrets where the SecretTemplate is not up to date with a + // Certificate's secret. + secretsUpdateData func(context.Context, *acmapi.Certificate, internal.SecretData) error +} + +func NewController( + log logr.Logger, + kubeClient kubernetes.Interface, + factory informers.SharedInformerFactory, + clock clock.Clock, + certificateControllerOptions controllerpkg.CertificateOptions, + fieldManager string, +) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { + + // // create a queue used to queue up items to be processed + // queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second, time.Second*30), ControllerName) + + // // obtain references to all the informers used by this controller + // certificateInforomer := acmFa + return nil, nil, nil +} diff --git a/pkg/controller/certificates/suite_test.go b/pkg/controller/certificates/suite_test.go new file mode 100644 index 0000000..d0a7a4f --- /dev/null +++ b/pkg/controller/certificates/suite_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2022. + +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 controllers + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + anthoscertmanagerv1 "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/apis/anthoscertmanager/v1" + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + + ctrl.SetLogger(logf.Log) + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = anthoscertmanagerv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/pkg/controller/context.go b/pkg/controller/context.go new file mode 100644 index 0000000..bba0a5a --- /dev/null +++ b/pkg/controller/context.go @@ -0,0 +1,212 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "time" + + "k8s.io/client-go/discovery" + kubeinformers "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/flowcontrol" + "k8s.io/utils/clock" + + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" + "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/util" + "github.com/go-logr/logr" +) + +// This sets the informer's resync period to 10 hours +// following the controller-runtime defaults +// and following discussion: https://github.com/kubernetes-sigs/controller-runtime/pull/88#issuecomment-408500629 +const resyncPeriod = 10 * time.Hour + +// Context contains various types that are used by controller implementations. +// We purposely don't have specific informers/listers here, and instead keep a +// reference to a SharedInformerFactory so that controllers can choose +// themselves which listers are required. +// Each component should be given distinct Contexts, built from the +// ContextFactory that has configured the underlying client to use separate +// User Agents. +type Context struct { + // RootContext is the root context for the controller + RootContext context.Context + + // StopCh is a channel that will be closed when the controller is signalled + // to exit + StopCh <-chan struct{} + + // FieldManager is the string that should be used as the field manager when + // applying API object. This value is derived from the user agent. + FieldManager string + // RESTConfig is the loaded Kubernetes apiserver rest client configuration + RESTConfig *rest.Config + // Client is a Kubernetes clientset + Client kubernetes.Interface + // DiscoveryClient is a discovery interface. Usually set to Client.Discovery unless a fake client is in use. + DiscoveryClient discovery.DiscoveryInterface + + // KubeSharedInformerFactory can be used to obtain shared + // SharedIndexInformer instances for Kubernetes types + KubeSharedInformerFactory kubeinformers.SharedInformerFactory + + ContextOptions +} + +// ContextOptions are static Controller Context options. +type ContextOptions struct { + // APIServerHost is the host address of the target Kubernetes API server. + APIServerHost string + + // Kubeconfig is the optional file path location to a kubeconfig to connect + // and authenticate to the API server. + Kubeconfig string + + // Kubernetes API QPS is the value of the maximum QPS to the API server from + // clients. + KubernetesAPIQPS float32 + + // KubernetesAPIBurst is the value of the Maximum burst for throttle. + KubernetesAPIBurst int + + // Namespace is the namespace to operate within. + // If unset, operates on all namespaces + Namespace string + + // Clock should be used to access the current time instead of relying on + // time.Now, to make it easier to test controllers that utilise time + Clock clock.Clock + + IssuerOptions + CertificateOptions + SchedulerOptions +} + +type IssuerOptions struct { + // ClusterResourceNamespace is the namespace to store resources created by + // non-namespaced resources (e.g. ClusterIssuer) in. + ClusterResourceNamespace string + + // ClusterIssuerAmbientCredentials controls whether a cluster issuer should + // pick up ambient credentials, such as those from metadata services, to + // construct clients. + ClusterIssuerAmbientCredentials bool + + // IssuerAmbientCredentials controls whether an issuer should pick up ambient + // credentials, such as those from metadata services, to construct clients. + IssuerAmbientCredentials bool +} + +type CertificateOptions struct { + // EnableOwnerRef controls whether the certificate is configured as an owner of + // secret where the effective TLS certificate is stored. + EnableOwnerRef bool + // CopiedAnnotationPrefixes defines which annotations should be copied + // Certificate -> CertificateRequest, CertificateRequest -> Order. + CopiedAnnotationPrefixes []string +} + +type SchedulerOptions struct { + // MaxConcurrentChallenges determines the maximum number of challenges that can be + // scheduled as 'processing' at once. + MaxConcurrentChallenges int +} + +// ContextFactory is used for constructing new Contexts who's clients have been +// configured with a User Agent built from the component name. +type ContextFactory struct { + // baseRestConfig is the base Kubernetes REST config that can authenticate to + // the Kubernetes API server. + baseRestConfig *rest.Config + + // log is the factory logger which is used to construct event broadcasters. + log logr.Logger + + // ctx is the base controller Context that all Contexts will be built from. + ctx *Context +} + +// NewContextFactory builds a ContextFactory that builds controller Contexts +// that have been configured for that components User Agent. +// All resulting Context's and clients contain the same RateLimiter and +// corresponding QPS and Burst buckets. +func NewContextFactory(ctx context.Context, opts ContextOptions) (*ContextFactory, error) { + // Load the users Kubernetes config + restConfig, err := clientcmd.BuildConfigFromFlags(opts.APIServerHost, opts.Kubeconfig) + if err != nil { + return nil, fmt.Errorf("error creating rest config: %w", err) + } + restConfig = util.RestConfigWithUserAgent(restConfig) + restConfig.QPS = opts.KubernetesAPIQPS + restConfig.Burst = opts.KubernetesAPIBurst + + // Construct a single RateLimiter used across all built Context's clients. A + // single rate limiter (with corresponding QPS and Burst buckets) are + // preserved for all Contexts. + // Adapted from + // https://github.com/kubernetes/client-go/blob/v0.23.3/kubernetes/clientset.go#L431-L435 + if restConfig.RateLimiter == nil && restConfig.QPS > 0 { + if restConfig.Burst <= 0 { + return nil, errors.New("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + restConfig.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(restConfig.QPS, restConfig.Burst) + } + + clients, err := buildClients(restConfig) + if err != nil { + return nil, err + } + + kubeSharedInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(clients.kubeClient, resyncPeriod, kubeinformers.WithNamespace(opts.Namespace)) + + return &ContextFactory{ + baseRestConfig: restConfig, + log: logf.FromContext(ctx), + ctx: &Context{ + RootContext: ctx, + StopCh: ctx.Done(), + KubeSharedInformerFactory: kubeSharedInformerFactory, + ContextOptions: opts, + }, + }, nil +} + +// Build builds a new controller Context who's clients have a User Agent +// derived from the optional component name. +func (c *ContextFactory) Build(component ...string) (*Context, error) { + restConfig := util.RestConfigWithUserAgent(c.baseRestConfig, component...) + + clients, err := buildClients(restConfig) + if err != nil { + return nil, err + } + + ctx := *c.ctx + ctx.FieldManager = util.PrefixFromUserAgent(restConfig.UserAgent) + ctx.RESTConfig = restConfig + ctx.Client = clients.kubeClient + ctx.DiscoveryClient = clients.kubeClient.Discovery() + + return &ctx, nil +} + +// contextClients is a helper struct containing API clients. +type contextClients struct { + kubeClient kubernetes.Interface +} + +// buildClients builds all required clients for the context using the given +// REST config. +func buildClients(restConfig *rest.Config) (contextClients, error) { + + // Create a Kubernetes api client + kubeClient, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return contextClients{}, fmt.Errorf("error creating kubernetes client: %w", err) + } + + return contextClients{kubeClient}, nil +} diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go new file mode 100644 index 0000000..244bb3f --- /dev/null +++ b/pkg/controller/controller.go @@ -0,0 +1,140 @@ +package controller + +import ( + "context" + "sync" + "time" + + logf "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/logs" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" +) + +type runFunc func(context.Context) + +type runPeriodicFunc struct { + fn runFunc + interval time.Duration +} + +type controller struct { + // ctx is the root golang context for the controller + ctx context.Context + + // name of the controller + name string + + // the function that should be called when an item is popped + // off the workqueue + syncHandler func(ctx context.Context, key string) error + + // mustSync is a slice of informers that must have synced before + // this controller can start + mustSync []cache.InformerSynced + + // a set of functions will be called just after the controller initialization, once. + runFirstFuncs []runFunc + + // a set of functions will be called every interval, periodic. + periodicFuncs []runPeriodicFunc + + // queue is a reference to the queue used to enqueue resources + // to be processed + queue workqueue.RateLimitingInterface +} + +type queueingController interface { + Register(*Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) + ProcessItem(ctx context.Context, key string) error +} + +// Create a new controller instance +func NewController(ctx context.Context, + name string, + syncFunc func(ctx context.Context, key string) error, + mustSync []cache.InformerSynced, + runPeriodicFunc []runPeriodicFunc, + queue workqueue.RateLimitingInterface) Interface { + return &controller{ + ctx: ctx, + name: name, + syncHandler: syncFunc, + mustSync: mustSync, + periodicFuncs: runPeriodicFunc, + queue: queue, + } +} + +// Run starts the controller loop +func (c *controller) Run(workers int, stopCh <-chan struct{}) error { + ctx, cancel := context.WithCancel(c.ctx) + defer cancel() + log := logf.FromContext(ctx) + + log.V(logf.DebugLevel).Info("starting control loop") + return nil + + var wg sync.WaitGroup + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + c.worker(ctx) + }() + } + + // run the intial funcs + for _, f := range c.runFirstFuncs { + f(ctx) + } + + // fun the periodical funcs + for _, f := range c.periodicFuncs { + f := f // capture range variable + go wait.Until(func() { f.fn(ctx) }, f.interval, stopCh) + } + + <-stopCh + log.V(logf.InfoLevel).Info("shutting down queue as workqueue signaled shutdown") + c.queue.ShutDown() + log.V(logf.DebugLevel).Info("waiting for workers to exit...") + wg.Wait() + log.V(logf.DebugLevel).Info("workers exited") + return nil +} + +func (c *controller) worker(ctx context.Context) { + log := logf.FromContext(ctx) + log.V(logf.DebugLevel).Info("starting worker") + for { + obj, shutdown := c.queue.Get() + if shutdown { + break + } + + var key string + // use an inlined function so we can use defer + func() { + defer c.queue.Done(obj) + var ok bool + if key, ok = obj.(string); !ok { + return + } + log := log.WithValues("key", key) + log.V(logf.DebugLevel).Info("syncing item") + + err := c.syncHandler(ctx, key) + if err != nil { + log.Error(err, "re-queuing item due to error processing") + + c.queue.AddRateLimited(obj) + return + } + + log.V(logf.DebugLevel).Info("finished processing work item") + c.queue.Forget(obj) + }() + } + log.V(logf.DebugLevel).Info("exiting worker loop") +} diff --git a/pkg/controller/register.go b/pkg/controller/register.go new file mode 100644 index 0000000..c602e6c --- /dev/null +++ b/pkg/controller/register.go @@ -0,0 +1,32 @@ +package controller + +// This file defines types for controllers to register themselves with the +// controller package. + +// Interface represents a controller that can be run. +type Interface interface { + // Run will start a controller. 'workers' should be the number of + // independent goroutines for this controller in question that are to be + // run, and the workers should shut down upon a signal on stopCh. + // This method should block until all workers have exited cleanly, thus + // allowing for graceful shutdown of control loops. + Run(workers int, stopCh <-chan struct{}) error +} + +// Constructor is a function that creates a new control loop given a +// controller Context. +type Constructor func(ctx *ContextFactory) (Interface, error) + +var ( + known = make(map[string]Constructor) +) + +// Known returns a map of the registered controller Constructors +func Known() map[string]Constructor { + return known +} + +// Register registers a controller constructor with the controller package +func Register(name string, fn Constructor) { + known[name] = fn +} diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go new file mode 100644 index 0000000..11d3c70 --- /dev/null +++ b/pkg/logs/logs.go @@ -0,0 +1,159 @@ +package logs + +import ( + "context" + "flag" + "fmt" + "log" + "time" + + api "gitbucket.jerxie.com/yangyangxie/AnthosCertManager/pkg/api" + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" +) + +var ( + Log = klogr.New().WithName("anthos-cert-manager") +) + +const ( + // Following analog to https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md + + ErrorLevel = 0 + WarnLevel = 1 + InfoLevel = 2 + ExtendedInfoLevel = 3 + DebugLevel = 4 + TraceLevel = 5 +) + +var logFlushFreq = flag.Duration("log-flush-frequency", 5*time.Second, "Maximum number of seconds between log flushes") + +// GlogWriter serves as a bridge between the standard log package and the glog package. +type GlogWriter struct{} + +// Write implements the io.Writer interface. +func (writer GlogWriter) Write(data []byte) (n int, err error) { + klog.Info(string(data)) + return len(data), nil +} + +// InitLogs initializes logs the way we want for kubernetes. +func InitLogs(fs *flag.FlagSet) { + if fs == nil { + fs = flag.CommandLine + } + klog.InitFlags(fs) + _ = fs.Set("logtostderr", "true") + + log.SetOutput(GlogWriter{}) + log.SetFlags(0) + + // The default glog flush interval is 30 seconds, which is frighteningly long. + go wait.Until(klog.Flush, *logFlushFreq, wait.NeverStop) +} + +// FlushLogs flushes logs immediately. +func FlushLogs() { + klog.Flush() +} + +const ( + ResourceNameKey = "resource_name" + ResourceNamespaceKey = "resource_namespace" + ResourceKindKey = "resource_kind" + ResourceVersionKey = "resource_version" + + RelatedResourceNameKey = "related_resource_name" + RelatedResourceNamespaceKey = "related_resource_namespace" + RelatedResourceKindKey = "related_resource_kind" + RelatedResourceVersionKey = "related_resource_version" +) + +func WithResource(l logr.Logger, obj metav1.Object) logr.Logger { + var gvk schema.GroupVersionKind + + if runtimeObj, ok := obj.(runtime.Object); ok { + gvks, _, _ := api.Scheme.ObjectKinds(runtimeObj) + if len(gvks) > 0 { + gvk = gvks[0] + } + } + + return l.WithValues( + ResourceNameKey, obj.GetName(), + ResourceNamespaceKey, obj.GetNamespace(), + ResourceKindKey, gvk.Kind, + ResourceVersionKey, gvk.Version, + ) +} + +func WithRelatedResource(l logr.Logger, obj metav1.Object) logr.Logger { + var gvk schema.GroupVersionKind + + if runtimeObj, ok := obj.(runtime.Object); ok { + gvks, _, _ := api.Scheme.ObjectKinds(runtimeObj) + if len(gvks) > 0 { + gvk = gvks[0] + } + } + + return l.WithValues( + RelatedResourceNameKey, obj.GetName(), + RelatedResourceNamespaceKey, obj.GetNamespace(), + RelatedResourceKindKey, gvk.Kind, + RelatedResourceVersionKey, gvk.Version, + ) +} + +func WithRelatedResourceName(l logr.Logger, name, namespace, kind string) logr.Logger { + return l.WithValues( + RelatedResourceNameKey, name, + RelatedResourceNamespaceKey, namespace, + RelatedResourceKindKey, kind, + ) +} + +func FromContext(ctx context.Context, names ...string) logr.Logger { + l, err := logr.FromContext(ctx) + if err != nil { + l = Log + } + for _, n := range names { + l = l.WithName(n) + } + return l +} + +func NewContext(ctx context.Context, l logr.Logger, names ...string) context.Context { + for _, n := range names { + l = l.WithName(n) + } + return logr.NewContext(ctx, l) +} + +func V(level int) klog.Verbose { + return klog.V(klog.Level(level)) +} + +// LogWithFormat is a wrapper for logger that adds Infof method to log messages +// with the given format and arguments. +// +// Used as a patch to the controller eventBroadcaster for sending non-string objects. +type LogWithFormat struct { + logr.Logger +} + +func WithInfof(l logr.Logger) *LogWithFormat { + return &LogWithFormat{l} +} + +// Infof logs message with the given format and arguments. +func (l *LogWithFormat) Infof(format string, a ...interface{}) { + l.Info(fmt.Sprintf(format, a...)) +} diff --git a/pkg/util/useragent.go b/pkg/util/useragent.go new file mode 100644 index 0000000..319d399 --- /dev/null +++ b/pkg/util/useragent.go @@ -0,0 +1,44 @@ +package util + +import ( + "bytes" + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/client-go/rest" +) + +// RestConfigWithUserAgent returns a copy of the Kubernetes REST config with +// the User Agent set which includes the optional component strings given. +func RestConfigWithUserAgent(restConfig *rest.Config, component ...string) *rest.Config { + restConfig = rest.CopyConfig(restConfig) + restConfig.UserAgent = fmt.Sprintf("%s/%s (%s) anthos-cert-manager/%s", + strings.Join(append([]string{"anthos-cert-manager"}, component...), "-"), + version(), VersionInfo().Platform, VersionInfo().GitCommit) + return restConfig +} + +// PrefixFromUserAgent takes the characters preceding the first /, quote +// unprintable character and then trim what's beyond the FieldManagerMaxLength +// limit. +// Taken from +// https://github.com/kubernetes/kubernetes/blob/9a75e7b0fd1b567f774a3373be640e19b33e7ef1/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go#L252 +func PrefixFromUserAgent(u string) string { + m := strings.Split(u, "/")[0] + buf := bytes.NewBuffer(nil) + for _, r := range m { + // Ignore non-printable characters + if !unicode.IsPrint(r) { + continue + } + // Only append if we have room for it + if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength { + break + } + buf.WriteRune(r) + } + return buf.String() +} diff --git a/pkg/util/version.go b/pkg/util/version.go new file mode 100644 index 0000000..a88c0e3 --- /dev/null +++ b/pkg/util/version.go @@ -0,0 +1,44 @@ +package util + +import ( + "fmt" + "runtime" +) + +type Version struct { + GitVersion string `json:"gitVersion"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` + GoVersion string `json:"goVersion"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` +} + +// This variable block holds information used to build up the version string +var ( + AppGitState = "" + AppGitCommit = "" + AppVersion = "canary" +) + +func VersionInfo() Version { + return Version{ + GitVersion: AppVersion, + GitCommit: AppGitCommit, + GitTreeState: AppGitState, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +} + +func version() string { + v := AppVersion + if AppVersion == "canary" && AppGitCommit != "" { + v += "-" + AppGitCommit + } + if AppGitState != "" { + v += fmt.Sprintf(" (%v)", AppGitState) + } + return v +}